Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e32d251cc1 | ||
|
|
9dd5e7bf1d | ||
|
|
b6de6035f6 | ||
|
|
88ccaf0b83 | ||
|
|
52c8bc075f | ||
|
|
2537cd5271 | ||
|
|
db91625de4 | ||
|
|
df78386fbe | ||
|
|
a1d70f740a | ||
|
|
187f42277a | ||
|
|
e1f89bb569 | ||
|
|
1d94f8ab2b | ||
|
|
045ecabb78 | ||
|
|
e6c3cb078a | ||
|
|
afa51b3ff6 | ||
|
|
f9c80b2285 | ||
|
|
fc5cf44b2c | ||
|
|
0c0f1663b1 | ||
|
|
306d8494d6 | ||
|
|
f5c00c3e2d | ||
|
|
ac9571c6b2 | ||
|
|
934fafb64b | ||
|
|
d51514015f | ||
|
|
a9cfd16d53 | ||
|
|
1a6f26fa3b | ||
|
|
0dd723b29f | ||
|
|
7ad6fc8e73 | ||
|
|
31c7e6362b | ||
|
|
072b42d867 | ||
|
|
5d66c193aa | ||
|
|
aa729515b9 | ||
|
|
54b7e23974 | ||
|
|
ad80e0c1ab | ||
|
|
5d7b278957 | ||
|
|
678caaf6a0 | ||
|
|
7228cd7b12 | ||
|
|
7b598a3534 | ||
|
|
9cdc9e9153 | ||
|
|
71ab0416b0 | ||
|
|
10a13bc8a7 | ||
|
|
be386a8e33 | ||
|
|
c33fb8bb97 | ||
|
|
20f20f051b | ||
|
|
179274ade0 | ||
|
|
84607e332b | ||
|
|
8186ef2342 | ||
|
|
19b184adba | ||
|
|
a97fd35d6e | ||
|
|
470ca020e2 | ||
|
|
f64d7c4343 | ||
|
|
c6f68a64e6 | ||
|
|
5aaa122460 | ||
|
|
de169c027f | ||
|
|
314c9663a2 | ||
|
|
21995eb3e3 | ||
|
|
6fc700bd62 | ||
|
|
acdbe2163e | ||
|
|
c3a231e0ab | ||
|
|
984e143336 | ||
|
|
e2ba2f82c0 | ||
|
|
ace5e97e68 | ||
|
|
82d42297e8 | ||
|
|
f99d5e8656 | ||
|
|
0795008c23 | ||
|
|
c975a86a70 | ||
|
|
69eee345d2 | ||
|
|
48afc05bcb | ||
|
|
39a62f5db7 | ||
|
|
006b218ade |
@@ -1,2 +1,2 @@
|
||||
FROM qmcgaw/godevcontainer
|
||||
RUN apk add wireguard-tools
|
||||
RUN apk add wireguard-tools htop
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"vscode"
|
||||
],
|
||||
"shutdownAction": "stopCompose",
|
||||
"postCreateCommand": "~/.windows.sh && go mod download && go mod tidy",
|
||||
"postCreateCommand": "source ~/.windows.sh && go mod download && go mod tidy",
|
||||
"workspaceFolder": "/workspace",
|
||||
"extensions": [
|
||||
"golang.go",
|
||||
@@ -25,6 +25,7 @@
|
||||
"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",
|
||||
|
||||
@@ -3,7 +3,6 @@ version: "3.7"
|
||||
services:
|
||||
vscode:
|
||||
build: .
|
||||
image: godevcontainer
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
volumes:
|
||||
@@ -11,16 +10,16 @@ services:
|
||||
# Docker socket to access Docker server
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
# Docker configuration
|
||||
- ~/.docker:/root/.docker:z
|
||||
- ~/.docker:/root/.docker
|
||||
# SSH directory for Linux, OSX and WSL
|
||||
- ~/.ssh:/root/.ssh:z
|
||||
- ~/.ssh:/root/.ssh
|
||||
# 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:z
|
||||
- ~/.zsh_history:/root/.zsh_history
|
||||
# Git config
|
||||
- ~/.gitconfig:/root/.gitconfig:z
|
||||
- ~/.gitconfig:/root/.gitconfig
|
||||
environment:
|
||||
- TZ=
|
||||
cap_add:
|
||||
|
||||
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
env:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: reviewdog/action-misspell@v1
|
||||
with:
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
needs: [verify]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# 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.8.0
|
||||
uses: docker/build-push-action@v2.10.0
|
||||
with:
|
||||
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
25
.github/workflows/codeql.yml
vendored
Normal file
25
.github/workflows/codeql.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
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
|
||||
2
.github/workflows/dependabot.yml
vendored
2
.github/workflows/dependabot.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
env:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build test image
|
||||
run: docker build --target test -t test-container .
|
||||
|
||||
4
.github/workflows/dockerhub-description.yml
vendored
4
.github/workflows/dockerhub-description.yml
vendored
@@ -10,9 +10,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.4.0
|
||||
uses: actions/checkout@v3
|
||||
- name: Docker Hub Description
|
||||
uses: peter-evans/dockerhub-description@v2
|
||||
uses: peter-evans/dockerhub-description@v3
|
||||
with:
|
||||
username: qmcgaw
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
2
.github/workflows/fork.yml
vendored
2
.github/workflows/fork.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
env:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Linting
|
||||
run: docker build --target lint .
|
||||
|
||||
2
.github/workflows/labels.yml
vendored
2
.github/workflows/labels.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
labeler:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- uses: actions/checkout@v3
|
||||
- uses: crazy-max/ghaction-github-labeler@v3
|
||||
with:
|
||||
yaml-file: .github/labels.yml
|
||||
|
||||
@@ -124,7 +124,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
||||
LOG_LEVEL=info \
|
||||
# Health
|
||||
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
|
||||
HEALTH_ADDRESS_TO_PING=github.com \
|
||||
HEALTH_TARGET_ADDRESS=cloudflare.com:443 \
|
||||
HEALTH_VPN_DURATION_INITIAL=6s \
|
||||
HEALTH_VPN_DURATION_ADDITION=5s \
|
||||
# DNS over TLS
|
||||
@@ -181,7 +181,7 @@ EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
|
||||
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheck
|
||||
ARG TARGETPLATFORM
|
||||
RUN apk add --no-cache --update -l apk-tools && \
|
||||
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.11-r0 && \
|
||||
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.12-r0 && \
|
||||
mv /usr/sbin/openvpn /usr/sbin/openvpn2.4 && \
|
||||
apk del openvpn && \
|
||||
apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \
|
||||
|
||||
@@ -5,7 +5,6 @@ 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!
|
||||
|
||||

|
||||
|
||||
@@ -102,6 +101,8 @@ 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
|
||||
|
||||
@@ -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,12 +64,11 @@ func main() {
|
||||
}
|
||||
|
||||
background := context.Background()
|
||||
signalCtx, stop := signal.NotifyContext(background, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
|
||||
signalCh := make(chan os.Signal, 1)
|
||||
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)
|
||||
ctx, cancel := context.WithCancel(background)
|
||||
|
||||
logger := logging.New(logging.Settings{
|
||||
Level: logging.LevelInfo,
|
||||
})
|
||||
logger := log.New(log.SetLevel(log.LevelInfo))
|
||||
|
||||
args := os.Args
|
||||
tun := tun.New()
|
||||
@@ -88,13 +87,11 @@ func main() {
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-signalCtx.Done():
|
||||
stop()
|
||||
case signal := <-signalCh:
|
||||
fmt.Println("")
|
||||
logger.Warn("Caught OS signal, shutting down")
|
||||
logger.Warn("Caught OS signal " + signal.String() + ", shutting down")
|
||||
cancel()
|
||||
case err := <-errorCh:
|
||||
stop()
|
||||
close(errorCh)
|
||||
if err == nil { // expected exit such as healthcheck
|
||||
os.Exit(0)
|
||||
@@ -113,6 +110,8 @@ 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)
|
||||
@@ -124,7 +123,7 @@ var (
|
||||
|
||||
//nolint:gocognit,gocyclo,maintidx
|
||||
func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
args []string, logger logging.ParentLogger, source sources.Source,
|
||||
args []string, logger log.LoggerInterface, source sources.Source,
|
||||
tun tun.Interface, netLinker netlink.NetLinker, cmder command.RunStarter,
|
||||
cli cli.CLIer) error {
|
||||
if len(args) > 1 { // cli operation
|
||||
@@ -174,17 +173,15 @@ 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.PatchLevel(*allSettings.Log.Level)
|
||||
logger.Patch(log.SetLevel(*allSettings.Log.Level))
|
||||
|
||||
routingLogger := logger.NewChild(logging.Settings{
|
||||
Prefix: "routing: ",
|
||||
})
|
||||
routingLogger := logger.New(log.SetComponent("routing"))
|
||||
if *allSettings.Firewall.Debug { // To remove in v4
|
||||
routingLogger.PatchLevel(logging.LevelDebug)
|
||||
routingLogger.Patch(log.SetLevel(log.LevelDebug))
|
||||
}
|
||||
routingConf := routing.New(netLinker, routingLogger)
|
||||
|
||||
defaultInterface, defaultGateway, err := routingConf.DefaultRoute()
|
||||
defaultRoutes, err := routingConf.DefaultRoutes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -194,19 +191,16 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
return err
|
||||
}
|
||||
|
||||
defaultIP, err := routingConf.DefaultIP()
|
||||
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)
|
||||
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 {
|
||||
@@ -215,7 +209,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.NewChild(logging.Settings{Prefix: "storage: "})
|
||||
storageLogger := logger.New(log.SetComponent("storage"))
|
||||
storage, err := storage.New(storageLogger, constants.ServersData)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -228,7 +222,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
return err
|
||||
}
|
||||
|
||||
allSettings.Pprof.HTTPServer.Logger = logger
|
||||
allSettings.Pprof.HTTPServer.Logger = logger.New(log.SetComponent("pprof"))
|
||||
pprofServer, err := pprof.New(allSettings.Pprof)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create Pprof server: %w", err)
|
||||
@@ -241,7 +235,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
// Create configurators
|
||||
alpineConf := alpine.New()
|
||||
ovpnConf := openvpn.New(
|
||||
logger.NewChild(logging.Settings{Prefix: "openvpn configurator: "}),
|
||||
logger.New(log.SetComponent("openvpn configurator")),
|
||||
cmder, puid, pgid)
|
||||
dnsCrypto := dnscrypto.New(httpClient, "", "")
|
||||
const cacertsPath = "/etc/ssl/certs/ca-certificates.crt"
|
||||
@@ -294,9 +288,9 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
return fmt.Errorf("cannot setup routing: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
logger.Info("routing cleanup...")
|
||||
routingLogger.Info("routing cleanup...")
|
||||
if err := routingConf.TearDown(); err != nil {
|
||||
logger.Error("cannot teardown routing: " + err.Error())
|
||||
routingLogger.Error("cannot teardown routing: " + err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -317,9 +311,11 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
}
|
||||
|
||||
for _, port := range allSettings.Firewall.InputPorts {
|
||||
err = firewallConf.SetAllowedPort(ctx, port, defaultInterface)
|
||||
if err != nil {
|
||||
return err
|
||||
for _, defaultRoute := range defaultRoutes {
|
||||
err = firewallConf.SetAllowedPort(ctx, port, defaultRoute.NetInterface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} // TODO move inside firewall?
|
||||
|
||||
@@ -346,14 +342,14 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
otherGroupHandler.Add(pprofHandler)
|
||||
<-pprofReady
|
||||
|
||||
portForwardLogger := logger.NewChild(logging.Settings{Prefix: "port forwarding: "})
|
||||
portForwardLogger := logger.New(log.SetComponent("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.NewChild(logging.Settings{Prefix: "dns over tls: "})
|
||||
unboundLogger := logger.New(log.SetComponent("dns over tls"))
|
||||
unboundLooper := dns.NewLoop(dnsConf, allSettings.DNS, httpClient,
|
||||
unboundLogger)
|
||||
dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler(
|
||||
@@ -368,7 +364,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
controlGroupHandler.Add(dnsTickerHandler)
|
||||
|
||||
publicIPLooper := publicip.NewLoop(httpClient,
|
||||
logger.NewChild(logging.Settings{Prefix: "ip getter: "}),
|
||||
logger.New(log.SetComponent("ip getter")),
|
||||
allSettings.PublicIP, puid, pgid)
|
||||
pubIPHandler, pubIPCtx, pubIPDone := goshutdown.NewGoRoutineHandler(
|
||||
"public IP", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
@@ -380,7 +376,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone)
|
||||
tickersGroupHandler.Add(pubIPTickerHandler)
|
||||
|
||||
vpnLogger := logger.NewChild(logging.Settings{Prefix: "vpn: "})
|
||||
vpnLogger := logger.New(log.SetComponent("vpn"))
|
||||
vpnLooper := vpn.NewLoop(allSettings.VPN, allSettings.Firewall.VPNInputPorts,
|
||||
allServers, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
|
||||
cmder, publicIPLooper, unboundLooper, vpnLogger, httpClient,
|
||||
@@ -391,7 +387,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
|
||||
updaterLooper := updater.NewLooper(allSettings.Updater,
|
||||
allServers, storage, vpnLooper.SetServers, httpClient,
|
||||
logger.NewChild(logging.Settings{Prefix: "updater: "}))
|
||||
logger.New(log.SetComponent("updater")))
|
||||
updaterHandler, updaterCtx, updaterDone := goshutdown.NewGoRoutineHandler(
|
||||
"updater", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
|
||||
@@ -404,7 +400,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
controlGroupHandler.Add(updaterTickerHandler)
|
||||
|
||||
httpProxyLooper := httpproxy.NewLoop(
|
||||
logger.NewChild(logging.Settings{Prefix: "http proxy: "}),
|
||||
logger.New(log.SetComponent("http proxy")),
|
||||
allSettings.HTTPProxy)
|
||||
httpProxyHandler, httpProxyCtx, httpProxyDone := goshutdown.NewGoRoutineHandler(
|
||||
"http proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
@@ -412,7 +408,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
otherGroupHandler.Add(httpProxyHandler)
|
||||
|
||||
shadowsocksLooper := shadowsocks.NewLooper(allSettings.Shadowsocks,
|
||||
logger.NewChild(logging.Settings{Prefix: "shadowsocks: "}))
|
||||
logger.New(log.SetComponent("shadowsocks")))
|
||||
shadowsocksHandler, shadowsocksCtx, shadowsocksDone := goshutdown.NewGoRoutineHandler(
|
||||
"shadowsocks proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
go shadowsocksLooper.Run(shadowsocksCtx, shadowsocksDone)
|
||||
@@ -422,13 +418,18 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
controlServerLogging := *allSettings.ControlServer.Log
|
||||
httpServerHandler, httpServerCtx, httpServerDone := goshutdown.NewGoRoutineHandler(
|
||||
"http server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
httpServer := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
|
||||
logger.NewChild(logging.Settings{Prefix: "http server: "}),
|
||||
httpServer, err := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
|
||||
logger.New(log.SetComponent("http server")),
|
||||
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper)
|
||||
go httpServer.Run(httpServerCtx, httpServerDone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot setup control server: %w", err)
|
||||
}
|
||||
httpServerReady := make(chan struct{})
|
||||
go httpServer.Run(httpServerCtx, httpServerReady, httpServerDone)
|
||||
<-httpServerReady
|
||||
controlGroupHandler.Add(httpServerHandler)
|
||||
|
||||
healthLogger := logger.NewChild(logging.Settings{Prefix: "healthcheck: "})
|
||||
healthLogger := logger.New(log.SetComponent("healthcheck"))
|
||||
healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger, vpnLooper)
|
||||
healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler(
|
||||
"HTTP health server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||
|
||||
8
go.mod
8
go.mod
@@ -3,9 +3,8 @@ module github.com/qdm12/gluetun
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/breml/rootcerts v0.2.2
|
||||
github.com/breml/rootcerts v0.2.3
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/go-ping/ping v0.0.0-20210911151512-381826476871
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/qdm12/dns v1.11.0
|
||||
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
|
||||
@@ -13,9 +12,10 @@ 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.0
|
||||
github.com/stretchr/testify v1.7.1
|
||||
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
|
||||
@@ -26,7 +26,6 @@ require (
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/google/go-cmp v0.5.5 // indirect
|
||||
github.com/google/uuid v1.2.0 // indirect
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
|
||||
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
@@ -41,6 +40,5 @@ require (
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
|
||||
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
)
|
||||
|
||||
15
go.sum
15
go.sum
@@ -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.2 h1:hkHEpbTdYaNvDoYeq+mwRvCeg/YTTl23DjQ1Tnj71Zs=
|
||||
github.com/breml/rootcerts v0.2.2/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
|
||||
github.com/breml/rootcerts v0.2.3 h1:1vkYjKOiHVSyuz9Ue4AOrViEvUm8gk8phTg0vbcuU0A=
|
||||
github.com/breml/rootcerts v0.2.3/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=
|
||||
@@ -32,8 +32,6 @@ github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsd
|
||||
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
||||
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
|
||||
github.com/go-ping/ping v0.0.0-20210911151512-381826476871 h1:wtjTfjwAR/BYYMJ+QOLI/3J/qGEI0fgrkZvgsEWK2/Q=
|
||||
github.com/go-ping/ping v0.0.0-20210911151512-381826476871/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
@@ -47,8 +45,6 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gotify/go-api-client/v2 v2.0.4/go.mod h1:VKiah/UK20bXsr0JObE1eBVLW44zbBouzjuri9iwjFU=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
@@ -119,6 +115,8 @@ 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=
|
||||
@@ -132,8 +130,9 @@ 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=
|
||||
@@ -176,7 +175,6 @@ golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d h1:nTDGCTeAu2LhcsHTRzjyIUbZHCJ4QePArsm27Hka0UM=
|
||||
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
@@ -210,7 +208,6 @@ golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/storage"
|
||||
)
|
||||
|
||||
@@ -28,26 +29,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, "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")
|
||||
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")
|
||||
if err := flagSet.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -66,45 +67,45 @@ func (c *CLI) FormatServers(args []string) error {
|
||||
var formatted string
|
||||
switch {
|
||||
case cyberghost:
|
||||
formatted = currentServers.Cyberghost.ToMarkdown()
|
||||
formatted = currentServers.Cyberghost.ToMarkdown(providers.Cyberghost)
|
||||
case expressvpn:
|
||||
formatted = currentServers.Expressvpn.ToMarkdown()
|
||||
formatted = currentServers.Expressvpn.ToMarkdown(providers.Expressvpn)
|
||||
case fastestvpn:
|
||||
formatted = currentServers.Fastestvpn.ToMarkdown()
|
||||
formatted = currentServers.Fastestvpn.ToMarkdown(providers.Fastestvpn)
|
||||
case hideMyAss:
|
||||
formatted = currentServers.HideMyAss.ToMarkdown()
|
||||
formatted = currentServers.HideMyAss.ToMarkdown(providers.HideMyAss)
|
||||
case ipvanish:
|
||||
formatted = currentServers.Ipvanish.ToMarkdown()
|
||||
formatted = currentServers.Ipvanish.ToMarkdown(providers.Ipvanish)
|
||||
case ivpn:
|
||||
formatted = currentServers.Ivpn.ToMarkdown()
|
||||
formatted = currentServers.Ivpn.ToMarkdown(providers.Ivpn)
|
||||
case mullvad:
|
||||
formatted = currentServers.Mullvad.ToMarkdown()
|
||||
formatted = currentServers.Mullvad.ToMarkdown(providers.Mullvad)
|
||||
case nordvpn:
|
||||
formatted = currentServers.Nordvpn.ToMarkdown()
|
||||
formatted = currentServers.Nordvpn.ToMarkdown(providers.Nordvpn)
|
||||
case perfectPrivacy:
|
||||
formatted = currentServers.Perfectprivacy.ToMarkdown()
|
||||
formatted = currentServers.Perfectprivacy.ToMarkdown(providers.Perfectprivacy)
|
||||
case pia:
|
||||
formatted = currentServers.Pia.ToMarkdown()
|
||||
formatted = currentServers.Pia.ToMarkdown(providers.PrivateInternetAccess)
|
||||
case privado:
|
||||
formatted = currentServers.Privado.ToMarkdown()
|
||||
formatted = currentServers.Privado.ToMarkdown(providers.Privado)
|
||||
case privatevpn:
|
||||
formatted = currentServers.Privatevpn.ToMarkdown()
|
||||
formatted = currentServers.Privatevpn.ToMarkdown(providers.Privatevpn)
|
||||
case protonvpn:
|
||||
formatted = currentServers.Protonvpn.ToMarkdown()
|
||||
formatted = currentServers.Protonvpn.ToMarkdown(providers.Protonvpn)
|
||||
case purevpn:
|
||||
formatted = currentServers.Purevpn.ToMarkdown()
|
||||
formatted = currentServers.Purevpn.ToMarkdown(providers.Purevpn)
|
||||
case surfshark:
|
||||
formatted = currentServers.Surfshark.ToMarkdown()
|
||||
formatted = currentServers.Surfshark.ToMarkdown(providers.Surfshark)
|
||||
case torguard:
|
||||
formatted = currentServers.Torguard.ToMarkdown()
|
||||
formatted = currentServers.Torguard.ToMarkdown(providers.Torguard)
|
||||
case vpnUnlimited:
|
||||
formatted = currentServers.VPNUnlimited.ToMarkdown()
|
||||
formatted = currentServers.VPNUnlimited.ToMarkdown(providers.VPNUnlimited)
|
||||
case vyprvpn:
|
||||
formatted = currentServers.Vyprvpn.ToMarkdown()
|
||||
formatted = currentServers.Vyprvpn.ToMarkdown(providers.Vyprvpn)
|
||||
case wevpn:
|
||||
formatted = currentServers.Wevpn.ToMarkdown()
|
||||
formatted = currentServers.Wevpn.ToMarkdown(providers.Wevpn)
|
||||
case windscribe:
|
||||
formatted = currentServers.Windscribe.ToMarkdown()
|
||||
formatted = currentServers.Windscribe.ToMarkdown(providers.Windscribe)
|
||||
default:
|
||||
return ErrProviderUnspecified
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ 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"
|
||||
@@ -62,8 +63,8 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
|
||||
}
|
||||
|
||||
if updateAll {
|
||||
for _, provider := range constants.AllProviders() {
|
||||
if provider == constants.Custom {
|
||||
for _, provider := range providers.All() {
|
||||
if provider == providers.Custom {
|
||||
continue
|
||||
}
|
||||
options.Providers = append(options.Providers, provider)
|
||||
|
||||
@@ -15,10 +15,10 @@ type Health struct {
|
||||
// for the health check server.
|
||||
// It cannot be the empty string in the internal state.
|
||||
ServerAddress string
|
||||
// AddressToPing is the IP address or domain name to
|
||||
// ping periodically for the health check.
|
||||
// TargetAddress is the address (host or host:port)
|
||||
// to TCP dial to periodically for the health check.
|
||||
// It cannot be the empty string in the internal state.
|
||||
AddressToPing string
|
||||
TargetAddress string
|
||||
VPN HealthyWait
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ func (h Health) Validate() (err error) {
|
||||
func (h *Health) copy() (copied Health) {
|
||||
return Health{
|
||||
ServerAddress: h.ServerAddress,
|
||||
AddressToPing: h.AddressToPing,
|
||||
TargetAddress: h.TargetAddress,
|
||||
VPN: h.VPN.copy(),
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ func (h *Health) copy() (copied Health) {
|
||||
// unset field of the receiver settings object.
|
||||
func (h *Health) MergeWith(other Health) {
|
||||
h.ServerAddress = helpers.MergeWithString(h.ServerAddress, other.ServerAddress)
|
||||
h.AddressToPing = helpers.MergeWithString(h.AddressToPing, other.AddressToPing)
|
||||
h.TargetAddress = helpers.MergeWithString(h.TargetAddress, other.TargetAddress)
|
||||
h.VPN.mergeWith(other.VPN)
|
||||
}
|
||||
|
||||
@@ -59,13 +59,13 @@ func (h *Health) MergeWith(other Health) {
|
||||
// settings.
|
||||
func (h *Health) OverrideWith(other Health) {
|
||||
h.ServerAddress = helpers.OverrideWithString(h.ServerAddress, other.ServerAddress)
|
||||
h.AddressToPing = helpers.OverrideWithString(h.AddressToPing, other.AddressToPing)
|
||||
h.TargetAddress = helpers.OverrideWithString(h.TargetAddress, other.TargetAddress)
|
||||
h.VPN.overrideWith(other.VPN)
|
||||
}
|
||||
|
||||
func (h *Health) SetDefaults() {
|
||||
h.ServerAddress = helpers.DefaultString(h.ServerAddress, "127.0.0.1:9999")
|
||||
h.AddressToPing = helpers.DefaultString(h.AddressToPing, "github.com")
|
||||
h.TargetAddress = helpers.DefaultString(h.TargetAddress, "cloudflare.com:443")
|
||||
h.VPN.setDefaults()
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ func (h Health) String() string {
|
||||
func (h Health) toLinesNode() (node *gotree.Node) {
|
||||
node = gotree.New("Health settings:")
|
||||
node.Appendf("Server listening address: %s", h.ServerAddress)
|
||||
node.Appendf("Address to ping: %s", h.AddressToPing)
|
||||
node.Appendf("Target address: %s", h.TargetAddress)
|
||||
node.AppendNode(h.VPN.toLinesNode("VPN"))
|
||||
return node
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/log"
|
||||
"inet.af/netaddr"
|
||||
)
|
||||
|
||||
@@ -44,6 +44,15 @@ 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
|
||||
@@ -62,11 +71,11 @@ func CopyDurationPtr(original *time.Duration) (copied *time.Duration) {
|
||||
return copied
|
||||
}
|
||||
|
||||
func CopyLogLevelPtr(original *logging.Level) (copied *logging.Level) {
|
||||
func CopyLogLevelPtr(original *log.Level) (copied *log.Level) {
|
||||
if original == nil {
|
||||
return nil
|
||||
}
|
||||
copied = new(logging.Level)
|
||||
copied = new(log.Level)
|
||||
*copied = *original
|
||||
return copied
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/log"
|
||||
)
|
||||
|
||||
func DefaultInt(existing *int, defaultValue int) (
|
||||
@@ -36,6 +36,15 @@ 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) {
|
||||
@@ -74,12 +83,12 @@ func DefaultDuration(existing *time.Duration,
|
||||
return result
|
||||
}
|
||||
|
||||
func DefaultLogLevel(existing *logging.Level,
|
||||
defaultValue logging.Level) (result *logging.Level) {
|
||||
func DefaultLogLevel(existing *log.Level,
|
||||
defaultValue log.Level) (result *log.Level) {
|
||||
if existing != nil {
|
||||
return existing
|
||||
}
|
||||
result = new(logging.Level)
|
||||
result = new(log.Level)
|
||||
*result = defaultValue
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/log"
|
||||
"inet.af/netaddr"
|
||||
)
|
||||
|
||||
@@ -78,6 +78,17 @@ 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
|
||||
@@ -96,13 +107,13 @@ func MergeWithDuration(existing, other *time.Duration) (result *time.Duration) {
|
||||
return other
|
||||
}
|
||||
|
||||
func MergeWithLogLevel(existing, other *logging.Level) (result *logging.Level) {
|
||||
func MergeWithLogLevel(existing, other *log.Level) (result *log.Level) {
|
||||
if existing != nil {
|
||||
return existing
|
||||
} else if other == nil {
|
||||
return nil
|
||||
}
|
||||
result = new(logging.Level)
|
||||
result = new(log.Level)
|
||||
*result = *other
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/log"
|
||||
"inet.af/netaddr"
|
||||
)
|
||||
|
||||
@@ -68,6 +68,15 @@ 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
|
||||
@@ -86,11 +95,11 @@ func OverrideWithDuration(existing, other *time.Duration) (result *time.Duration
|
||||
return result
|
||||
}
|
||||
|
||||
func OverrideWithLogLevel(existing, other *logging.Level) (result *logging.Level) {
|
||||
func OverrideWithLogLevel(existing, other *log.Level) (result *log.Level) {
|
||||
if other == nil {
|
||||
return existing
|
||||
}
|
||||
result = new(logging.Level)
|
||||
result = new(log.Level)
|
||||
*result = *other
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -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 *logging.Level
|
||||
Level *log.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, logging.LevelInfo)
|
||||
l.Level = helpers.DefaultLogLevel(l.Level, log.LevelInfo)
|
||||
}
|
||||
|
||||
func (l Log) String() string {
|
||||
|
||||
@@ -2,10 +2,12 @@ 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"
|
||||
)
|
||||
@@ -73,6 +75,8 @@ 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}
|
||||
@@ -81,13 +85,16 @@ func (o OpenVPN) validate(vpnProvider string) (err error) {
|
||||
ErrOpenVPNVersionIsNotValid, o.Version, strings.Join(validVersions, ", "))
|
||||
}
|
||||
|
||||
isCustom := vpnProvider == constants.Custom
|
||||
isCustom := vpnProvider == providers.Custom
|
||||
|
||||
if !isCustom && o.User == "" {
|
||||
return ErrOpenVPNUserIsEmpty
|
||||
}
|
||||
|
||||
if !isCustom && o.Password == "" {
|
||||
passwordRequired := !isCustom &&
|
||||
(vpnProvider != providers.Ivpn || !ivpnAccountID.MatchString(o.User))
|
||||
|
||||
if passwordRequired && o.Password == "" {
|
||||
return ErrOpenVPNPasswordIsEmpty
|
||||
}
|
||||
|
||||
@@ -147,8 +154,8 @@ func validateOpenVPNClientCertificate(vpnProvider,
|
||||
clientCert string) (err error) {
|
||||
switch vpnProvider {
|
||||
case
|
||||
constants.Cyberghost,
|
||||
constants.VPNUnlimited:
|
||||
providers.Cyberghost,
|
||||
providers.VPNUnlimited:
|
||||
if clientCert == "" {
|
||||
return ErrMissingValue
|
||||
}
|
||||
@@ -168,9 +175,9 @@ func validateOpenVPNClientCertificate(vpnProvider,
|
||||
func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) {
|
||||
switch vpnProvider {
|
||||
case
|
||||
constants.Cyberghost,
|
||||
constants.VPNUnlimited,
|
||||
constants.Wevpn:
|
||||
providers.Cyberghost,
|
||||
providers.VPNUnlimited,
|
||||
providers.Wevpn:
|
||||
if clientKey == "" {
|
||||
return ErrMissingValue
|
||||
}
|
||||
@@ -250,7 +257,7 @@ func (o *OpenVPN) overrideWith(other OpenVPN) {
|
||||
|
||||
func (o *OpenVPN) setDefaults(vpnProvider string) {
|
||||
o.Version = helpers.DefaultString(o.Version, constants.Openvpn25)
|
||||
if vpnProvider == constants.Mullvad {
|
||||
if vpnProvider == providers.Mullvad {
|
||||
o.Password = "m"
|
||||
}
|
||||
|
||||
@@ -260,7 +267,7 @@ func (o *OpenVPN) setDefaults(vpnProvider string) {
|
||||
o.ClientKey = helpers.DefaultStringPtr(o.ClientKey, "")
|
||||
|
||||
var defaultEncPreset string
|
||||
if vpnProvider == constants.PrivateInternetAccess {
|
||||
if vpnProvider == providers.PrivateInternetAccess {
|
||||
defaultEncPreset = constants.PIAEncryptionPresetStrong
|
||||
}
|
||||
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
|
||||
|
||||
44
internal/configuration/settings/openvpn_test.go
Normal file
44
internal/configuration/settings/openvpn_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ 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"
|
||||
)
|
||||
|
||||
@@ -39,11 +40,11 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
||||
|
||||
// Validate TCP
|
||||
if *o.TCP && helpers.IsOneOf(vpnProvider,
|
||||
constants.Ipvanish,
|
||||
constants.Perfectprivacy,
|
||||
constants.Privado,
|
||||
constants.VPNUnlimited,
|
||||
constants.Vyprvpn,
|
||||
providers.Ipvanish,
|
||||
providers.Perfectprivacy,
|
||||
providers.Privado,
|
||||
providers.VPNUnlimited,
|
||||
providers.Vyprvpn,
|
||||
) {
|
||||
return fmt.Errorf("%w: for VPN service provider %s",
|
||||
ErrOpenVPNTCPNotSupported, vpnProvider)
|
||||
@@ -53,33 +54,39 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
||||
if *o.CustomPort != 0 {
|
||||
switch vpnProvider {
|
||||
// no restriction on port
|
||||
case constants.Cyberghost, constants.HideMyAss,
|
||||
constants.PrivateInternetAccess, constants.Privatevpn,
|
||||
constants.Protonvpn, constants.Torguard:
|
||||
case providers.Cyberghost, providers.HideMyAss,
|
||||
providers.PrivateInternetAccess, providers.Privatevpn,
|
||||
providers.Protonvpn, providers.Torguard:
|
||||
// no custom port allowed
|
||||
case constants.Expressvpn, constants.Fastestvpn,
|
||||
constants.Ipvanish, constants.Nordvpn,
|
||||
constants.Privado, constants.Purevpn,
|
||||
constants.Surfshark, constants.VPNUnlimited,
|
||||
constants.Vyprvpn:
|
||||
case providers.Expressvpn, providers.Fastestvpn,
|
||||
providers.Ipvanish, providers.Nordvpn,
|
||||
providers.Privado, providers.Purevpn,
|
||||
providers.Surfshark, providers.VPNUnlimited,
|
||||
providers.Vyprvpn:
|
||||
return fmt.Errorf("%w: for VPN service provider %s",
|
||||
ErrOpenVPNCustomPortNotAllowed, vpnProvider)
|
||||
default:
|
||||
var allowedTCP, allowedUDP []uint16
|
||||
switch vpnProvider {
|
||||
case constants.Ivpn:
|
||||
case providers.Ivpn:
|
||||
allowedTCP = []uint16{80, 443, 1143}
|
||||
allowedUDP = []uint16{53, 1194, 2049, 2050}
|
||||
case constants.Mullvad:
|
||||
case providers.Mullvad:
|
||||
allowedTCP = []uint16{80, 443, 1401}
|
||||
allowedUDP = []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400}
|
||||
case constants.Perfectprivacy:
|
||||
case providers.Perfectprivacy:
|
||||
allowedTCP = []uint16{44, 443, 4433}
|
||||
allowedUDP = []uint16{44, 443, 4433}
|
||||
case constants.Wevpn:
|
||||
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:
|
||||
allowedTCP = []uint16{53, 1195, 1199, 2018}
|
||||
allowedUDP = []uint16{80, 1194, 1198}
|
||||
case constants.Windscribe:
|
||||
case providers.Windscribe:
|
||||
allowedTCP = []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783}
|
||||
allowedUDP = []uint16{53, 80, 123, 443, 1194, 54783}
|
||||
}
|
||||
@@ -97,7 +104,7 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
||||
}
|
||||
|
||||
// Validate EncPreset
|
||||
if vpnProvider == constants.PrivateInternetAccess {
|
||||
if vpnProvider == providers.PrivateInternetAccess {
|
||||
validEncryptionPresets := []string{
|
||||
constants.PIAEncryptionPresetNone,
|
||||
constants.PIAEncryptionPresetNormal,
|
||||
@@ -142,7 +149,7 @@ func (o *OpenVPNSelection) setDefaults(vpnProvider string) {
|
||||
o.CustomPort = helpers.DefaultUint16(o.CustomPort, 0)
|
||||
|
||||
var defaultEncPreset string
|
||||
if vpnProvider == constants.PrivateInternetAccess {
|
||||
if vpnProvider == providers.PrivateInternetAccess {
|
||||
defaultEncPreset = constants.PIAEncryptionPresetStrong
|
||||
}
|
||||
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"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/gotree"
|
||||
)
|
||||
|
||||
@@ -28,7 +28,7 @@ func (p PortForwarding) validate(vpnProvider string) (err error) {
|
||||
}
|
||||
|
||||
// Validate Enabled
|
||||
validProviders := []string{constants.PrivateInternetAccess}
|
||||
validProviders := []string{providers.PrivateInternetAccess}
|
||||
if !helpers.IsOneOf(vpnProvider, validProviders...) {
|
||||
return fmt.Errorf("%w: for provider %s, it is only available for %s",
|
||||
ErrPortForwardingEnabled, vpnProvider, strings.Join(validProviders, ", "))
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
"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/constants/vpn"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
@@ -25,15 +26,15 @@ type Provider struct {
|
||||
func (p *Provider) validate(vpnType string, allServers models.AllServers) (err error) {
|
||||
// Validate Name
|
||||
var validNames []string
|
||||
if vpnType == constants.OpenVPN {
|
||||
validNames = constants.AllProviders()
|
||||
if vpnType == vpn.OpenVPN {
|
||||
validNames = providers.All()
|
||||
validNames = append(validNames, "pia") // Retro-compatibility
|
||||
} else { // Wireguard
|
||||
validNames = []string{
|
||||
constants.Custom,
|
||||
constants.Ivpn,
|
||||
constants.Mullvad,
|
||||
constants.Windscribe,
|
||||
providers.Custom,
|
||||
providers.Ivpn,
|
||||
providers.Mullvad,
|
||||
providers.Windscribe,
|
||||
}
|
||||
}
|
||||
if !helpers.IsOneOf(*p.Name, validNames...) {
|
||||
@@ -75,7 +76,7 @@ func (p *Provider) overrideWith(other Provider) {
|
||||
}
|
||||
|
||||
func (p *Provider) setDefaults() {
|
||||
p.Name = helpers.DefaultStringPtr(p.Name, constants.PrivateInternetAccess)
|
||||
p.Name = helpers.DefaultStringPtr(p.Name, providers.PrivateInternetAccess)
|
||||
p.ServerSelection.setDefaults(*p.Name)
|
||||
p.PortForwarding.setDefaults()
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ import (
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/validation"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
@@ -69,7 +70,7 @@ var (
|
||||
func (ss *ServerSelection) validate(vpnServiceProvider string,
|
||||
allServers models.AllServers) (err error) {
|
||||
switch ss.VPN {
|
||||
case constants.OpenVPN, constants.Wireguard:
|
||||
case vpn.OpenVPN, vpn.Wireguard:
|
||||
default:
|
||||
return fmt.Errorf("%w: %s", ErrVPNTypeNotValid, ss.VPN)
|
||||
}
|
||||
@@ -90,15 +91,15 @@ func (ss *ServerSelection) validate(vpnServiceProvider string,
|
||||
}
|
||||
|
||||
if *ss.OwnedOnly &&
|
||||
vpnServiceProvider != constants.Mullvad {
|
||||
vpnServiceProvider != providers.Mullvad {
|
||||
return fmt.Errorf("%w: for VPN service provider %s",
|
||||
ErrOwnedOnlyNotSupported, vpnServiceProvider)
|
||||
}
|
||||
|
||||
if *ss.FreeOnly &&
|
||||
!helpers.IsOneOf(vpnServiceProvider,
|
||||
constants.Protonvpn,
|
||||
constants.VPNUnlimited,
|
||||
providers.Protonvpn,
|
||||
providers.VPNUnlimited,
|
||||
) {
|
||||
return fmt.Errorf("%w: for VPN service provider %s",
|
||||
ErrFreeOnlyNotSupported, vpnServiceProvider)
|
||||
@@ -106,20 +107,20 @@ func (ss *ServerSelection) validate(vpnServiceProvider string,
|
||||
|
||||
if *ss.StreamOnly &&
|
||||
!helpers.IsOneOf(vpnServiceProvider,
|
||||
constants.Protonvpn,
|
||||
constants.VPNUnlimited,
|
||||
providers.Protonvpn,
|
||||
providers.VPNUnlimited,
|
||||
) {
|
||||
return fmt.Errorf("%w: for VPN service provider %s",
|
||||
ErrStreamOnlyNotSupported, vpnServiceProvider)
|
||||
}
|
||||
|
||||
if *ss.MultiHopOnly &&
|
||||
vpnServiceProvider != constants.Surfshark {
|
||||
vpnServiceProvider != providers.Surfshark {
|
||||
return fmt.Errorf("%w: for VPN service provider %s",
|
||||
ErrMultiHopOnlyNotSupported, vpnServiceProvider)
|
||||
}
|
||||
|
||||
if ss.VPN == constants.OpenVPN {
|
||||
if ss.VPN == vpn.OpenVPN {
|
||||
err = ss.OpenVPN.validate(vpnServiceProvider)
|
||||
if err != nil {
|
||||
return fmt.Errorf("OpenVPN server selection settings: %w", err)
|
||||
@@ -140,85 +141,85 @@ func getLocationFilterChoices(vpnServiceProvider string, ss *ServerSelection,
|
||||
ispChoices, nameChoices, hostnameChoices []string,
|
||||
err error) {
|
||||
switch vpnServiceProvider {
|
||||
case constants.Custom:
|
||||
case constants.Cyberghost:
|
||||
case providers.Custom:
|
||||
case providers.Cyberghost:
|
||||
servers := allServers.GetCyberghost()
|
||||
countryChoices = validation.CyberghostCountryChoices(servers)
|
||||
hostnameChoices = validation.CyberghostHostnameChoices(servers)
|
||||
case constants.Expressvpn:
|
||||
countryChoices = validation.ExtractCountries(servers)
|
||||
hostnameChoices = validation.ExtractHostnames(servers)
|
||||
case providers.Expressvpn:
|
||||
servers := allServers.GetExpressvpn()
|
||||
countryChoices = validation.ExpressvpnCountriesChoices(servers)
|
||||
cityChoices = validation.ExpressvpnCityChoices(servers)
|
||||
hostnameChoices = validation.ExpressvpnHostnameChoices(servers)
|
||||
case constants.Fastestvpn:
|
||||
countryChoices = validation.ExtractCountries(servers)
|
||||
cityChoices = validation.ExtractCities(servers)
|
||||
hostnameChoices = validation.ExtractHostnames(servers)
|
||||
case providers.Fastestvpn:
|
||||
servers := allServers.GetFastestvpn()
|
||||
countryChoices = validation.FastestvpnCountriesChoices(servers)
|
||||
hostnameChoices = validation.FastestvpnHostnameChoices(servers)
|
||||
case constants.HideMyAss:
|
||||
countryChoices = validation.ExtractCountries(servers)
|
||||
hostnameChoices = validation.ExtractHostnames(servers)
|
||||
case providers.HideMyAss:
|
||||
servers := allServers.GetHideMyAss()
|
||||
countryChoices = validation.HideMyAssCountryChoices(servers)
|
||||
regionChoices = validation.HideMyAssRegionChoices(servers)
|
||||
cityChoices = validation.HideMyAssCityChoices(servers)
|
||||
hostnameChoices = validation.HideMyAssHostnameChoices(servers)
|
||||
case constants.Ipvanish:
|
||||
countryChoices = validation.ExtractCountries(servers)
|
||||
regionChoices = validation.ExtractRegions(servers)
|
||||
cityChoices = validation.ExtractCities(servers)
|
||||
hostnameChoices = validation.ExtractHostnames(servers)
|
||||
case providers.Ipvanish:
|
||||
servers := allServers.GetIpvanish()
|
||||
countryChoices = validation.IpvanishCountryChoices(servers)
|
||||
cityChoices = validation.IpvanishCityChoices(servers)
|
||||
hostnameChoices = validation.IpvanishHostnameChoices(servers)
|
||||
case constants.Ivpn:
|
||||
countryChoices = validation.ExtractCountries(servers)
|
||||
cityChoices = validation.ExtractCities(servers)
|
||||
hostnameChoices = validation.ExtractHostnames(servers)
|
||||
case providers.Ivpn:
|
||||
servers := allServers.GetIvpn()
|
||||
countryChoices = validation.IvpnCountryChoices(servers)
|
||||
cityChoices = validation.IvpnCityChoices(servers)
|
||||
ispChoices = validation.IvpnISPChoices(servers)
|
||||
hostnameChoices = validation.IvpnHostnameChoices(servers)
|
||||
case constants.Mullvad:
|
||||
countryChoices = validation.ExtractCountries(servers)
|
||||
cityChoices = validation.ExtractCities(servers)
|
||||
ispChoices = validation.ExtractISPs(servers)
|
||||
hostnameChoices = validation.ExtractHostnames(servers)
|
||||
case providers.Mullvad:
|
||||
servers := allServers.GetMullvad()
|
||||
countryChoices = validation.MullvadCountryChoices(servers)
|
||||
cityChoices = validation.MullvadCityChoices(servers)
|
||||
ispChoices = validation.MullvadISPChoices(servers)
|
||||
hostnameChoices = validation.MullvadHostnameChoices(servers)
|
||||
case constants.Nordvpn:
|
||||
countryChoices = validation.ExtractCountries(servers)
|
||||
cityChoices = validation.ExtractCities(servers)
|
||||
ispChoices = validation.ExtractISPs(servers)
|
||||
hostnameChoices = validation.ExtractHostnames(servers)
|
||||
case providers.Nordvpn:
|
||||
servers := allServers.GetNordvpn()
|
||||
regionChoices = validation.NordvpnRegionChoices(servers)
|
||||
hostnameChoices = validation.NordvpnHostnameChoices(servers)
|
||||
case constants.Perfectprivacy:
|
||||
regionChoices = validation.ExtractRegions(servers)
|
||||
hostnameChoices = validation.ExtractHostnames(servers)
|
||||
case providers.Perfectprivacy:
|
||||
servers := allServers.GetPerfectprivacy()
|
||||
cityChoices = validation.PerfectprivacyCityChoices(servers)
|
||||
case constants.Privado:
|
||||
cityChoices = validation.ExtractCities(servers)
|
||||
case providers.Privado:
|
||||
servers := allServers.GetPrivado()
|
||||
countryChoices = validation.PrivadoCountryChoices(servers)
|
||||
regionChoices = validation.PrivadoRegionChoices(servers)
|
||||
cityChoices = validation.PrivadoCityChoices(servers)
|
||||
hostnameChoices = validation.PrivadoHostnameChoices(servers)
|
||||
case constants.PrivateInternetAccess:
|
||||
countryChoices = validation.ExtractCountries(servers)
|
||||
regionChoices = validation.ExtractRegions(servers)
|
||||
cityChoices = validation.ExtractCities(servers)
|
||||
hostnameChoices = validation.ExtractHostnames(servers)
|
||||
case providers.PrivateInternetAccess:
|
||||
servers := allServers.GetPia()
|
||||
regionChoices = validation.PIAGeoChoices(servers)
|
||||
hostnameChoices = validation.PIAHostnameChoices(servers)
|
||||
nameChoices = validation.PIANameChoices(servers)
|
||||
case constants.Privatevpn:
|
||||
regionChoices = validation.ExtractRegions(servers)
|
||||
hostnameChoices = validation.ExtractHostnames(servers)
|
||||
nameChoices = validation.ExtractServerNames(servers)
|
||||
case providers.Privatevpn:
|
||||
servers := allServers.GetPrivatevpn()
|
||||
countryChoices = validation.PrivatevpnCountryChoices(servers)
|
||||
cityChoices = validation.PrivatevpnCityChoices(servers)
|
||||
hostnameChoices = validation.PrivatevpnHostnameChoices(servers)
|
||||
case constants.Protonvpn:
|
||||
countryChoices = validation.ExtractCountries(servers)
|
||||
cityChoices = validation.ExtractCities(servers)
|
||||
hostnameChoices = validation.ExtractHostnames(servers)
|
||||
case providers.Protonvpn:
|
||||
servers := allServers.GetProtonvpn()
|
||||
countryChoices = validation.ProtonvpnCountryChoices(servers)
|
||||
regionChoices = validation.ProtonvpnRegionChoices(servers)
|
||||
cityChoices = validation.ProtonvpnCityChoices(servers)
|
||||
nameChoices = validation.ProtonvpnNameChoices(servers)
|
||||
hostnameChoices = validation.ProtonvpnHostnameChoices(servers)
|
||||
case constants.Purevpn:
|
||||
countryChoices = validation.ExtractCountries(servers)
|
||||
regionChoices = validation.ExtractRegions(servers)
|
||||
cityChoices = validation.ExtractCities(servers)
|
||||
nameChoices = validation.ExtractServerNames(servers)
|
||||
hostnameChoices = validation.ExtractHostnames(servers)
|
||||
case providers.Purevpn:
|
||||
servers := allServers.GetPurevpn()
|
||||
countryChoices = validation.PurevpnCountryChoices(servers)
|
||||
regionChoices = validation.PurevpnRegionChoices(servers)
|
||||
cityChoices = validation.PurevpnCityChoices(servers)
|
||||
hostnameChoices = validation.PurevpnHostnameChoices(servers)
|
||||
case constants.Surfshark:
|
||||
countryChoices = validation.ExtractCountries(servers)
|
||||
regionChoices = validation.ExtractRegions(servers)
|
||||
cityChoices = validation.ExtractCities(servers)
|
||||
hostnameChoices = validation.ExtractHostnames(servers)
|
||||
case providers.Surfshark:
|
||||
servers := allServers.GetSurfshark()
|
||||
countryChoices = validation.SurfsharkCountryChoices(servers)
|
||||
cityChoices = validation.SurfsharkCityChoices(servers)
|
||||
hostnameChoices = validation.SurfsharkHostnameChoices(servers)
|
||||
regionChoices = validation.SurfsharkRegionChoices(servers)
|
||||
countryChoices = validation.ExtractCountries(servers)
|
||||
cityChoices = validation.ExtractCities(servers)
|
||||
hostnameChoices = validation.ExtractHostnames(servers)
|
||||
regionChoices = validation.ExtractRegions(servers)
|
||||
// TODO v4 remove
|
||||
regionChoices = append(regionChoices, validation.SurfsharkRetroLocChoices()...)
|
||||
if err := helpers.AreAllOneOf(ss.Regions, regionChoices); err != nil {
|
||||
@@ -227,28 +228,28 @@ func getLocationFilterChoices(vpnServiceProvider string, ss *ServerSelection,
|
||||
// Retro compatibility
|
||||
// TODO remove in v4
|
||||
*ss = surfsharkRetroRegion(*ss)
|
||||
case constants.Torguard:
|
||||
case providers.Torguard:
|
||||
servers := allServers.GetTorguard()
|
||||
countryChoices = validation.TorguardCountryChoices(servers)
|
||||
cityChoices = validation.TorguardCityChoices(servers)
|
||||
hostnameChoices = validation.TorguardHostnameChoices(servers)
|
||||
case constants.VPNUnlimited:
|
||||
countryChoices = validation.ExtractCountries(servers)
|
||||
cityChoices = validation.ExtractCities(servers)
|
||||
hostnameChoices = validation.ExtractHostnames(servers)
|
||||
case providers.VPNUnlimited:
|
||||
servers := allServers.GetVPNUnlimited()
|
||||
countryChoices = validation.VPNUnlimitedCountryChoices(servers)
|
||||
cityChoices = validation.VPNUnlimitedCityChoices(servers)
|
||||
hostnameChoices = validation.VPNUnlimitedHostnameChoices(servers)
|
||||
case constants.Vyprvpn:
|
||||
countryChoices = validation.ExtractCountries(servers)
|
||||
cityChoices = validation.ExtractCities(servers)
|
||||
hostnameChoices = validation.ExtractHostnames(servers)
|
||||
case providers.Vyprvpn:
|
||||
servers := allServers.GetVyprvpn()
|
||||
regionChoices = validation.VyprvpnRegionChoices(servers)
|
||||
case constants.Wevpn:
|
||||
regionChoices = validation.ExtractRegions(servers)
|
||||
case providers.Wevpn:
|
||||
servers := allServers.GetWevpn()
|
||||
cityChoices = validation.WevpnCityChoices(servers)
|
||||
hostnameChoices = validation.WevpnHostnameChoices(servers)
|
||||
case constants.Windscribe:
|
||||
cityChoices = validation.ExtractCities(servers)
|
||||
hostnameChoices = validation.ExtractHostnames(servers)
|
||||
case providers.Windscribe:
|
||||
servers := allServers.GetWindscribe()
|
||||
regionChoices = validation.WindscribeRegionChoices(servers)
|
||||
cityChoices = validation.WindscribeCityChoices(servers)
|
||||
hostnameChoices = validation.WindscribeHostnameChoices(servers)
|
||||
regionChoices = validation.ExtractRegions(servers)
|
||||
cityChoices = validation.ExtractCities(servers)
|
||||
hostnameChoices = validation.ExtractHostnames(servers)
|
||||
default:
|
||||
return nil, nil, nil, nil, nil, nil, fmt.Errorf("%w: %s", ErrVPNProviderNameNotValid, vpnServiceProvider)
|
||||
}
|
||||
@@ -347,7 +348,7 @@ func (ss *ServerSelection) overrideWith(other ServerSelection) {
|
||||
}
|
||||
|
||||
func (ss *ServerSelection) setDefaults(vpnProvider string) {
|
||||
ss.VPN = helpers.DefaultString(ss.VPN, constants.OpenVPN)
|
||||
ss.VPN = helpers.DefaultString(ss.VPN, vpn.OpenVPN)
|
||||
ss.TargetIP = helpers.DefaultIP(ss.TargetIP, net.IP{})
|
||||
ss.OwnedOnly = helpers.DefaultBool(ss.OwnedOnly, false)
|
||||
ss.FreeOnly = helpers.DefaultBool(ss.FreeOnly, false)
|
||||
@@ -415,7 +416,7 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
|
||||
node.Appendf("Multi-hop only servers: yes")
|
||||
}
|
||||
|
||||
if ss.VPN == constants.OpenVPN {
|
||||
if ss.VPN == vpn.OpenVPN {
|
||||
node.AppendNode(ss.OpenVPN.toLinesNode())
|
||||
} else {
|
||||
node.AppendNode(ss.Wireguard.toLinesNode())
|
||||
|
||||
@@ -66,7 +66,7 @@ func Test_Settings_String(t *testing.T) {
|
||||
| └── Log level: INFO
|
||||
├── Health settings:
|
||||
| ├── Server listening address: 127.0.0.1:9999
|
||||
| ├── Address to ping: github.com
|
||||
| ├── Target address: cloudflare.com:443
|
||||
| └── VPN wait durations:
|
||||
| ├── Initial duration: 6s
|
||||
| └── Additional duration: 5s
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
|
||||
// System contains settings to configure system related elements.
|
||||
type System struct {
|
||||
PUID *uint16
|
||||
PGID *uint16
|
||||
PUID *uint32
|
||||
PGID *uint32
|
||||
Timezone string
|
||||
}
|
||||
|
||||
@@ -19,28 +19,28 @@ func (s System) validate() (err error) {
|
||||
|
||||
func (s *System) copy() (copied System) {
|
||||
return System{
|
||||
PUID: helpers.CopyUint16Ptr(s.PUID),
|
||||
PGID: helpers.CopyUint16Ptr(s.PGID),
|
||||
PUID: helpers.CopyUint32Ptr(s.PUID),
|
||||
PGID: helpers.CopyUint32Ptr(s.PGID),
|
||||
Timezone: s.Timezone,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *System) mergeWith(other System) {
|
||||
s.PUID = helpers.MergeWithUint16(s.PUID, other.PUID)
|
||||
s.PGID = helpers.MergeWithUint16(s.PGID, other.PGID)
|
||||
s.PUID = helpers.MergeWithUint32(s.PUID, other.PUID)
|
||||
s.PGID = helpers.MergeWithUint32(s.PGID, other.PGID)
|
||||
s.Timezone = helpers.MergeWithString(s.Timezone, other.Timezone)
|
||||
}
|
||||
|
||||
func (s *System) overrideWith(other System) {
|
||||
s.PUID = helpers.OverrideWithUint16(s.PUID, other.PUID)
|
||||
s.PGID = helpers.OverrideWithUint16(s.PGID, other.PGID)
|
||||
s.PUID = helpers.OverrideWithUint32(s.PUID, other.PUID)
|
||||
s.PGID = helpers.OverrideWithUint32(s.PGID, other.PGID)
|
||||
s.Timezone = helpers.OverrideWithString(s.Timezone, other.Timezone)
|
||||
}
|
||||
|
||||
func (s *System) setDefaults() {
|
||||
const defaultID = 1000
|
||||
s.PUID = helpers.DefaultUint16(s.PUID, defaultID)
|
||||
s.PGID = helpers.DefaultUint16(s.PGID, defaultID)
|
||||
s.PUID = helpers.DefaultUint32(s.PUID, defaultID)
|
||||
s.PGID = helpers.DefaultUint32(s.PGID, defaultID)
|
||||
}
|
||||
|
||||
func (s System) String() string {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -42,8 +42,8 @@ func (u Updater) Validate() (err error) {
|
||||
|
||||
for i, provider := range u.Providers {
|
||||
valid := false
|
||||
for _, validProvider := range constants.AllProviders() {
|
||||
if validProvider == constants.Custom {
|
||||
for _, validProvider := range providers.All() {
|
||||
if validProvider == providers.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 != constants.Custom {
|
||||
if len(u.Providers) == 0 && vpnProvider != providers.Custom {
|
||||
u.Providers = []string{vpnProvider}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
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)
|
||||
}
|
||||
111
internal/configuration/settings/validation/servers.go
Normal file
111
internal/configuration/settings/validation/servers.go
Normal file
@@ -0,0 +1,111 @@
|
||||
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
|
||||
}
|
||||
@@ -1,44 +1,9 @@
|
||||
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()
|
||||
@@ -49,12 +14,8 @@ func SurfsharkRetroLocChoices() (choices []string) {
|
||||
continue
|
||||
}
|
||||
seen[data.RetroLoc] = struct{}{}
|
||||
choices = append(choices, data.RetroLoc)
|
||||
choices = sortedInsert(choices, data.RetroLoc)
|
||||
}
|
||||
|
||||
sort.Slice(choices, func(i, j int) bool {
|
||||
return choices[i] < choices[j]
|
||||
})
|
||||
|
||||
return choices
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"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{constants.OpenVPN, constants.Wireguard}
|
||||
validVPNTypes := []string{vpn.OpenVPN, vpn.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 == constants.OpenVPN {
|
||||
if v.Type == vpn.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, constants.OpenVPN)
|
||||
v.Type = helpers.DefaultString(v.Type, vpn.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 == constants.OpenVPN {
|
||||
if v.Type == vpn.OpenVPN {
|
||||
node.AppendNode(v.OpenVPN.toLinesNode())
|
||||
} else {
|
||||
node.AppendNode(v.Wireguard.toLinesNode())
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"regexp"
|
||||
|
||||
"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"
|
||||
"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,
|
||||
constants.Custom,
|
||||
constants.Ivpn,
|
||||
constants.Mullvad,
|
||||
constants.Windscribe,
|
||||
providers.Custom,
|
||||
providers.Ivpn,
|
||||
providers.Mullvad,
|
||||
providers.Windscribe,
|
||||
) {
|
||||
// do not validate for VPN provider not supporting Wireguard
|
||||
return nil
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"net"
|
||||
|
||||
"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"
|
||||
"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 constants.Ivpn, constants.Mullvad, constants.Windscribe: // endpoint IP addresses are baked in
|
||||
case constants.Custom:
|
||||
case providers.Ivpn, providers.Mullvad, providers.Windscribe: // endpoint IP addresses are baked in
|
||||
case providers.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 constants.Custom:
|
||||
case providers.Custom:
|
||||
if *w.EndpointPort == 0 {
|
||||
return ErrWireguardEndpointPortNotSet
|
||||
}
|
||||
case constants.Ivpn, constants.Mullvad, constants.Windscribe:
|
||||
case providers.Ivpn, providers.Mullvad, providers.Windscribe:
|
||||
// EndpointPort is optional and can be 0
|
||||
if *w.EndpointPort == 0 {
|
||||
break // no custom endpoint port set
|
||||
}
|
||||
if vpnProvider == constants.Mullvad {
|
||||
if vpnProvider == providers.Mullvad {
|
||||
break // no restriction on custom endpoint port value
|
||||
}
|
||||
var allowed []uint16
|
||||
switch vpnProvider {
|
||||
case constants.Ivpn:
|
||||
case providers.Ivpn:
|
||||
allowed = []uint16{2049, 2050, 53, 30587, 41893, 48574, 58237}
|
||||
case constants.Windscribe:
|
||||
case providers.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 constants.Ivpn, constants.Mullvad, constants.Windscribe: // public keys are baked in
|
||||
case constants.Custom:
|
||||
case providers.Ivpn, providers.Mullvad, providers.Windscribe: // public keys are baked in
|
||||
case providers.Custom:
|
||||
if w.PublicKey == "" {
|
||||
return ErrWireguardPublicKeyNotSet
|
||||
}
|
||||
|
||||
4
internal/configuration/sources/env/health.go
vendored
4
internal/configuration/sources/env/health.go
vendored
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
func (r *Reader) ReadHealth() (health settings.Health, err error) {
|
||||
health.ServerAddress = os.Getenv("HEALTH_SERVER_ADDRESS")
|
||||
health.AddressToPing = os.Getenv("HEALTH_ADDRESS_TO_PING")
|
||||
_, health.TargetAddress = r.getEnvWithRetro("HEALTH_TARGET_ADDRESS", "HEALTH_ADDRESS_TO_PING")
|
||||
|
||||
health.VPN.Initial, err = r.readDurationWithRetro(
|
||||
"HEALTH_VPN_DURATION_INITIAL",
|
||||
@@ -19,7 +19,7 @@ func (r *Reader) ReadHealth() (health settings.Health, err error) {
|
||||
return health, err
|
||||
}
|
||||
|
||||
health.VPN.Initial, err = r.readDurationWithRetro(
|
||||
health.VPN.Addition, err = r.readDurationWithRetro(
|
||||
"HEALTH_VPN_DURATION_ADDITION",
|
||||
"HEALTH_OPENVPN_DURATION_ADDITION")
|
||||
if err != nil {
|
||||
|
||||
@@ -135,5 +135,5 @@ func unsetEnvKeys(envKeys []string, err error) (newErr error) {
|
||||
}
|
||||
|
||||
func stringPtr(s string) *string { return &s }
|
||||
func uint16Ptr(n uint16) *uint16 { return &n }
|
||||
func uint32Ptr(n uint32) *uint32 { return &n }
|
||||
func boolPtr(b bool) *bool { return &b }
|
||||
|
||||
@@ -20,3 +20,7 @@ func setTestEnv(t *testing.T, key, value string) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestXxx(t *testing.T) {
|
||||
t.Log(int(^uint32(0)))
|
||||
}
|
||||
|
||||
16
internal/configuration/sources/env/log.go
vendored
16
internal/configuration/sources/env/log.go
vendored
@@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/log"
|
||||
)
|
||||
|
||||
func readLog() (log settings.Log, err error) {
|
||||
@@ -19,13 +19,13 @@ func readLog() (log settings.Log, err error) {
|
||||
return log, nil
|
||||
}
|
||||
|
||||
func readLogLevel() (level *logging.Level, err error) {
|
||||
func readLogLevel() (level *log.Level, err error) {
|
||||
s := os.Getenv("LOG_LEVEL")
|
||||
if s == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
level = new(logging.Level)
|
||||
level = new(log.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 *logging.Level, err error) {
|
||||
|
||||
var ErrLogLevelUnknown = errors.New("log level is unknown")
|
||||
|
||||
func parseLogLevel(s string) (level logging.Level, err error) {
|
||||
func parseLogLevel(s string) (level log.Level, err error) {
|
||||
switch strings.ToLower(s) {
|
||||
case "debug":
|
||||
return logging.LevelDebug, nil
|
||||
return log.LevelDebug, nil
|
||||
case "info":
|
||||
return logging.LevelInfo, nil
|
||||
return log.LevelInfo, nil
|
||||
case "warning":
|
||||
return logging.LevelWarn, nil
|
||||
return log.LevelWarn, nil
|
||||
case "error":
|
||||
return logging.LevelError, nil
|
||||
return log.LevelError, nil
|
||||
default:
|
||||
return level, fmt.Errorf(
|
||||
"%w: %q is not valid and can be one of debug, info, warning or error",
|
||||
|
||||
@@ -65,6 +65,11 @@ func (r *Reader) readOpenVPN() (
|
||||
return openVPN, fmt.Errorf("environment variable OPENVPN_VERBOSITY: %w", err)
|
||||
}
|
||||
|
||||
flagsStr := os.Getenv("OPENVPN_FLAGS")
|
||||
if flagsStr != "" {
|
||||
openVPN.Flags = strings.Fields(flagsStr)
|
||||
}
|
||||
|
||||
return openVPN, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
)
|
||||
|
||||
func (r *Reader) readProvider(vpnType string) (provider settings.Provider, err error) {
|
||||
@@ -33,13 +34,13 @@ func (r *Reader) readVPNServiceProvider(vpnType string) (vpnProviderPtr *string)
|
||||
_, s := r.getEnvWithRetro("VPN_SERVICE_PROVIDER", "VPNSP")
|
||||
s = strings.ToLower(s)
|
||||
switch {
|
||||
case vpnType != constants.Wireguard &&
|
||||
case vpnType != vpn.Wireguard &&
|
||||
os.Getenv("OPENVPN_CUSTOM_CONFIG") != "": // retro compatibility
|
||||
return stringPtr(constants.Custom)
|
||||
return stringPtr(providers.Custom)
|
||||
case s == "":
|
||||
return nil
|
||||
case s == "pia": // retro compatibility
|
||||
return stringPtr(constants.PrivateInternetAccess)
|
||||
return stringPtr(providers.PrivateInternetAccess)
|
||||
}
|
||||
return stringPtr(s)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -27,7 +27,7 @@ func (r *Reader) readServerSelection(vpnProvider, vpnType string) (
|
||||
|
||||
countriesKey, _ := r.getEnvWithRetro("SERVER_COUNTRIES", "COUNTRY")
|
||||
ss.Countries = envToCSV(countriesKey)
|
||||
if vpnProvider == constants.Cyberghost && len(ss.Countries) == 0 {
|
||||
if vpnProvider == providers.Cyberghost && len(ss.Countries) == 0 {
|
||||
// Retro-compatibility for Cyberghost using the REGION variable
|
||||
ss.Countries = envToCSV("REGION")
|
||||
if len(ss.Countries) > 0 {
|
||||
|
||||
19
internal/configuration/sources/env/system.go
vendored
19
internal/configuration/sources/env/system.go
vendored
@@ -34,20 +34,23 @@ 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 *uint16, err error) {
|
||||
id *uint32, err error) {
|
||||
idEnvKey, idString := r.getEnvWithRetro(key, retroKey)
|
||||
if idString == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
idInt, err := strconv.Atoi(idString)
|
||||
const base = 10
|
||||
const bitSize = 64
|
||||
const max = uint64(^uint32(0))
|
||||
idUint64, err := strconv.ParseUint(idString, base, bitSize)
|
||||
if err != nil {
|
||||
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 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 uint16Ptr(uint16(idInt)), nil
|
||||
return uint32Ptr(uint32(idUint64)), nil
|
||||
}
|
||||
|
||||
@@ -14,15 +14,51 @@ func Test_Reader_readID(t *testing.T) {
|
||||
keyValue string
|
||||
retroKeyPrefix string
|
||||
retroValue string
|
||||
id *uint16
|
||||
id *uint32
|
||||
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: uint16Ptr(1000),
|
||||
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`,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
8
internal/constants/protocol.go
Normal file
8
internal/constants/protocol.go
Normal file
@@ -0,0 +1,8 @@
|
||||
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"
|
||||
)
|
||||
@@ -1,27 +0,0 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
53
internal/constants/providers/providers.go
Normal file
53
internal/constants/providers/providers.go
Normal file
@@ -0,0 +1,53 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
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"
|
||||
)
|
||||
6
internal/constants/vpn/protocol.go
Normal file
6
internal/constants/vpn/protocol.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package vpn
|
||||
|
||||
const (
|
||||
OpenVPN = "openvpn"
|
||||
Wireguard = "wireguard"
|
||||
)
|
||||
61
internal/firewall/cmd_matcher_test.go
Normal file
61
internal/firewall/cmd_matcher_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -96,13 +96,9 @@ func (c *Config) enable(ctx context.Context) (err error) {
|
||||
if err = c.acceptEstablishedRelatedTraffic(ctx, remove); 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
|
||||
}
|
||||
|
||||
if err = c.allowVPNIP(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, network := range c.localNetworks {
|
||||
@@ -111,10 +107,8 @@ func (c *Config) enable(ctx context.Context) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
for _, subnet := range c.outboundSubnets {
|
||||
if err := c.acceptOutputFromIPToSubnet(ctx, c.defaultInterface, c.localIP, subnet, remove); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = c.allowOutboundSubnets(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Allows packets from any IP address to go through eth0 / local network
|
||||
@@ -125,10 +119,8 @@ func (c *Config) enable(ctx context.Context) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
for port, intf := range c.allowedInputPorts {
|
||||
if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = c.allowInputPorts(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.runUserPostRules(ctx, c.customRulesPath, remove); err != nil {
|
||||
@@ -137,3 +129,47 @@ 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
|
||||
}
|
||||
|
||||
@@ -23,17 +23,16 @@ type Configurator interface {
|
||||
}
|
||||
|
||||
type Config struct { //nolint:maligned
|
||||
runner command.Runner
|
||||
logger Logger
|
||||
iptablesMutex sync.Mutex
|
||||
ip6tablesMutex sync.Mutex
|
||||
defaultInterface string
|
||||
defaultGateway net.IP
|
||||
localNetworks []routing.LocalNetwork
|
||||
localIP net.IP
|
||||
runner command.Runner
|
||||
logger Logger
|
||||
iptablesMutex sync.Mutex
|
||||
ip6tablesMutex sync.Mutex
|
||||
defaultRoutes []routing.DefaultRoute
|
||||
localNetworks []routing.LocalNetwork
|
||||
|
||||
// Fixed state
|
||||
ip6Tables bool
|
||||
ipTables string
|
||||
ip6Tables string
|
||||
customRulesPath string
|
||||
|
||||
// State
|
||||
@@ -41,24 +40,34 @@ type Config struct { //nolint:maligned
|
||||
vpnConnection models.Connection
|
||||
vpnIntf string
|
||||
outboundSubnets []net.IPNet
|
||||
allowedInputPorts map[uint16]string // port to interface mapping
|
||||
allowedInputPorts map[uint16]map[string]struct{} // port to interfaces set mapping
|
||||
stateMutex sync.Mutex
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// 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
|
||||
}
|
||||
|
||||
return &Config{
|
||||
runner: runner,
|
||||
logger: logger,
|
||||
allowedInputPorts: make(map[uint16]string),
|
||||
ip6Tables: ip6tablesSupported(context.Background(), runner),
|
||||
allowedInputPorts: make(map[uint16]map[string]struct{}),
|
||||
ipTables: iptables,
|
||||
ip6Tables: ip6tables,
|
||||
customRulesPath: "/iptables/post-rules.txt",
|
||||
// Obtained from routing
|
||||
defaultInterface: defaultInterface,
|
||||
defaultGateway: defaultGateway,
|
||||
localNetworks: localNetworks,
|
||||
localIP: localIP,
|
||||
}
|
||||
defaultRoutes: defaultRoutes,
|
||||
localNetworks: localNetworks,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -10,16 +10,18 @@ import (
|
||||
"github.com/qdm12/golibs/command"
|
||||
)
|
||||
|
||||
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
|
||||
// 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
|
||||
}
|
||||
return true
|
||||
return ip6tablesPath, nil
|
||||
}
|
||||
|
||||
func (c *Config) runIP6tablesInstructions(ctx context.Context, instructions []string) error {
|
||||
@@ -32,18 +34,19 @@ 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("ip6tables " + instruction)
|
||||
c.logger.Debug(c.ip6Tables + " " + instruction)
|
||||
|
||||
flags := strings.Fields(instruction)
|
||||
cmd := exec.CommandContext(ctx, "ip6tables", flags...)
|
||||
cmd := exec.CommandContext(ctx, c.ip6Tables, flags...) // #nosec G204
|
||||
if output, err := c.runner.Run(cmd); err != nil {
|
||||
return fmt.Errorf("command failed: \"ip6tables %s\": %s: %w", instruction, output, err)
|
||||
return fmt.Errorf("command failed: \"%s %s\": %s: %w",
|
||||
c.ip6Tables, instruction, output, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -71,12 +71,13 @@ 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("iptables " + instruction)
|
||||
c.logger.Debug(c.ipTables + " " + instruction)
|
||||
|
||||
flags := strings.Fields(instruction)
|
||||
cmd := exec.CommandContext(ctx, "iptables", flags...)
|
||||
cmd := exec.CommandContext(ctx, c.ipTables, flags...) // #nosec G204
|
||||
if output, err := c.runner.Run(cmd); err != nil {
|
||||
return fmt.Errorf("command failed: \"iptables %s\": %s: %w", instruction, output, err)
|
||||
return fmt.Errorf("command failed: \"%s %s\": %s: %w",
|
||||
c.ipTables, instruction, output, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -124,7 +125,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)
|
||||
@@ -151,7 +152,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)
|
||||
@@ -172,7 +173,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)
|
||||
@@ -223,9 +224,15 @@ 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
|
||||
}
|
||||
@@ -237,7 +244,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)
|
||||
|
||||
@@ -41,9 +41,13 @@ 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 {
|
||||
if err := c.acceptOutputFromIPToSubnet(ctx, c.defaultInterface, c.localIP, subNet, remove); err != nil {
|
||||
c.logger.Error("cannot remove outdated outbound subnet: " + err.Error())
|
||||
continue
|
||||
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
|
||||
}
|
||||
}
|
||||
c.outboundSubnets = subnet.RemoveSubnetFromSubnets(c.outboundSubnets, subNet)
|
||||
}
|
||||
@@ -52,8 +56,12 @@ 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 {
|
||||
if err := c.acceptOutputFromIPToSubnet(ctx, c.defaultInterface, c.localIP, subnet, remove); err != nil {
|
||||
return err
|
||||
for _, defaultRoute := range c.defaultRoutes {
|
||||
err := c.acceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
|
||||
defaultRoute.AssignedIP, subnet, remove)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
c.outboundSubnets = append(c.outboundSubnets, subnet)
|
||||
}
|
||||
|
||||
@@ -21,27 +21,30 @@ 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")
|
||||
c.allowedInputPorts[port] = intf
|
||||
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 {
|
||||
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: %w", port, err)
|
||||
return fmt.Errorf("cannot allow input to port %d through interface %s: %w",
|
||||
port, intf, err)
|
||||
}
|
||||
c.allowedInputPorts[port] = intf
|
||||
netInterfaces[intf] = struct{}{}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -60,17 +63,24 @@ 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)) + "...")
|
||||
|
||||
intf, ok := c.allowedInputPorts[port]
|
||||
interfacesSet, ok := c.allowedInputPorts[port]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
const remove = true
|
||||
if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil {
|
||||
return fmt.Errorf("cannot remove allowed port %d: %w", port, err)
|
||||
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)
|
||||
}
|
||||
|
||||
// All interfaces were removed successfully, so remove the port entry.
|
||||
delete(c.allowedInputPorts, port)
|
||||
|
||||
return nil
|
||||
|
||||
50
internal/firewall/runner_mock_test.go
Normal file
50
internal/firewall/runner_mock_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// 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)
|
||||
}
|
||||
159
internal/firewall/support.go
Normal file
159
internal/firewall/support.go
Normal file
@@ -0,0 +1,159 @@
|
||||
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)
|
||||
}
|
||||
250
internal/firewall/support_test.go
Normal file
250
internal/firewall/support_test.go
Normal file
@@ -0,0 +1,250 @@
|
||||
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)
|
||||
}
|
||||
@@ -31,8 +31,10 @@ func (c *Config) SetVPNConnection(ctx context.Context,
|
||||
|
||||
remove := true
|
||||
if c.vpnConnection.IP != nil {
|
||||
if err := c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, c.vpnConnection, remove); err != nil {
|
||||
c.logger.Error("cannot remove outdated VPN connection rule: " + err.Error())
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
c.vpnConnection = models.Connection{}
|
||||
@@ -46,8 +48,10 @@ func (c *Config) SetVPNConnection(ctx context.Context,
|
||||
|
||||
remove = false
|
||||
|
||||
if err := c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, connection, remove); err != nil {
|
||||
return fmt.Errorf("cannot allow output traffic through VPN connection: %w", err)
|
||||
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)
|
||||
}
|
||||
}
|
||||
c.vpnConnection = connection
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@ package healthcheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -14,7 +17,12 @@ func (s *Server) runHealthcheckLoop(ctx context.Context, done chan<- struct{}) {
|
||||
for {
|
||||
previousErr := s.handler.getErr()
|
||||
|
||||
err := healthCheck(ctx, s.pinger)
|
||||
const healthcheckTimeout = 3 * time.Second
|
||||
healthcheckCtx, healthcheckCancel := context.WithTimeout(
|
||||
ctx, healthcheckTimeout)
|
||||
err := s.healthCheck(healthcheckCtx)
|
||||
healthcheckCancel()
|
||||
|
||||
s.handler.setErr(err)
|
||||
|
||||
if previousErr != nil && err == nil {
|
||||
@@ -56,23 +64,40 @@ func (s *Server) runHealthcheckLoop(ctx context.Context, done chan<- struct{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func healthCheck(ctx context.Context, pinger Pinger) (err error) {
|
||||
func (s *Server) healthCheck(ctx context.Context) (err error) {
|
||||
// TODO use mullvad API if current provider is Mullvad
|
||||
// If we run without root, you need to run this on the gluetun binary:
|
||||
// setcap cap_net_raw=+ep /path/to/your/compiled/binary
|
||||
// Alternatively, we could have a separate binary just for healthcheck to
|
||||
// reduce attack surface.
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- pinger.Run()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
pinger.Stop()
|
||||
<-errCh
|
||||
return ctx.Err()
|
||||
case err = <-errCh:
|
||||
address, err := makeAddressToDial(s.config.TargetAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
const dialNetwork = "tcp4"
|
||||
connection, err := s.dialer.DialContext(ctx, dialNetwork, address)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot dial: %w", err)
|
||||
}
|
||||
|
||||
err = connection.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot close connection: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeAddressToDial(address string) (addressToDial string, err error) {
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
addrErr := new(net.AddrError)
|
||||
ok := errors.As(err, &addrErr)
|
||||
if !ok || addrErr.Err != "missing port in address" {
|
||||
return "", fmt.Errorf("cannot split host and port from address: %w", err)
|
||||
}
|
||||
host = address
|
||||
const defaultPort = "443"
|
||||
port = defaultPort
|
||||
}
|
||||
address = net.JoinHostPort(host, port)
|
||||
return address, nil
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
//go:build integration
|
||||
|
||||
package healthcheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_healthCheck_ping(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const timeout = time.Second
|
||||
|
||||
testCases := map[string]struct {
|
||||
address string
|
||||
err error
|
||||
}{
|
||||
"github.com": {
|
||||
address: "github.com",
|
||||
},
|
||||
"99.99.99.99": {
|
||||
address: "99.99.99.99",
|
||||
err: context.DeadlineExceeded,
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
pinger := newPinger(testCase.address)
|
||||
|
||||
err := healthCheck(ctx, pinger)
|
||||
|
||||
assert.ErrorIs(t, testCase.err, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,40 +2,90 @@ package healthcheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_healthCheck(t *testing.T) {
|
||||
func Test_Server_healthCheck(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
canceledCtx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
t.Run("canceled real dialer", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
someErr := errors.New("error")
|
||||
dialer := &net.Dialer{}
|
||||
const address = "cloudflare.com:443"
|
||||
|
||||
server := &Server{
|
||||
dialer: dialer,
|
||||
config: settings.Health{
|
||||
TargetAddress: address,
|
||||
},
|
||||
}
|
||||
|
||||
canceledCtx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
err := server.healthCheck(canceledCtx)
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "operation was canceled")
|
||||
})
|
||||
|
||||
t.Run("dial localhost:0", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
listener, err := net.Listen("tcp4", "localhost:0")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err = listener.Close()
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
listeningAddress := listener.Addr()
|
||||
|
||||
dialer := &net.Dialer{}
|
||||
server := &Server{
|
||||
dialer: dialer,
|
||||
config: settings.Health{
|
||||
TargetAddress: listeningAddress.String(),
|
||||
},
|
||||
}
|
||||
|
||||
const timeout = 100 * time.Millisecond
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
err = server.healthCheck(ctx)
|
||||
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_makeAddressToDial(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
ctx context.Context
|
||||
runErr error
|
||||
stopCall bool
|
||||
err error
|
||||
address string
|
||||
addressToDial string
|
||||
err error
|
||||
}{
|
||||
"success": {
|
||||
ctx: context.Background(),
|
||||
"host without port": {
|
||||
address: "test.com",
|
||||
addressToDial: "test.com:443",
|
||||
},
|
||||
"error": {
|
||||
ctx: context.Background(),
|
||||
runErr: someErr,
|
||||
err: someErr,
|
||||
"host with port": {
|
||||
address: "test.com:80",
|
||||
addressToDial: "test.com:80",
|
||||
},
|
||||
"context canceled": {
|
||||
ctx: canceledCtx,
|
||||
stopCall: true,
|
||||
err: context.Canceled,
|
||||
"bad address": {
|
||||
address: "test.com::",
|
||||
err: fmt.Errorf("cannot split host and port from address: address test.com::: too many colons in address"), //nolint:lll
|
||||
},
|
||||
}
|
||||
|
||||
@@ -43,54 +93,15 @@ func Test_healthCheck(t *testing.T) {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
stopped := make(chan struct{})
|
||||
addressToDial, err := makeAddressToDial(testCase.address)
|
||||
|
||||
pinger := NewMockPinger(ctrl)
|
||||
pinger.EXPECT().Run().DoAndReturn(func() error {
|
||||
if testCase.stopCall {
|
||||
<-stopped
|
||||
}
|
||||
return testCase.runErr
|
||||
})
|
||||
|
||||
if testCase.stopCall {
|
||||
pinger.EXPECT().Stop().DoAndReturn(func() {
|
||||
close(stopped)
|
||||
})
|
||||
assert.Equal(t, testCase.addressToDial, addressToDial)
|
||||
if testCase.err != nil {
|
||||
assert.EqualError(t, err, testCase.err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
err := healthCheck(testCase.ctx, pinger)
|
||||
|
||||
assert.ErrorIs(t, testCase.err, err)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("canceled real pinger", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pinger := newPinger("github.com")
|
||||
|
||||
canceledCtx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
err := healthCheck(canceledCtx, pinger)
|
||||
|
||||
assert.ErrorIs(t, context.Canceled, err)
|
||||
})
|
||||
|
||||
t.Run("ping 127.0.0.1", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pinger := newPinger("127.0.0.1")
|
||||
|
||||
const timeout = 100 * time.Millisecond
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
err := healthCheck(ctx, pinger)
|
||||
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package healthcheck
|
||||
|
||||
import "github.com/go-ping/ping"
|
||||
|
||||
//go:generate mockgen -destination=pinger_mock_test.go -package healthcheck . Pinger
|
||||
|
||||
type Pinger interface {
|
||||
Run() error
|
||||
Stop()
|
||||
}
|
||||
|
||||
func newPinger(addrToPing string) (pinger *ping.Pinger) {
|
||||
const count = 1
|
||||
pinger = ping.New(addrToPing)
|
||||
pinger.Count = count
|
||||
pinger.SetPrivileged(true) // see https://github.com/go-ping/ping#linux
|
||||
return pinger
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/qdm12/gluetun/internal/healthcheck (interfaces: Pinger)
|
||||
|
||||
// Package healthcheck is a generated GoMock package.
|
||||
package healthcheck
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockPinger is a mock of Pinger interface.
|
||||
type MockPinger struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockPingerMockRecorder
|
||||
}
|
||||
|
||||
// MockPingerMockRecorder is the mock recorder for MockPinger.
|
||||
type MockPingerMockRecorder struct {
|
||||
mock *MockPinger
|
||||
}
|
||||
|
||||
// NewMockPinger creates a new mock instance.
|
||||
func NewMockPinger(ctrl *gomock.Controller) *MockPinger {
|
||||
mock := &MockPinger{ctrl: ctrl}
|
||||
mock.recorder = &MockPingerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockPinger) EXPECT() *MockPingerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Run mocks base method.
|
||||
func (m *MockPinger) Run() error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Run")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Run indicates an expected call of Run.
|
||||
func (mr *MockPingerMockRecorder) Run() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockPinger)(nil).Run))
|
||||
}
|
||||
|
||||
// Stop mocks base method.
|
||||
func (m *MockPinger) Stop() {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Stop")
|
||||
}
|
||||
|
||||
// Stop indicates an expected call of Stop.
|
||||
func (mr *MockPingerMockRecorder) Stop() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockPinger)(nil).Stop))
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package healthcheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/vpn"
|
||||
@@ -16,7 +17,7 @@ type ServerRunner interface {
|
||||
type Server struct {
|
||||
logger Logger
|
||||
handler *handler
|
||||
pinger Pinger
|
||||
dialer *net.Dialer
|
||||
config settings.Health
|
||||
vpn vpnHealth
|
||||
}
|
||||
@@ -26,7 +27,7 @@ func NewServer(config settings.Health,
|
||||
return &Server{
|
||||
logger: logger,
|
||||
handler: newHandler(),
|
||||
pinger: newPinger(config.AddressToPing),
|
||||
dialer: &net.Dialer{},
|
||||
config: config,
|
||||
vpn: vpnHealth{
|
||||
looper: vpnLooper,
|
||||
|
||||
@@ -7,7 +7,6 @@ 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)
|
||||
|
||||
@@ -23,12 +23,11 @@ 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(s.name + " http server failed shutting down within " +
|
||||
s.logger.Error("http server failed shutting down within " +
|
||||
s.shutdownTimeout.String())
|
||||
}
|
||||
}()
|
||||
@@ -47,7 +46,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(s.name + " http server listening on " + s.address)
|
||||
s.logger.Info("http server listening on " + s.address)
|
||||
close(ready)
|
||||
|
||||
err = server.Serve(listener)
|
||||
|
||||
@@ -16,12 +16,10 @@ func Test_Server_Run_success(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
logger := NewMockLogger(ctrl)
|
||||
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")
|
||||
logger.EXPECT().Info(newRegexMatcher("^http server listening on 127.0.0.1:[1-9][0-9]{0,4}$"))
|
||||
const shutdownTimeout = 10 * time.Second
|
||||
|
||||
server := &Server{
|
||||
name: "test",
|
||||
address: "127.0.0.1:0",
|
||||
addressSet: make(chan struct{}),
|
||||
logger: logger,
|
||||
@@ -55,7 +53,6 @@ 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,
|
||||
|
||||
@@ -29,7 +29,6 @@ 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
|
||||
@@ -47,7 +46,6 @@ func New(settings Settings) (s *Server, err error) {
|
||||
}
|
||||
|
||||
return &Server{
|
||||
name: *settings.Name,
|
||||
address: settings.Address,
|
||||
addressSet: make(chan struct{}),
|
||||
handler: settings.Handler,
|
||||
|
||||
@@ -29,14 +29,12 @@ 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,
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
@@ -14,9 +13,6 @@ 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
|
||||
@@ -32,7 +28,6 @@ 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)
|
||||
@@ -40,7 +35,6 @@ 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,
|
||||
@@ -49,7 +43,6 @@ 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 {
|
||||
@@ -59,7 +52,6 @@ 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 {
|
||||
@@ -100,7 +92,7 @@ func (s Settings) Validate() (err error) {
|
||||
}
|
||||
|
||||
func (s Settings) ToLinesNode() (node *gotree.Node) {
|
||||
node = gotree.New("%s HTTP server settings:", strings.Title(*s.Name))
|
||||
node = gotree.New("HTTP server settings:")
|
||||
node.Appendf("Listening address: %s", s.Address)
|
||||
node.Appendf("Shutdown timeout: %s", *s.ShutdownTimeout)
|
||||
return node
|
||||
|
||||
@@ -21,19 +21,16 @@ 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),
|
||||
},
|
||||
@@ -65,14 +62,12 @@ 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,
|
||||
@@ -107,14 +102,12 @@ 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,
|
||||
@@ -123,14 +116,12 @@ 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,
|
||||
@@ -165,14 +156,12 @@ 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,
|
||||
@@ -181,14 +170,12 @@ 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,
|
||||
@@ -197,19 +184,16 @@ 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,
|
||||
@@ -307,11 +291,10 @@ func Test_Settings_String(t *testing.T) {
|
||||
}{
|
||||
"all values": {
|
||||
settings: Settings{
|
||||
Name: stringPtr("name"),
|
||||
Address: ":8000",
|
||||
ShutdownTimeout: durationPtr(time.Second),
|
||||
},
|
||||
s: `Name HTTP server settings:
|
||||
s: `HTTP server settings:
|
||||
├── Listening address: :8000
|
||||
└── Shutdown timeout: 1s`,
|
||||
},
|
||||
|
||||
@@ -28,246 +28,98 @@ func (a AllServers) GetCopy() (servers AllServers) {
|
||||
return servers
|
||||
}
|
||||
|
||||
func (a *AllServers) GetCyberghost() (servers []CyberghostServer) {
|
||||
if a.Cyberghost.Servers == nil {
|
||||
return nil
|
||||
}
|
||||
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
|
||||
func (a *AllServers) GetCyberghost() (servers []Server) {
|
||||
return copyServers(a.Cyberghost.Servers)
|
||||
}
|
||||
|
||||
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) GetExpressvpn() (servers []Server) {
|
||||
return copyServers(a.Expressvpn.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) GetFastestvpn() (servers []Server) {
|
||||
return copyServers(a.Fastestvpn.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) GetHideMyAss() (servers []Server) {
|
||||
return copyServers(a.HideMyAss.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) GetIpvanish() (servers []Server) {
|
||||
return copyServers(a.Ipvanish.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) GetIvpn() (servers []Server) {
|
||||
return copyServers(a.Ivpn.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) GetMullvad() (servers []Server) {
|
||||
return copyServers(a.Mullvad.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) GetNordvpn() (servers []Server) {
|
||||
return copyServers(a.Nordvpn.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) GetPerfectprivacy() (servers []Server) {
|
||||
return copyServers(a.Perfectprivacy.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) GetPia() (servers []Server) {
|
||||
return copyServers(a.Pia.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) GetPrivado() (servers []Server) {
|
||||
return copyServers(a.Privado.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) GetPrivatevpn() (servers []Server) {
|
||||
return copyServers(a.Privatevpn.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) GetProtonvpn() (servers []Server) {
|
||||
return copyServers(a.Protonvpn.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) GetPurevpn() (servers []Server) {
|
||||
return copyServers(a.Purevpn.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) GetSurfshark() (servers []Server) {
|
||||
return copyServers(a.Surfshark.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) GetTorguard() (servers []Server) {
|
||||
return copyServers(a.Torguard.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) GetVPNUnlimited() (servers []Server) {
|
||||
return copyServers(a.VPNUnlimited.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) GetVyprvpn() (servers []Server) {
|
||||
return copyServers(a.Vyprvpn.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) GetWevpn() (servers []Server) {
|
||||
return copyServers(a.Wevpn.Servers)
|
||||
}
|
||||
|
||||
func (a *AllServers) GetWindscribe() (servers []WindscribeServer) {
|
||||
if a.Windscribe.Servers == nil {
|
||||
func (a *AllServers) GetWindscribe() (servers []Server) {
|
||||
return copyServers(a.Windscribe.Servers)
|
||||
}
|
||||
|
||||
func copyServers(servers []Server) (serversCopy []Server) {
|
||||
if 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)
|
||||
|
||||
serversCopy = make([]Server, len(servers))
|
||||
for i, server := range servers {
|
||||
serversCopy[i] = server
|
||||
serversCopy[i].IPs = copyIPs(server.IPs)
|
||||
}
|
||||
return servers
|
||||
|
||||
return serversCopy
|
||||
}
|
||||
|
||||
func copyIPs(toCopy []net.IP) (copied []net.IP) {
|
||||
|
||||
@@ -10,101 +10,100 @@ import (
|
||||
|
||||
func Test_AllServers_GetCopy(t *testing.T) {
|
||||
allServers := AllServers{
|
||||
Cyberghost: CyberghostServers{
|
||||
Cyberghost: Servers{
|
||||
Version: 2,
|
||||
Servers: []CyberghostServer{{
|
||||
Servers: []Server{{
|
||||
IPs: []net.IP{{1, 2, 3, 4}},
|
||||
}},
|
||||
},
|
||||
Expressvpn: ExpressvpnServers{
|
||||
Servers: []ExpressvpnServer{{
|
||||
Expressvpn: Servers{
|
||||
Servers: []Server{{
|
||||
IPs: []net.IP{{1, 2, 3, 4}},
|
||||
}},
|
||||
},
|
||||
Fastestvpn: FastestvpnServers{
|
||||
Servers: []FastestvpnServer{{
|
||||
Fastestvpn: Servers{
|
||||
Servers: []Server{{
|
||||
IPs: []net.IP{{1, 2, 3, 4}},
|
||||
}},
|
||||
},
|
||||
HideMyAss: HideMyAssServers{
|
||||
Servers: []HideMyAssServer{{
|
||||
HideMyAss: Servers{
|
||||
Servers: []Server{{
|
||||
IPs: []net.IP{{1, 2, 3, 4}},
|
||||
}},
|
||||
},
|
||||
Ipvanish: IpvanishServers{
|
||||
Servers: []IpvanishServer{{
|
||||
Ipvanish: Servers{
|
||||
Servers: []Server{{
|
||||
IPs: []net.IP{{1, 2, 3, 4}},
|
||||
}},
|
||||
},
|
||||
Ivpn: IvpnServers{
|
||||
Servers: []IvpnServer{{
|
||||
Ivpn: Servers{
|
||||
Servers: []Server{{
|
||||
IPs: []net.IP{{1, 2, 3, 4}},
|
||||
}},
|
||||
},
|
||||
Mullvad: MullvadServers{
|
||||
Servers: []MullvadServer{{
|
||||
Mullvad: Servers{
|
||||
Servers: []Server{{
|
||||
IPs: []net.IP{{1, 2, 3, 4}},
|
||||
}},
|
||||
},
|
||||
Nordvpn: NordvpnServers{
|
||||
Servers: []NordvpnServer{{
|
||||
IP: net.IP{1, 2, 3, 4},
|
||||
}},
|
||||
},
|
||||
Perfectprivacy: PerfectprivacyServers{
|
||||
Servers: []PerfectprivacyServer{{
|
||||
Nordvpn: Servers{
|
||||
Servers: []Server{{
|
||||
IPs: []net.IP{{1, 2, 3, 4}},
|
||||
}},
|
||||
},
|
||||
Privado: PrivadoServers{
|
||||
Servers: []PrivadoServer{{
|
||||
IP: net.IP{1, 2, 3, 4},
|
||||
}},
|
||||
},
|
||||
Pia: PiaServers{
|
||||
Servers: []PIAServer{{
|
||||
Perfectprivacy: Servers{
|
||||
Servers: []Server{{
|
||||
IPs: []net.IP{{1, 2, 3, 4}},
|
||||
}},
|
||||
},
|
||||
Privatevpn: PrivatevpnServers{
|
||||
Servers: []PrivatevpnServer{{
|
||||
Privado: 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}},
|
||||
Pia: Servers{
|
||||
Servers: []Server{{
|
||||
IPs: []net.IP{{1, 2, 3, 4}},
|
||||
}},
|
||||
},
|
||||
Purevpn: PurevpnServers{
|
||||
Privatevpn: Servers{
|
||||
Servers: []Server{{
|
||||
IPs: []net.IP{{1, 2, 3, 4}},
|
||||
}},
|
||||
},
|
||||
Protonvpn: Servers{
|
||||
Servers: []Server{{
|
||||
IPs: []net.IP{{1, 2, 3, 4}},
|
||||
}},
|
||||
},
|
||||
Purevpn: Servers{
|
||||
Version: 1,
|
||||
Servers: []PurevpnServer{{
|
||||
Servers: []Server{{
|
||||
IPs: []net.IP{{1, 2, 3, 4}},
|
||||
}},
|
||||
},
|
||||
Surfshark: SurfsharkServers{
|
||||
Servers: []SurfsharkServer{{
|
||||
Surfshark: Servers{
|
||||
Servers: []Server{{
|
||||
IPs: []net.IP{{1, 2, 3, 4}},
|
||||
}},
|
||||
},
|
||||
Torguard: TorguardServers{
|
||||
Servers: []TorguardServer{{
|
||||
Torguard: Servers{
|
||||
Servers: []Server{{
|
||||
IPs: []net.IP{{1, 2, 3, 4}},
|
||||
}},
|
||||
},
|
||||
VPNUnlimited: VPNUnlimitedServers{
|
||||
Servers: []VPNUnlimitedServer{{
|
||||
VPNUnlimited: Servers{
|
||||
Servers: []Server{{
|
||||
IPs: []net.IP{{1, 2, 3, 4}},
|
||||
}},
|
||||
},
|
||||
Vyprvpn: VyprvpnServers{
|
||||
Servers: []VyprvpnServer{{
|
||||
Vyprvpn: Servers{
|
||||
Servers: []Server{{
|
||||
IPs: []net.IP{{1, 2, 3, 4}},
|
||||
}},
|
||||
},
|
||||
Windscribe: WindscribeServers{
|
||||
Servers: []WindscribeServer{{
|
||||
Windscribe: Servers{
|
||||
Servers: []Server{{
|
||||
IPs: []net.IP{{1, 2, 3, 4}},
|
||||
}},
|
||||
},
|
||||
@@ -117,8 +116,8 @@ func Test_AllServers_GetCopy(t *testing.T) {
|
||||
|
||||
func Test_AllServers_GetVyprvpn(t *testing.T) {
|
||||
allServers := AllServers{
|
||||
Vyprvpn: VyprvpnServers{
|
||||
Servers: []VyprvpnServer{
|
||||
Vyprvpn: Servers{
|
||||
Servers: []Server{
|
||||
{Hostname: "a", IPs: []net.IP{{1, 1, 1, 1}}},
|
||||
{Hostname: "b", IPs: []net.IP{{2, 2, 2, 2}}},
|
||||
},
|
||||
@@ -127,7 +126,7 @@ func Test_AllServers_GetVyprvpn(t *testing.T) {
|
||||
|
||||
servers := allServers.GetVyprvpn()
|
||||
|
||||
expectedServers := []VyprvpnServer{
|
||||
expectedServers := []Server{
|
||||
{Hostname: "a", IPs: []net.IP{{1, 1, 1, 1}}},
|
||||
{Hostname: "b", IPs: []net.IP{{2, 2, 2, 2}}},
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package models
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
)
|
||||
|
||||
func boolToMarkdown(b bool) string {
|
||||
@@ -14,280 +16,126 @@ func boolToMarkdown(b bool) string {
|
||||
|
||||
func markdownTableHeading(legendFields ...string) (markdown string) {
|
||||
return "| " + strings.Join(legendFields, " | ") + " |\n" +
|
||||
"|" + strings.Repeat(" --- |", len(legendFields)) + "\n"
|
||||
"|" + strings.Repeat(" --- |", len(legendFields))
|
||||
}
|
||||
|
||||
func (s *CyberghostServers) ToMarkdown() (markdown string) {
|
||||
markdown = markdownTableHeading("Country", "Hostname", "TCP", "UDP")
|
||||
for _, server := range s.Servers {
|
||||
markdown += server.ToMarkdown() + "\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 ""
|
||||
}
|
||||
|
||||
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 (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"
|
||||
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
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -3,43 +3,54 @@ package models
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_CyberghostServers_ToMarkdown(t *testing.T) {
|
||||
func Test_Servers_ToMarkdown(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
servers := CyberghostServers{
|
||||
Servers: []CyberghostServer{
|
||||
{Country: "a", UDP: true, Hostname: "xa"},
|
||||
{Country: "b", TCP: true, Hostname: "xb"},
|
||||
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",
|
||||
},
|
||||
}
|
||||
|
||||
markdown := servers.ToMarkdown()
|
||||
const expected = "| Country | Hostname | TCP | UDP |\n" +
|
||||
"| --- | --- | --- | --- |\n" +
|
||||
"| a | `xa` | ❌ | ✅ |\n" +
|
||||
"| b | `xb` | ✅ | ❌ |\n"
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.Equal(t, expected, markdown)
|
||||
}
|
||||
markdown := testCase.servers.ToMarkdown(testCase.provider)
|
||||
|
||||
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},
|
||||
},
|
||||
assert.Equal(t, testCase.expectedMarkdown, markdown)
|
||||
})
|
||||
}
|
||||
|
||||
markdown := servers.ToMarkdown()
|
||||
const expected = "| Country | Hostname | TCP | UDP |\n" +
|
||||
"| --- | --- | --- | --- |\n" +
|
||||
"| a | `xa` | ✅ | ❌ |\n" +
|
||||
"| b | `xb` | ❌ | ✅ |\n"
|
||||
|
||||
assert.Equal(t, expected, markdown)
|
||||
}
|
||||
|
||||
@@ -4,189 +4,25 @@ import (
|
||||
"net"
|
||||
)
|
||||
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
package models
|
||||
|
||||
type AllServers struct {
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
func (a *AllServers) Count() int {
|
||||
@@ -47,103 +47,8 @@ func (a *AllServers) Count() int {
|
||||
len(a.Windscribe.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"`
|
||||
type Servers struct {
|
||||
Version uint16 `json:"version"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Servers []Server `json:"servers"`
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user