Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4a260f148 | ||
|
|
614eb10d67 | ||
|
|
0bfd58a3f5 | ||
|
|
ff56857fc8 | ||
|
|
8d258feff7 | ||
|
|
96ee1bbfb2 | ||
|
|
abaf688ad8 | ||
|
|
bec8ff27ae | ||
|
|
7191d4e911 | ||
|
|
6f59bc3037 | ||
|
|
5c2286f4e8 | ||
|
|
9218c7ef19 | ||
|
|
3d8e61900b | ||
|
|
105d81c018 | ||
|
|
d4ca5cf257 | ||
|
|
05018ec971 | ||
|
|
538bc72c3c | ||
|
|
0027a76c49 | ||
|
|
a0cb6fabfd | ||
|
|
9e5400f52d | ||
|
|
7a1d0ff3ec | ||
|
|
d9fbecaa01 | ||
|
|
ecdf9396a5 | ||
|
|
df51aa40f4 | ||
|
|
996942af47 | ||
|
|
f17a4eae3e | ||
|
|
c515603d2f | ||
|
|
14c3b6429b | ||
|
|
bd110b960b | ||
|
|
3ad4319163 | ||
|
|
97340ec70b | ||
|
|
5140a7b010 | ||
|
|
bd74879303 | ||
|
|
da30ae287f | ||
|
|
6a545aa088 | ||
|
|
384a4bae3a | ||
|
|
e65f924cd7 | ||
|
|
9105b33e9f | ||
|
|
cc2235653a | ||
|
|
a00de75f61 | ||
|
|
836412b032 | ||
|
|
ba16270059 | ||
|
|
2c73672e64 | ||
|
|
74b7c81195 | ||
|
|
a021ff6b22 | ||
|
|
6d1a90cac0 | ||
|
|
1f47c16102 | ||
|
|
abbcf60aed | ||
|
|
f339c882d7 | ||
|
|
982536e9e8 | ||
|
|
c17b351efb | ||
|
|
130bebf2c6 | ||
|
|
83c4ad2e59 | ||
|
|
0bcc6ed597 | ||
|
|
c61f854edc | ||
|
|
2998cf5e48 |
@@ -1 +1,2 @@
|
||||
FROM qmcgaw/godevcontainer
|
||||
RUN apk add wireguard-tools
|
||||
|
||||
@@ -4,6 +4,8 @@ services:
|
||||
vscode:
|
||||
build: .
|
||||
image: godevcontainer
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
volumes:
|
||||
- ../:/workspace
|
||||
# Docker socket to access Docker server
|
||||
@@ -23,7 +25,8 @@ services:
|
||||
- TZ=
|
||||
cap_add:
|
||||
# For debugging with dlv
|
||||
- SYS_PTRACE
|
||||
# - SYS_PTRACE
|
||||
- NET_ADMIN
|
||||
security_opt:
|
||||
# For debugging with dlv
|
||||
- seccomp:unconfined
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/bug.md
vendored
4
.github/ISSUE_TEMPLATE/bug.md
vendored
@@ -15,6 +15,10 @@ assignees: qdm12
|
||||
|
||||
**Host OS** (approximate answer is fine too): Ubuntu 18
|
||||
|
||||
<!---
|
||||
🚧 If this is about the Unraid template see https://github.com/qdm12/gluetun/discussions/550
|
||||
-->
|
||||
|
||||
**CPU arch** or **device name**: amd64
|
||||
|
||||
**What VPN provider are you using**:
|
||||
|
||||
3
.github/labels.yml
vendored
3
.github/labels.yml
vendored
@@ -70,6 +70,9 @@
|
||||
- name: "Openvpn"
|
||||
color: "ffc7ea"
|
||||
description: ""
|
||||
- name: "Wireguard"
|
||||
color: "ffc7ea"
|
||||
description: ""
|
||||
- name: "Unbound (DNS over TLS)"
|
||||
color: "ffc7ea"
|
||||
description: ""
|
||||
|
||||
19
Dockerfile
19
Dockerfile
@@ -1,6 +1,6 @@
|
||||
ARG ALPINE_VERSION=3.14
|
||||
ARG GO_ALPINE_VERSION=3.13
|
||||
ARG GO_VERSION=1.16
|
||||
ARG GO_ALPINE_VERSION=3.14
|
||||
ARG GO_VERSION=1.17
|
||||
ARG XCPUTRANSLATE_VERSION=v0.6.0
|
||||
ARG GOLANGCI_LINT_VERSION=v1.41.1
|
||||
ARG BUILDPLATFORM=linux/amd64
|
||||
@@ -68,6 +68,7 @@ LABEL \
|
||||
org.opencontainers.image.description="VPN swiss-knife like client to tunnel to multiple VPN servers using OpenVPN, IPtables, DNS over TLS, Shadowsocks, an HTTP proxy and Alpine Linux"
|
||||
ENV VPNSP=pia \
|
||||
VERSION_INFORMATION=on \
|
||||
VPN_TYPE=openvpn \
|
||||
PROTOCOL=udp \
|
||||
OPENVPN_VERSION=2.5 \
|
||||
OPENVPN_VERBOSITY=1 \
|
||||
@@ -76,6 +77,12 @@ ENV VPNSP=pia \
|
||||
OPENVPN_TARGET_IP= \
|
||||
OPENVPN_IPV6=off \
|
||||
OPENVPN_CUSTOM_CONFIG= \
|
||||
OPENVPN_INTERFACE=tun0 \
|
||||
WIREGUARD_PRIVATE_KEY= \
|
||||
WIREGUARD_PRESHARED_KEY= \
|
||||
WIREGUARD_ADDRESS= \
|
||||
WIREGUARD_PORT= \
|
||||
WIREGUARD_INTERFACE=wg0 \
|
||||
TZ= \
|
||||
PUID= \
|
||||
PGID= \
|
||||
@@ -147,17 +154,17 @@ ENV VPNSP=pia \
|
||||
# Shadowsocks
|
||||
SHADOWSOCKS=off \
|
||||
SHADOWSOCKS_LOG=off \
|
||||
SHADOWSOCKS_PORT=8388 \
|
||||
SHADOWSOCKS_ADDRESS=":8388" \
|
||||
SHADOWSOCKS_PASSWORD= \
|
||||
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
|
||||
SHADOWSOCKS_METHOD=chacha20-ietf-poly1305 \
|
||||
SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305 \
|
||||
UPDATER_PERIOD=0
|
||||
ENTRYPOINT ["/entrypoint"]
|
||||
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
|
||||
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /entrypoint healthcheck
|
||||
ARG TARGETPLATFORM
|
||||
RUN apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.11-r0 && \
|
||||
if [ "${TARGETPLATFORM}" != "linux/ppc64le" ]; then apk add --no-cache --update apk-tools==2.12.6-r0; fi && \
|
||||
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 && \
|
||||
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,7 @@ HideMyAss, IPVanish, IVPN, Mullvad, NordVPN, Privado, Private Internet Access, P
|
||||
ProtonVPN, PureVPN, Surfshark, TorGuard, VPNUnlimited, VyprVPN and Windscribe VPN servers
|
||||
using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
|
||||
|
||||
**ANNOUNCEMENT**:
|
||||
**ANNOUNCEMENT**: Wireguard is now supported for all providers supporting it!
|
||||
|
||||

|
||||
|
||||
@@ -55,9 +55,10 @@ using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
|
||||
|
||||
## Features
|
||||
|
||||
- Based on Alpine 3.13 for a small Docker image of 54MB
|
||||
- Based on Alpine 3.14 for a small Docker image of 31MB
|
||||
- Supports: **Cyberghost**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **VPNUnlimited**, **Vyprvpn**, **Windscribe** servers
|
||||
- Supports Openvpn only for now
|
||||
- Supports OpenVPN
|
||||
- Supports Wireguard for **Mullvad** and **Windscribe** (more in progress, see #134)
|
||||
- DNS over TLS baked in with service provider(s) of your choice
|
||||
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
|
||||
- Choose the vpn network protocol, `udp` or `tcp`
|
||||
@@ -110,6 +111,7 @@ The following points are all optional but should give you insights on all the po
|
||||
|
||||
- [Test your setup](https://github.com/qdm12/gluetun/wiki/Test-your-setup)
|
||||
- [How to connect other containers and devices to Gluetun](https://github.com/qdm12/gluetun/wiki/Connect-to-gluetun)
|
||||
- [How to use Wireguard](https://github.com/qdm12/gluetun/wiki/Wireguard)
|
||||
- [VPN server side port forwarding](https://github.com/qdm12/gluetun/wiki/Port-forwarding)
|
||||
- [HTTP control server](https://github.com/qdm12/gluetun/wiki/HTTP-Control-server) to automate things, restart Openvpn etc.
|
||||
- Update the image with `docker pull qmcgaw/gluetun:latest`. See this [Wiki document](https://github.com/qdm12/gluetun/wiki/Docker-image-tags) for Docker tags available.
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -23,15 +22,17 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/healthcheck"
|
||||
"github.com/qdm12/gluetun/internal/httpproxy"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/netlink"
|
||||
"github.com/qdm12/gluetun/internal/openvpn"
|
||||
"github.com/qdm12/gluetun/internal/portforward"
|
||||
"github.com/qdm12/gluetun/internal/publicip"
|
||||
"github.com/qdm12/gluetun/internal/routing"
|
||||
"github.com/qdm12/gluetun/internal/server"
|
||||
"github.com/qdm12/gluetun/internal/shadowsocks"
|
||||
"github.com/qdm12/gluetun/internal/storage"
|
||||
"github.com/qdm12/gluetun/internal/unix"
|
||||
"github.com/qdm12/gluetun/internal/tun"
|
||||
"github.com/qdm12/gluetun/internal/updater"
|
||||
versionpkg "github.com/qdm12/gluetun/internal/version"
|
||||
"github.com/qdm12/gluetun/internal/vpn"
|
||||
"github.com/qdm12/golibs/command"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/golibs/params"
|
||||
@@ -59,29 +60,32 @@ func main() {
|
||||
Created: created,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
ctx, stop := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
background := context.Background()
|
||||
signalCtx, stop := signal.NotifyContext(background, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
|
||||
ctx, cancel := context.WithCancel(background)
|
||||
|
||||
logger := logging.NewParent(logging.Settings{
|
||||
logger := logging.New(logging.Settings{
|
||||
Level: logging.LevelInfo,
|
||||
})
|
||||
|
||||
args := os.Args
|
||||
unix := unix.New()
|
||||
tun := tun.New()
|
||||
netLinker := netlink.New()
|
||||
cli := cli.New()
|
||||
env := params.NewEnv()
|
||||
env := params.New()
|
||||
cmder := command.NewCmder()
|
||||
|
||||
errorCh := make(chan error)
|
||||
go func() {
|
||||
errorCh <- _main(ctx, buildInfo, args, logger, env, unix, cmder, cli)
|
||||
errorCh <- _main(ctx, buildInfo, args, logger, env, tun, netLinker, cmder, cli)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-signalCtx.Done():
|
||||
stop()
|
||||
fmt.Println("")
|
||||
logger.Warn("Caught OS signal, shutting down")
|
||||
cancel()
|
||||
case err := <-errorCh:
|
||||
stop()
|
||||
close(errorCh)
|
||||
@@ -113,8 +117,9 @@ var (
|
||||
|
||||
//nolint:gocognit,gocyclo
|
||||
func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
args []string, logger logging.ParentLogger, env params.Env,
|
||||
unix unix.Unix, cmder command.RunStarter, cli cli.CLIer) error {
|
||||
args []string, logger logging.ParentLogger, env params.Interface,
|
||||
tun tun.Interface, netLinker netlink.NetLinker, cmder command.RunStarter,
|
||||
cli cli.CLIer) error {
|
||||
if len(args) > 1 { // cli operation
|
||||
switch args[1] {
|
||||
case "healthcheck":
|
||||
@@ -122,7 +127,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
case "clientkey":
|
||||
return cli.ClientKey(args[2:])
|
||||
case "openvpnconfig":
|
||||
return cli.OpenvpnConfig(logger)
|
||||
return cli.OpenvpnConfig(logger, env)
|
||||
case "update":
|
||||
return cli.Update(ctx, args[2:], logger)
|
||||
default:
|
||||
@@ -130,19 +135,28 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
}
|
||||
}
|
||||
|
||||
var allSettings configuration.Settings
|
||||
err := allSettings.Read(env,
|
||||
logger.NewChild(logging.Settings{Prefix: "configuration: "}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
puid, pgid := allSettings.System.PUID, allSettings.System.PGID
|
||||
|
||||
const clientTimeout = 15 * time.Second
|
||||
httpClient := &http.Client{Timeout: clientTimeout}
|
||||
// Create configurators
|
||||
alpineConf := alpine.New()
|
||||
ovpnConf := openvpn.NewConfigurator(
|
||||
ovpnConf := openvpn.New(
|
||||
logger.NewChild(logging.Settings{Prefix: "openvpn configurator: "}),
|
||||
unix, cmder)
|
||||
cmder, puid, pgid)
|
||||
dnsCrypto := dnscrypto.New(httpClient, "", "")
|
||||
const cacertsPath = "/etc/ssl/certs/ca-certificates.crt"
|
||||
dnsConf := unbound.NewConfigurator(nil, cmder, dnsCrypto,
|
||||
"/etc/unbound", "/usr/sbin/unbound", cacertsPath)
|
||||
|
||||
announcementExp, err := time.Parse(time.RFC3339, "2021-07-22T00:00:00Z")
|
||||
announcementExp, err := time.Parse(time.RFC3339, "2021-10-02T00:00:00Z")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -153,7 +167,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
Version: buildInfo.Version,
|
||||
Commit: buildInfo.Commit,
|
||||
BuildDate: buildInfo.Created,
|
||||
Announcement: "",
|
||||
Announcement: "Wireguard is now supported!",
|
||||
AnnounceExp: announcementExp,
|
||||
// Sponsor information
|
||||
PaypalUser: "qmcgaw",
|
||||
@@ -176,12 +190,6 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
return err
|
||||
}
|
||||
|
||||
var allSettings configuration.Settings
|
||||
err = allSettings.Read(env,
|
||||
logger.NewChild(logging.Settings{Prefix: "configuration: "}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Info(allSettings.String())
|
||||
|
||||
if err := os.MkdirAll("/tmp/gluetun", 0644); err != nil {
|
||||
@@ -200,9 +208,6 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
return err
|
||||
}
|
||||
|
||||
// Should never change
|
||||
puid, pgid := allSettings.System.PUID, allSettings.System.PGID
|
||||
|
||||
const defaultUsername = "nonrootuser"
|
||||
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
|
||||
if err != nil {
|
||||
@@ -214,6 +219,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
// set it for Unbound
|
||||
// TODO remove this when migrating to qdm12/dns v2
|
||||
allSettings.DNS.Unbound.Username = nonRootUsername
|
||||
allSettings.VPN.OpenVPN.ProcUser = nonRootUsername
|
||||
|
||||
if err := os.Chown("/etc/unbound", puid, pgid); err != nil {
|
||||
return err
|
||||
@@ -271,17 +277,14 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ovpnConf.CheckTUN(); err != nil {
|
||||
logger.Warn(err.Error())
|
||||
err = ovpnConf.CreateTUN()
|
||||
if err := tun.Check(constants.TunnelDevice); err != nil {
|
||||
logger.Info(err.Error() + "; creating it...")
|
||||
err = tun.Create(constants.TunnelDevice)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
tunnelReadyCh := make(chan struct{})
|
||||
defer close(tunnelReadyCh)
|
||||
|
||||
if allSettings.Firewall.Enabled {
|
||||
err := firewallConf.SetEnabled(ctx, true) // disabled by default
|
||||
if err != nil {
|
||||
@@ -290,7 +293,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
}
|
||||
|
||||
for _, vpnPort := range allSettings.Firewall.VPNInputPorts {
|
||||
err = firewallConf.SetAllowedPort(ctx, vpnPort, string(constants.TUN))
|
||||
err = firewallConf.SetAllowedPort(ctx, vpnPort, allSettings.VPN.OpenVPN.Interface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -321,21 +324,12 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupSettings)
|
||||
otherGroupHandler := goshutdown.NewGroupHandler("other", defaultGroupSettings)
|
||||
|
||||
openvpnLooper := openvpn.NewLoop(allSettings.OpenVPN, nonRootUsername, puid, pgid, allServers,
|
||||
ovpnConf, firewallConf, logger, httpClient, tunnelReadyCh)
|
||||
openvpnHandler, openvpnCtx, openvpnDone := goshutdown.NewGoRoutineHandler(
|
||||
"openvpn", goshutdown.GoRoutineSettings{Timeout: time.Second})
|
||||
// wait for restartOpenvpn
|
||||
go openvpnLooper.Run(openvpnCtx, openvpnDone)
|
||||
|
||||
updaterLooper := updater.NewLooper(allSettings.Updater,
|
||||
allServers, storage, openvpnLooper.SetServers, httpClient,
|
||||
logger.NewChild(logging.Settings{Prefix: "updater: "}))
|
||||
updaterHandler, updaterCtx, updaterDone := goshutdown.NewGoRoutineHandler(
|
||||
"updater", defaultGoRoutineSettings)
|
||||
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
|
||||
go updaterLooper.Run(updaterCtx, updaterDone)
|
||||
tickersGroupHandler.Add(updaterHandler)
|
||||
portForwardLogger := logger.NewChild(logging.Settings{Prefix: "port forwarding: "})
|
||||
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
|
||||
httpClient, firewallConf, portForwardLogger)
|
||||
portForwardHandler, portForwardCtx, portForwardDone := goshutdown.NewGoRoutineHandler(
|
||||
"port forwarding", goshutdown.GoRoutineSettings{Timeout: time.Second})
|
||||
go portForwardLooper.Run(portForwardCtx, portForwardDone)
|
||||
|
||||
unboundLogger := logger.NewChild(logging.Settings{Prefix: "dns over tls: "})
|
||||
unboundLooper := dns.NewLoop(dnsConf, allSettings.DNS, httpClient,
|
||||
@@ -346,6 +340,11 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
go unboundLooper.Run(dnsCtx, dnsDone)
|
||||
otherGroupHandler.Add(dnsHandler)
|
||||
|
||||
dnsTickerHandler, dnsTickerCtx, dnsTickerDone := goshutdown.NewGoRoutineHandler(
|
||||
"dns ticker", defaultGoRoutineSettings)
|
||||
go unboundLooper.RunRestartTicker(dnsTickerCtx, dnsTickerDone)
|
||||
controlGroupHandler.Add(dnsTickerHandler)
|
||||
|
||||
publicIPLooper := publicip.NewLoop(httpClient,
|
||||
logger.NewChild(logging.Settings{Prefix: "ip getter: "}),
|
||||
allSettings.PublicIP, puid, pgid)
|
||||
@@ -359,6 +358,29 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone)
|
||||
tickersGroupHandler.Add(pubIPTickerHandler)
|
||||
|
||||
vpnLogger := logger.NewChild(logging.Settings{Prefix: "vpn: "})
|
||||
vpnLooper := vpn.NewLoop(allSettings.VPN,
|
||||
allServers, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
|
||||
cmder, publicIPLooper, unboundLooper, vpnLogger, httpClient,
|
||||
buildInfo, allSettings.VersionInformation)
|
||||
vpnHandler, vpnCtx, vpnDone := goshutdown.NewGoRoutineHandler(
|
||||
"vpn", goshutdown.GoRoutineSettings{Timeout: time.Second})
|
||||
go vpnLooper.Run(vpnCtx, vpnDone)
|
||||
|
||||
updaterLooper := updater.NewLooper(allSettings.Updater,
|
||||
allServers, storage, vpnLooper.SetServers, httpClient,
|
||||
logger.NewChild(logging.Settings{Prefix: "updater: "}))
|
||||
updaterHandler, updaterCtx, updaterDone := goshutdown.NewGoRoutineHandler(
|
||||
"updater", defaultGoRoutineSettings)
|
||||
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
|
||||
go updaterLooper.Run(updaterCtx, updaterDone)
|
||||
tickersGroupHandler.Add(updaterHandler)
|
||||
|
||||
updaterTickerHandler, updaterTickerCtx, updaterTickerDone := goshutdown.NewGoRoutineHandler(
|
||||
"updater ticker", defaultGoRoutineSettings)
|
||||
go updaterLooper.RunRestartTicker(updaterTickerCtx, updaterTickerDone)
|
||||
controlGroupHandler.Add(updaterTickerHandler)
|
||||
|
||||
httpProxyLooper := httpproxy.NewLoop(
|
||||
logger.NewChild(logging.Settings{Prefix: "http proxy: "}),
|
||||
allSettings.HTTPProxy)
|
||||
@@ -374,26 +396,18 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
go shadowsocksLooper.Run(shadowsocksCtx, shadowsocksDone)
|
||||
otherGroupHandler.Add(shadowsocksHandler)
|
||||
|
||||
eventsRoutingHandler, eventsRoutingCtx, eventsRoutingDone := goshutdown.NewGoRoutineHandler(
|
||||
"events routing", defaultGoRoutineSettings)
|
||||
go routeReadyEvents(eventsRoutingCtx, eventsRoutingDone, buildInfo, tunnelReadyCh,
|
||||
unboundLooper, updaterLooper, publicIPLooper, routingConf, logger, httpClient,
|
||||
allSettings.VersionInformation, allSettings.OpenVPN.Provider.PortForwarding.Enabled, openvpnLooper.PortForward,
|
||||
)
|
||||
controlGroupHandler.Add(eventsRoutingHandler)
|
||||
|
||||
controlServerAddress := ":" + strconv.Itoa(int(allSettings.ControlServer.Port))
|
||||
controlServerLogging := allSettings.ControlServer.Log
|
||||
httpServerHandler, httpServerCtx, httpServerDone := goshutdown.NewGoRoutineHandler(
|
||||
"http server", defaultGoRoutineSettings)
|
||||
httpServer := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
|
||||
logger.NewChild(logging.Settings{Prefix: "http server: "}),
|
||||
buildInfo, openvpnLooper, unboundLooper, updaterLooper, publicIPLooper)
|
||||
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper)
|
||||
go httpServer.Run(httpServerCtx, httpServerDone)
|
||||
controlGroupHandler.Add(httpServerHandler)
|
||||
|
||||
healthLogger := logger.NewChild(logging.Settings{Prefix: "healthcheck: "})
|
||||
healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger, openvpnLooper)
|
||||
healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger, vpnLooper)
|
||||
healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler(
|
||||
"HTTP health server", defaultGoRoutineSettings)
|
||||
go healthcheckServer.Run(healthServerCtx, healthServerDone)
|
||||
@@ -406,21 +420,14 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
}
|
||||
orderHandler := goshutdown.NewOrder("gluetun", orderSettings)
|
||||
orderHandler.Append(controlGroupHandler, tickersGroupHandler, healthServerHandler,
|
||||
openvpnHandler, otherGroupHandler)
|
||||
vpnHandler, portForwardHandler, otherGroupHandler)
|
||||
|
||||
// Start openvpn for the first time in a blocking call
|
||||
// until openvpn is launched
|
||||
_, _ = openvpnLooper.ApplyStatus(ctx, constants.Running) // TODO option to disable with variable
|
||||
// Start VPN for the first time in a blocking call
|
||||
// until the VPN is launched
|
||||
_, _ = vpnLooper.ApplyStatus(ctx, constants.Running) // TODO option to disable with variable
|
||||
|
||||
<-ctx.Done()
|
||||
|
||||
if allSettings.OpenVPN.Provider.PortForwarding.Enabled {
|
||||
logger.Info("Clearing forwarded port status file " + allSettings.OpenVPN.Provider.PortForwarding.Filepath)
|
||||
if err := os.Remove(allSettings.OpenVPN.Provider.PortForwarding.Filepath); err != nil {
|
||||
logger.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return orderHandler.Shutdown(context.Background())
|
||||
}
|
||||
|
||||
@@ -445,73 +452,3 @@ func printVersions(ctx context.Context, logger logging.Logger,
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func routeReadyEvents(ctx context.Context, done chan<- struct{}, buildInfo models.BuildInformation,
|
||||
tunnelReadyCh <-chan struct{},
|
||||
unboundLooper dns.Looper, updaterLooper updater.Looper, publicIPLooper publicip.Looper,
|
||||
routing routing.VPNGetter, logger logging.Logger, httpClient *http.Client,
|
||||
versionInformation, portForwardingEnabled bool, startPortForward func(vpnGateway net.IP)) {
|
||||
defer close(done)
|
||||
|
||||
// for linters only
|
||||
var restartTickerContext context.Context
|
||||
var restartTickerCancel context.CancelFunc = func() {}
|
||||
|
||||
unboundTickerDone := make(chan struct{})
|
||||
close(unboundTickerDone)
|
||||
updaterTickerDone := make(chan struct{})
|
||||
close(updaterTickerDone)
|
||||
|
||||
first := true
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
restartTickerCancel() // for linters only
|
||||
<-unboundTickerDone
|
||||
<-updaterTickerDone
|
||||
return
|
||||
case <-tunnelReadyCh: // blocks until openvpn is connected
|
||||
vpnDestination, err := routing.VPNDestinationIP()
|
||||
if err != nil {
|
||||
logger.Warn(err.Error())
|
||||
} else {
|
||||
logger.Info("VPN routing IP address: " + vpnDestination.String())
|
||||
}
|
||||
|
||||
if unboundLooper.GetSettings().Enabled {
|
||||
_, _ = unboundLooper.ApplyStatus(ctx, constants.Running)
|
||||
}
|
||||
|
||||
restartTickerCancel() // stop previous restart tickers
|
||||
<-unboundTickerDone
|
||||
<-updaterTickerDone
|
||||
restartTickerContext, restartTickerCancel = context.WithCancel(ctx)
|
||||
|
||||
// Runs the Public IP getter job once
|
||||
_, _ = publicIPLooper.ApplyStatus(ctx, constants.Running)
|
||||
if versionInformation && first {
|
||||
first = false
|
||||
message, err := versionpkg.GetMessage(ctx, buildInfo, httpClient)
|
||||
if err != nil {
|
||||
logger.Error("cannot get version information: " + err.Error())
|
||||
} else {
|
||||
logger.Info(message)
|
||||
}
|
||||
}
|
||||
|
||||
unboundTickerDone = make(chan struct{})
|
||||
updaterTickerDone = make(chan struct{})
|
||||
go unboundLooper.RunRestartTicker(restartTickerContext, unboundTickerDone)
|
||||
go updaterLooper.RunRestartTicker(restartTickerContext, updaterTickerDone)
|
||||
if portForwardingEnabled {
|
||||
// vpnGateway required only for PIA
|
||||
vpnGateway, err := routing.VPNLocalGatewayIP()
|
||||
if err != nil {
|
||||
logger.Error("cannot get VPN local gateway IP: " + err.Error())
|
||||
}
|
||||
logger.Info("VPN gateway IP address: " + vpnGateway.String())
|
||||
startPortForward(vpnGateway)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,11 @@ services:
|
||||
volumes:
|
||||
- /yourpath:/gluetun
|
||||
environment:
|
||||
# More variables are available, see the readme table
|
||||
# More variables are available, see the Wiki table
|
||||
- OPENVPN_USER=
|
||||
- OPENVPN_PASSWORD=
|
||||
- VPNSP=private internet access
|
||||
- VPN_TYPE=openvpn
|
||||
# Timezone for accurate logs times
|
||||
- TZ=
|
||||
restart: always
|
||||
|
||||
28
go.mod
28
go.mod
@@ -1,18 +1,40 @@
|
||||
module github.com/qdm12/gluetun
|
||||
|
||||
go 1.16
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/fatih/color v1.12.0
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/qdm12/dns v1.11.0
|
||||
github.com/qdm12/golibs v0.0.0-20210723175634-a75ca7fd74c2
|
||||
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
|
||||
github.com/qdm12/goshutdown v0.1.0
|
||||
github.com/qdm12/gosplash v0.1.0
|
||||
github.com/qdm12/ss-server v0.2.0
|
||||
github.com/qdm12/ss-server v0.3.0
|
||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/vishvananda/netlink v1.1.0
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c
|
||||
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/google/go-cmp v0.5.5 // indirect
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
|
||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
github.com/mattn/go-isatty v0.0.12 // indirect
|
||||
github.com/mdlayher/genetlink v1.0.0 // indirect
|
||||
github.com/mdlayher/netlink v1.4.0 // indirect
|
||||
github.com/miekg/dns v1.1.40 // indirect
|
||||
github.com/mr-tron/base58 v1.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
|
||||
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 // indirect
|
||||
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
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
)
|
||||
|
||||
80
go.sum
80
go.sum
@@ -33,11 +33,28 @@ github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3K
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
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/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=
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b h1:c3NTyLNozICy8B4mlMXemD3z/gXgQzVXZS/HqT+i3do=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
@@ -51,8 +68,24 @@ github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43 h1:WgyLFv10Ov49JAQI/ZLUkCZ7VJS3r74hwFIGXJsgZlY=
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
|
||||
github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
|
||||
github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
|
||||
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
||||
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
||||
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
|
||||
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
|
||||
github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8=
|
||||
github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
|
||||
github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
|
||||
github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys=
|
||||
github.com/mdlayher/netlink v1.4.0 h1:n3ARR+Fm0dDv37dj5wSWZXDKcy+U0zwcXS3zKMnSiT0=
|
||||
github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8=
|
||||
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
|
||||
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
|
||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
||||
@@ -66,14 +99,15 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/qdm12/dns v1.11.0 h1:jpcD5DZXXQSQe5a263PL09ghukiIdptvXFOZvyKEm6Q=
|
||||
github.com/qdm12/dns v1.11.0/go.mod h1:FmQsNOUcrrZ4UFzWAiED56AKXeNgaX3ySbmPwEfNjjE=
|
||||
github.com/qdm12/golibs v0.0.0-20210603202746-e5494e9c2ebb/go.mod h1:15RBzkun0i8XB7ADIoLJWp9ITRgsz3LroEI2FiOXLRg=
|
||||
github.com/qdm12/golibs v0.0.0-20210723175634-a75ca7fd74c2 h1:FMeOhe/bGloI0T5Wb6QB7/rfOqgFeI//UF/N/f7PUCI=
|
||||
github.com/qdm12/golibs v0.0.0-20210723175634-a75ca7fd74c2/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
|
||||
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6 h1:bge5AL7cjHJMPz+5IOz5yF01q/l8No6+lIEBieA8gMg=
|
||||
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
|
||||
github.com/qdm12/goshutdown v0.1.0 h1:lmwnygdXtnr2pa6VqfR/bm8077/BnBef1+7CP96B7Sw=
|
||||
github.com/qdm12/goshutdown v0.1.0/go.mod h1:/LP3MWLqI+wGH/ijfaUG+RHzBbKXIiVKnrg5vXOCf6Q=
|
||||
github.com/qdm12/gosplash v0.1.0 h1:Sfl+zIjFZFP7b0iqf2l5UkmEY97XBnaKkH3FNY6Gf7g=
|
||||
github.com/qdm12/gosplash v0.1.0/go.mod h1:+A3fWW4/rUeDXhY3ieBzwghKdnIPFJgD8K3qQkenJlw=
|
||||
github.com/qdm12/ss-server v0.2.0 h1:+togLzeeLAJ68MD1JqOWvYi9rl9t/fx1Qh7wKzZhY1g=
|
||||
github.com/qdm12/ss-server v0.2.0/go.mod h1:+1bWO1EfWNvsGM5Cuep6vneChK2OHniqtAsED9Fh1y0=
|
||||
github.com/qdm12/ss-server v0.3.0 h1:BfKv4OU6dYb2KcDMYpTc7LIuO2jB73g3JCzy988GrLI=
|
||||
github.com/qdm12/ss-server v0.3.0/go.mod h1:ug+nWfuzKw/h5fxL1B6e9/OhkVuWJX4i2V1Pf0pJU1o=
|
||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g=
|
||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e/go.mod h1:UvJRGkZ9XL3/D7e7JiTTVLm1F3Cymd3/gFpD6frEpBo=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||
@@ -105,6 +139,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
@@ -112,38 +148,67 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
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-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
|
||||
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=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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-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=
|
||||
golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
@@ -152,7 +217,14 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210427022245-097af6e1351b/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19 h1:ab2jcw2W91Rz07eHAb8Lic7sFQKO0NhBftjv6m/gL/0=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c h1:ADNrRDI5NR23/TUCnEmlLZLt4u9DnZ2nwRkPrAcFvto=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c/go.mod h1:+1XihzyZUBJcSc5WO9SwNA7v26puQwOEDwanaxfNXPQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -13,10 +13,10 @@ import (
|
||||
)
|
||||
|
||||
type HealthChecker interface {
|
||||
HealthCheck(ctx context.Context, env params.Env, logger logging.Logger) error
|
||||
HealthCheck(ctx context.Context, env params.Interface, logger logging.Logger) error
|
||||
}
|
||||
|
||||
func (c *CLI) HealthCheck(ctx context.Context, env params.Env,
|
||||
func (c *CLI) HealthCheck(ctx context.Context, env params.Interface,
|
||||
logger logging.Logger) error {
|
||||
// Extract the health server port from the configuration.
|
||||
config := configuration.Health{}
|
||||
|
||||
@@ -14,12 +14,12 @@ import (
|
||||
)
|
||||
|
||||
type OpenvpnConfigMaker interface {
|
||||
OpenvpnConfig(logger logging.Logger) error
|
||||
OpenvpnConfig(logger logging.Logger, env params.Interface) error
|
||||
}
|
||||
|
||||
func (c *CLI) OpenvpnConfig(logger logging.Logger) error {
|
||||
func (c *CLI) OpenvpnConfig(logger logging.Logger, env params.Interface) error {
|
||||
var allSettings configuration.Settings
|
||||
err := allSettings.Read(params.NewEnv(), logger)
|
||||
err := allSettings.Read(env, logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -28,12 +28,12 @@ func (c *CLI) OpenvpnConfig(logger logging.Logger) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
providerConf := provider.New(allSettings.OpenVPN.Provider.Name, allServers, time.Now)
|
||||
connection, err := providerConf.GetOpenVPNConnection(allSettings.OpenVPN.Provider.ServerSelection)
|
||||
providerConf := provider.New(allSettings.VPN.Provider.Name, allServers, time.Now)
|
||||
connection, err := providerConf.GetConnection(allSettings.VPN.Provider.ServerSelection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lines := providerConf.BuildConf(connection, "nonroortuser", allSettings.OpenVPN)
|
||||
lines := providerConf.BuildConf(connection, allSettings.VPN.OpenVPN)
|
||||
fmt.Println(strings.Join(lines, "\n"))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,56 +4,18 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
func (settings *Provider) cyberghostLines() (lines []string) {
|
||||
lines = append(lines, lastIndent+"Server group: "+settings.ServerSelection.Group)
|
||||
|
||||
if len(settings.ServerSelection.Regions) > 0 {
|
||||
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Hostnames) > 0 {
|
||||
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
|
||||
}
|
||||
|
||||
if settings.ExtraConfigOptions.ClientKey != "" {
|
||||
lines = append(lines, lastIndent+"Client key is set")
|
||||
}
|
||||
|
||||
if settings.ExtraConfigOptions.ClientCertificate != "" {
|
||||
lines = append(lines, lastIndent+"Client certificate is set")
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readCyberghost(r reader) (err error) {
|
||||
settings.Name = constants.Cyberghost
|
||||
|
||||
settings.ServerSelection.TCP, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ExtraConfigOptions.ClientKey, err = readClientKey(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ExtraConfigOptions.ClientCertificate, err = readClientCertificate(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Group, err = r.env.Inside("CYBERGHOST_GROUP",
|
||||
constants.CyberghostGroupChoices(), params.Default("Premium UDP Europe"))
|
||||
settings.ServerSelection.Groups, err = r.env.CSVInside("CYBERGHOST_GROUP",
|
||||
constants.CyberghostGroupChoices())
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable CYBERGHOST_GROUP: %w", err)
|
||||
}
|
||||
@@ -68,5 +30,19 @@ func (settings *Provider) readCyberghost(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
||||
}
|
||||
|
||||
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r.env)
|
||||
}
|
||||
|
||||
func (settings *OpenVPN) readCyberghost(r reader) (err error) {
|
||||
settings.ClientKey, err = readClientKey(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ClientCrt, err = readClientCertificate(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ var (
|
||||
ErrDNSAddressNotAnIP = errors.New("DNS plaintext address is not an IP address")
|
||||
)
|
||||
|
||||
func (settings *DNS) readDNSPlaintext(env params.Env) error {
|
||||
func (settings *DNS) readDNSPlaintext(env params.Interface) error {
|
||||
s, err := env.Get("DNS_PLAINTEXT_ADDRESS", params.Default("1.1.1.1"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable DNS_PLAINTEXT_ADDRESS: %w", err)
|
||||
|
||||
@@ -36,7 +36,7 @@ var (
|
||||
ErrInvalidPrivateAddress = errors.New("private address is not a valid IP or CIDR range")
|
||||
)
|
||||
|
||||
func (settings *DNS) readPrivateAddresses(env params.Env) (err error) {
|
||||
func (settings *DNS) readPrivateAddresses(env params.Interface) (err error) {
|
||||
privateAddresses, err := env.CSV("DOT_PRIVATE_ADDRESS")
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable DOT_PRIVATE_ADDRESS: %w", err)
|
||||
|
||||
@@ -6,26 +6,9 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
func (settings *Provider) fastestvpnLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Hostnames) > 0 {
|
||||
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Countries) > 0 {
|
||||
lines = append(lines, lastIndent+"Countries: "+commaJoin(settings.ServerSelection.Countries))
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readFastestvpn(r reader) (err error) {
|
||||
settings.Name = constants.Fastestvpn
|
||||
|
||||
settings.ServerSelection.TCP, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -41,5 +24,5 @@ func (settings *Provider) readFastestvpn(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable COUNTRY: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return settings.ServerSelection.OpenVPN.readProtocolOnly(r.env)
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ func (settings *Firewall) read(r reader) (err error) {
|
||||
return settings.readOutboundSubnets(r)
|
||||
}
|
||||
|
||||
func (settings *Firewall) readVPNInputPorts(env params.Env) (err error) {
|
||||
func (settings *Firewall) readVPNInputPorts(env params.Interface) (err error) {
|
||||
settings.VPNInputPorts, err = readCSVPorts(env, "FIREWALL_VPN_INPUT_PORTS")
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable FIREWALL_VPN_INPUT_PORTS: %w", err)
|
||||
@@ -81,7 +81,7 @@ func (settings *Firewall) readVPNInputPorts(env params.Env) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (settings *Firewall) readInputPorts(env params.Env) (err error) {
|
||||
func (settings *Firewall) readInputPorts(env params.Interface) (err error) {
|
||||
settings.InputPorts, err = readCSVPorts(env, "FIREWALL_INPUT_PORTS")
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable FIREWALL_INPUT_PORTS: %w", err)
|
||||
|
||||
@@ -32,7 +32,7 @@ func (settings *Health) lines() (lines []string) {
|
||||
}
|
||||
|
||||
// Read is to be used for the healthcheck query mode.
|
||||
func (settings *Health) Read(env params.Env, logger logging.Logger) (err error) {
|
||||
func (settings *Health) Read(env params.Interface, logger logging.Logger) (err error) {
|
||||
reader := newReader(env, logger)
|
||||
return settings.read(reader)
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ func Test_Health_read(t *testing.T) {
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
env := mock_params.NewMockEnv(ctrl)
|
||||
env := mock_params.NewMockInterface(ctrl)
|
||||
logger := mock_logging.NewMockLogger(ctrl)
|
||||
|
||||
env.EXPECT().ListeningAddress("HEALTH_SERVER_ADDRESS", gomock.Any()).
|
||||
|
||||
@@ -6,34 +6,9 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
func (settings *Provider) hideMyAssLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Countries) > 0 {
|
||||
lines = append(lines, lastIndent+"Countries: "+commaJoin(settings.ServerSelection.Countries))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Regions) > 0 {
|
||||
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Cities) > 0 {
|
||||
lines = append(lines, lastIndent+"Cities: "+commaJoin(settings.ServerSelection.Cities))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Hostnames) > 0 {
|
||||
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readHideMyAss(r reader) (err error) {
|
||||
settings.Name = constants.HideMyAss
|
||||
|
||||
settings.ServerSelection.TCP, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -59,5 +34,5 @@ func (settings *Provider) readHideMyAss(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r.env)
|
||||
}
|
||||
|
||||
@@ -6,30 +6,9 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
func (settings *Provider) ipvanishLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Countries) > 0 {
|
||||
lines = append(lines, lastIndent+"Countries: "+commaJoin(settings.ServerSelection.Countries))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Cities) > 0 {
|
||||
lines = append(lines, lastIndent+"Cities: "+commaJoin(settings.ServerSelection.Cities))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Hostnames) > 0 {
|
||||
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readIpvanish(r reader) (err error) {
|
||||
settings.Name = constants.Ipvanish
|
||||
|
||||
settings.ServerSelection.TCP, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -50,5 +29,5 @@ func (settings *Provider) readIpvanish(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return settings.ServerSelection.OpenVPN.readProtocolOnly(r.env)
|
||||
}
|
||||
|
||||
@@ -12,41 +12,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_Provider_ipvanishLines(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
settings Provider
|
||||
lines []string
|
||||
}{
|
||||
"empty settings": {},
|
||||
"full settings": {
|
||||
settings: Provider{
|
||||
ServerSelection: ServerSelection{
|
||||
Countries: []string{"A", "B"},
|
||||
Cities: []string{"C", "D"},
|
||||
Hostnames: []string{"E", "F"},
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Countries: A, B",
|
||||
"|--Cities: C, D",
|
||||
"|--Hostnames: E, F",
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
lines := testCase.settings.ipvanishLines()
|
||||
|
||||
assert.Equal(t, testCase.lines, lines)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Provider_readIpvanish(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -65,23 +30,15 @@ func Test_Provider_readIpvanish(t *testing.T) {
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
protocol singleStringCall
|
||||
targetIP singleStringCall
|
||||
countries sliceStringCall
|
||||
cities sliceStringCall
|
||||
hostnames sliceStringCall
|
||||
protocol singleStringCall
|
||||
settings Provider
|
||||
err error
|
||||
}{
|
||||
"protocol error": {
|
||||
protocol: singleStringCall{call: true, err: errDummy},
|
||||
settings: Provider{
|
||||
Name: constants.Ipvanish,
|
||||
},
|
||||
err: errors.New("environment variable PROTOCOL: dummy test error"),
|
||||
},
|
||||
"target IP error": {
|
||||
protocol: singleStringCall{call: true},
|
||||
targetIP: singleStringCall{call: true, value: "something", err: errDummy},
|
||||
settings: Provider{
|
||||
Name: constants.Ipvanish,
|
||||
@@ -89,7 +46,6 @@ func Test_Provider_readIpvanish(t *testing.T) {
|
||||
err: errors.New("environment variable OPENVPN_TARGET_IP: dummy test error"),
|
||||
},
|
||||
"countries error": {
|
||||
protocol: singleStringCall{call: true},
|
||||
targetIP: singleStringCall{call: true},
|
||||
countries: sliceStringCall{call: true, err: errDummy},
|
||||
settings: Provider{
|
||||
@@ -98,7 +54,6 @@ func Test_Provider_readIpvanish(t *testing.T) {
|
||||
err: errors.New("environment variable COUNTRY: dummy test error"),
|
||||
},
|
||||
"cities error": {
|
||||
protocol: singleStringCall{call: true},
|
||||
targetIP: singleStringCall{call: true},
|
||||
countries: sliceStringCall{call: true},
|
||||
cities: sliceStringCall{call: true, err: errDummy},
|
||||
@@ -108,7 +63,6 @@ func Test_Provider_readIpvanish(t *testing.T) {
|
||||
err: errors.New("environment variable CITY: dummy test error"),
|
||||
},
|
||||
"hostnames error": {
|
||||
protocol: singleStringCall{call: true},
|
||||
targetIP: singleStringCall{call: true},
|
||||
countries: sliceStringCall{call: true},
|
||||
cities: sliceStringCall{call: true},
|
||||
@@ -118,26 +72,39 @@ func Test_Provider_readIpvanish(t *testing.T) {
|
||||
},
|
||||
err: errors.New("environment variable SERVER_HOSTNAME: dummy test error"),
|
||||
},
|
||||
"default settings": {
|
||||
protocol: singleStringCall{call: true},
|
||||
"protocol error": {
|
||||
targetIP: singleStringCall{call: true},
|
||||
countries: sliceStringCall{call: true},
|
||||
cities: sliceStringCall{call: true},
|
||||
hostnames: sliceStringCall{call: true},
|
||||
protocol: singleStringCall{call: true, err: errDummy},
|
||||
settings: Provider{
|
||||
Name: constants.Ipvanish,
|
||||
},
|
||||
err: errors.New("environment variable PROTOCOL: dummy test error"),
|
||||
},
|
||||
"default settings": {
|
||||
targetIP: singleStringCall{call: true},
|
||||
countries: sliceStringCall{call: true},
|
||||
cities: sliceStringCall{call: true},
|
||||
hostnames: sliceStringCall{call: true},
|
||||
protocol: singleStringCall{call: true},
|
||||
settings: Provider{
|
||||
Name: constants.Ipvanish,
|
||||
},
|
||||
},
|
||||
"set settings": {
|
||||
protocol: singleStringCall{call: true, value: constants.TCP},
|
||||
targetIP: singleStringCall{call: true, value: "1.2.3.4"},
|
||||
countries: sliceStringCall{call: true, values: []string{"A", "B"}},
|
||||
cities: sliceStringCall{call: true, values: []string{"C", "D"}},
|
||||
hostnames: sliceStringCall{call: true, values: []string{"E", "F"}},
|
||||
protocol: singleStringCall{call: true, value: constants.TCP},
|
||||
settings: Provider{
|
||||
Name: constants.Ipvanish,
|
||||
ServerSelection: ServerSelection{
|
||||
OpenVPN: OpenVPNSelection{
|
||||
TCP: true,
|
||||
},
|
||||
TargetIP: net.IPv4(1, 2, 3, 4),
|
||||
Countries: []string{"A", "B"},
|
||||
Cities: []string{"C", "D"},
|
||||
@@ -152,11 +119,7 @@ func Test_Provider_readIpvanish(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
env := mock_params.NewMockEnv(ctrl)
|
||||
if testCase.protocol.call {
|
||||
env.EXPECT().Inside("PROTOCOL", []string{constants.TCP, constants.UDP}, gomock.Any()).
|
||||
Return(testCase.protocol.value, testCase.protocol.err)
|
||||
}
|
||||
env := mock_params.NewMockInterface(ctrl)
|
||||
if testCase.targetIP.call {
|
||||
env.EXPECT().Get("OPENVPN_TARGET_IP").
|
||||
Return(testCase.targetIP.value, testCase.targetIP.err)
|
||||
@@ -173,6 +136,10 @@ func Test_Provider_readIpvanish(t *testing.T) {
|
||||
env.EXPECT().CSVInside("SERVER_HOSTNAME", constants.IpvanishHostnameChoices()).
|
||||
Return(testCase.hostnames.values, testCase.hostnames.err)
|
||||
}
|
||||
if testCase.protocol.call {
|
||||
env.EXPECT().Inside("PROTOCOL", []string{constants.TCP, constants.UDP}, gomock.Any()).
|
||||
Return(testCase.protocol.value, testCase.protocol.err)
|
||||
}
|
||||
|
||||
r := reader{env: env}
|
||||
|
||||
|
||||
@@ -6,30 +6,9 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
func (settings *Provider) ivpnLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Countries) > 0 {
|
||||
lines = append(lines, lastIndent+"Countries: "+commaJoin(settings.ServerSelection.Countries))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Cities) > 0 {
|
||||
lines = append(lines, lastIndent+"Cities: "+commaJoin(settings.ServerSelection.Cities))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Hostnames) > 0 {
|
||||
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readIvpn(r reader) (err error) {
|
||||
settings.Name = constants.Ivpn
|
||||
|
||||
settings.ServerSelection.TCP, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -50,5 +29,5 @@ func (settings *Provider) readIvpn(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return settings.ServerSelection.OpenVPN.readProtocolOnly(r.env)
|
||||
}
|
||||
|
||||
@@ -12,41 +12,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_Provider_ivpnLines(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
settings Provider
|
||||
lines []string
|
||||
}{
|
||||
"empty settings": {},
|
||||
"full settings": {
|
||||
settings: Provider{
|
||||
ServerSelection: ServerSelection{
|
||||
Countries: []string{"A", "B"},
|
||||
Cities: []string{"C", "D"},
|
||||
Hostnames: []string{"E", "F"},
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Countries: A, B",
|
||||
"|--Cities: C, D",
|
||||
"|--Hostnames: E, F",
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
lines := testCase.settings.ivpnLines()
|
||||
|
||||
assert.Equal(t, testCase.lines, lines)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Provider_readIvpn(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -73,15 +38,7 @@ func Test_Provider_readIvpn(t *testing.T) {
|
||||
settings Provider
|
||||
err error
|
||||
}{
|
||||
"protocol error": {
|
||||
protocol: singleStringCall{call: true, err: errDummy},
|
||||
settings: Provider{
|
||||
Name: constants.Ivpn,
|
||||
},
|
||||
err: errors.New("environment variable PROTOCOL: dummy test error"),
|
||||
},
|
||||
"target IP error": {
|
||||
protocol: singleStringCall{call: true},
|
||||
targetIP: singleStringCall{call: true, value: "something", err: errDummy},
|
||||
settings: Provider{
|
||||
Name: constants.Ivpn,
|
||||
@@ -89,7 +46,6 @@ func Test_Provider_readIvpn(t *testing.T) {
|
||||
err: errors.New("environment variable OPENVPN_TARGET_IP: dummy test error"),
|
||||
},
|
||||
"countries error": {
|
||||
protocol: singleStringCall{call: true},
|
||||
targetIP: singleStringCall{call: true},
|
||||
countries: sliceStringCall{call: true, err: errDummy},
|
||||
settings: Provider{
|
||||
@@ -98,7 +54,6 @@ func Test_Provider_readIvpn(t *testing.T) {
|
||||
err: errors.New("environment variable COUNTRY: dummy test error"),
|
||||
},
|
||||
"cities error": {
|
||||
protocol: singleStringCall{call: true},
|
||||
targetIP: singleStringCall{call: true},
|
||||
countries: sliceStringCall{call: true},
|
||||
cities: sliceStringCall{call: true, err: errDummy},
|
||||
@@ -108,7 +63,6 @@ func Test_Provider_readIvpn(t *testing.T) {
|
||||
err: errors.New("environment variable CITY: dummy test error"),
|
||||
},
|
||||
"hostnames error": {
|
||||
protocol: singleStringCall{call: true},
|
||||
targetIP: singleStringCall{call: true},
|
||||
countries: sliceStringCall{call: true},
|
||||
cities: sliceStringCall{call: true},
|
||||
@@ -118,26 +72,39 @@ func Test_Provider_readIvpn(t *testing.T) {
|
||||
},
|
||||
err: errors.New("environment variable SERVER_HOSTNAME: dummy test error"),
|
||||
},
|
||||
"default settings": {
|
||||
protocol: singleStringCall{call: true},
|
||||
"protocol error": {
|
||||
targetIP: singleStringCall{call: true},
|
||||
countries: sliceStringCall{call: true},
|
||||
cities: sliceStringCall{call: true},
|
||||
hostnames: sliceStringCall{call: true},
|
||||
protocol: singleStringCall{call: true, err: errDummy},
|
||||
settings: Provider{
|
||||
Name: constants.Ivpn,
|
||||
},
|
||||
err: errors.New("environment variable PROTOCOL: dummy test error"),
|
||||
},
|
||||
"default settings": {
|
||||
targetIP: singleStringCall{call: true},
|
||||
countries: sliceStringCall{call: true},
|
||||
cities: sliceStringCall{call: true},
|
||||
hostnames: sliceStringCall{call: true},
|
||||
protocol: singleStringCall{call: true},
|
||||
settings: Provider{
|
||||
Name: constants.Ivpn,
|
||||
},
|
||||
},
|
||||
"set settings": {
|
||||
protocol: singleStringCall{call: true, value: constants.TCP},
|
||||
targetIP: singleStringCall{call: true, value: "1.2.3.4"},
|
||||
countries: sliceStringCall{call: true, values: []string{"A", "B"}},
|
||||
cities: sliceStringCall{call: true, values: []string{"C", "D"}},
|
||||
hostnames: sliceStringCall{call: true, values: []string{"E", "F"}},
|
||||
protocol: singleStringCall{call: true, value: constants.TCP},
|
||||
settings: Provider{
|
||||
Name: constants.Ivpn,
|
||||
ServerSelection: ServerSelection{
|
||||
OpenVPN: OpenVPNSelection{
|
||||
TCP: true,
|
||||
},
|
||||
TargetIP: net.IPv4(1, 2, 3, 4),
|
||||
Countries: []string{"A", "B"},
|
||||
Cities: []string{"C", "D"},
|
||||
@@ -152,7 +119,7 @@ func Test_Provider_readIvpn(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
env := mock_params.NewMockEnv(ctrl)
|
||||
env := mock_params.NewMockInterface(ctrl)
|
||||
if testCase.protocol.call {
|
||||
env.EXPECT().Inside("PROTOCOL", []string{constants.TCP, constants.UDP}, gomock.Any()).
|
||||
Return(testCase.protocol.value, testCase.protocol.err)
|
||||
|
||||
@@ -2,48 +2,14 @@ package configuration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
func (settings *Provider) mullvadLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Countries) > 0 {
|
||||
lines = append(lines, lastIndent+"Countries: "+commaJoin(settings.ServerSelection.Countries))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Cities) > 0 {
|
||||
lines = append(lines, lastIndent+"Cities: "+commaJoin(settings.ServerSelection.Cities))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Hostnames) > 0 {
|
||||
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.ISPs) > 0 {
|
||||
lines = append(lines, lastIndent+"ISPs: "+commaJoin(settings.ServerSelection.ISPs))
|
||||
}
|
||||
|
||||
if settings.ServerSelection.CustomPort > 0 {
|
||||
lines = append(lines, lastIndent+"Custom port: "+strconv.Itoa(int(settings.ServerSelection.CustomPort)))
|
||||
}
|
||||
|
||||
if settings.ExtraConfigOptions.OpenVPNIPv6 {
|
||||
lines = append(lines, lastIndent+"IPv6: enabled")
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readMullvad(r reader) (err error) {
|
||||
settings.Name = constants.Mullvad
|
||||
|
||||
settings.ServerSelection.TCP, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -69,20 +35,24 @@ func (settings *Provider) readMullvad(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable ISP: %w", err)
|
||||
}
|
||||
|
||||
settings.ServerSelection.CustomPort, err = readCustomPort(r.env, settings.ServerSelection.TCP,
|
||||
[]uint16{80, 443, 1401}, []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Owned, err = r.env.YesNo("OWNED", params.Default("no"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable OWNED: %w", err)
|
||||
}
|
||||
|
||||
settings.ExtraConfigOptions.OpenVPNIPv6, err = r.env.OnOff("OPENVPN_IPV6", params.Default("off"))
|
||||
return settings.ServerSelection.OpenVPN.readMullvad(r.env)
|
||||
}
|
||||
|
||||
func (settings *OpenVPNSelection) readMullvad(env params.Interface) (err error) {
|
||||
settings.TCP, err = readProtocol(env)
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable OPENVPN_IPV6: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
settings.CustomPort, err = readOpenVPNCustomPort(env, settings.TCP,
|
||||
[]uint16{80, 443, 1401}, []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -8,38 +8,9 @@ import (
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
func (settings *Provider) nordvpnLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Regions) > 0 {
|
||||
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Hostnames) > 0 {
|
||||
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Names) > 0 {
|
||||
lines = append(lines, lastIndent+"Names: "+commaJoin(settings.ServerSelection.Hostnames))
|
||||
}
|
||||
|
||||
if numbersUint16 := settings.ServerSelection.Numbers; len(numbersUint16) > 0 {
|
||||
numbersString := make([]string, len(numbersUint16))
|
||||
for i, numberUint16 := range numbersUint16 {
|
||||
numbersString[i] = strconv.Itoa(int(numberUint16))
|
||||
}
|
||||
lines = append(lines, lastIndent+"Numbers: "+commaJoin(numbersString))
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readNordvpn(r reader) (err error) {
|
||||
settings.Name = constants.Nordvpn
|
||||
|
||||
settings.ServerSelection.TCP, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -65,10 +36,10 @@ func (settings *Provider) readNordvpn(r reader) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return settings.ServerSelection.OpenVPN.readProtocolOnly(r.env)
|
||||
}
|
||||
|
||||
func readNordVPNServerNumbers(env params.Env) (numbers []uint16, err error) {
|
||||
func readNordVPNServerNumbers(env params.Interface) (numbers []uint16, err error) {
|
||||
const possiblePortsCount = 65537
|
||||
possibilities := make([]string, possiblePortsCount)
|
||||
for i := range possibilities {
|
||||
|
||||
@@ -3,6 +3,7 @@ package configuration
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -20,9 +21,14 @@ type OpenVPN struct {
|
||||
Root bool `json:"run_as_root"`
|
||||
Cipher string `json:"cipher"`
|
||||
Auth string `json:"auth"`
|
||||
Provider Provider `json:"provider"`
|
||||
Config string `json:"custom_config"`
|
||||
Version string `json:"version"`
|
||||
ClientCrt string `json:"-"` // Cyberghost
|
||||
ClientKey string `json:"-"` // Cyberghost, VPNUnlimited
|
||||
EncPreset string `json:"encryption_preset"` // PIA
|
||||
IPv6 bool `json:"ipv6"` // Mullvad
|
||||
ProcUser string `json:"procuser"` // Process username
|
||||
Interface string `json:"interface"`
|
||||
}
|
||||
|
||||
func (settings *OpenVPN) String() string {
|
||||
@@ -36,6 +42,8 @@ func (settings *OpenVPN) lines() (lines []string) {
|
||||
|
||||
lines = append(lines, indent+lastIndent+"Verbosity level: "+strconv.Itoa(settings.Verbosity))
|
||||
|
||||
lines = append(lines, indent+lastIndent+"Network interface: "+settings.Interface)
|
||||
|
||||
if len(settings.Flags) > 0 {
|
||||
lines = append(lines, indent+lastIndent+"Flags: "+strings.Join(settings.Flags, " "))
|
||||
}
|
||||
@@ -55,48 +63,32 @@ func (settings *OpenVPN) lines() (lines []string) {
|
||||
lines = append(lines, indent+lastIndent+"Custom configuration: "+settings.Config)
|
||||
}
|
||||
|
||||
if settings.Provider.Name == "" {
|
||||
lines = append(lines, indent+lastIndent+"Provider: custom configuration")
|
||||
} else {
|
||||
lines = append(lines, indent+lastIndent+"Provider:")
|
||||
for _, line := range settings.Provider.lines() {
|
||||
lines = append(lines, indent+indent+line)
|
||||
if settings.ClientKey != "" {
|
||||
lines = append(lines, indent+lastIndent+"Client key is set")
|
||||
}
|
||||
|
||||
if settings.ClientCrt != "" {
|
||||
lines = append(lines, indent+lastIndent+"Client certificate is set")
|
||||
}
|
||||
|
||||
if settings.IPv6 {
|
||||
lines = append(lines, indent+lastIndent+"IPv6: enabled")
|
||||
}
|
||||
|
||||
if settings.EncPreset != "" { // PIA only
|
||||
lines = append(lines, indent+lastIndent+"Encryption preset: "+settings.EncPreset)
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidVPNProvider = errors.New("invalid VPN provider")
|
||||
)
|
||||
|
||||
func (settings *OpenVPN) read(r reader) (err error) {
|
||||
vpnsp, err := r.env.Inside("VPNSP", []string{
|
||||
"cyberghost", "fastestvpn", "hidemyass", "ipvanish", "ivpn", "mullvad", "nordvpn",
|
||||
"privado", "pia", "private internet access", "privatevpn", "protonvpn",
|
||||
"purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn", "windscribe"},
|
||||
params.Default("private internet access"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable VPNSP: %w", err)
|
||||
}
|
||||
if vpnsp == "pia" { // retro compatibility
|
||||
vpnsp = "private internet access"
|
||||
}
|
||||
|
||||
settings.Provider.Name = vpnsp
|
||||
|
||||
func (settings *OpenVPN) read(r reader, serviceProvider string) (err error) {
|
||||
settings.Config, err = r.env.Get("OPENVPN_CUSTOM_CONFIG", params.CaseSensitiveValue())
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable OPENVPN_CUSTOM_CONFIG: %w", err)
|
||||
}
|
||||
customConfig := settings.Config != ""
|
||||
|
||||
if customConfig {
|
||||
settings.Provider.Name = ""
|
||||
}
|
||||
|
||||
credentialsRequired := !customConfig && settings.Provider.Name != constants.VPNUnlimited
|
||||
credentialsRequired := settings.Config == "" && serviceProvider != constants.VPNUnlimited
|
||||
|
||||
settings.User, err = r.getFromEnvOrSecretFile("OPENVPN_USER", credentialsRequired, []string{"USER"})
|
||||
if err != nil {
|
||||
@@ -105,7 +97,7 @@ func (settings *OpenVPN) read(r reader) (err error) {
|
||||
// Remove spaces in user ID to simplify user's life, thanks @JeordyR
|
||||
settings.User = strings.ReplaceAll(settings.User, " ", "")
|
||||
|
||||
if settings.Provider.Name == constants.Mullvad {
|
||||
if serviceProvider == constants.Mullvad {
|
||||
settings.Password = "m"
|
||||
} else {
|
||||
settings.Password, err = r.getFromEnvOrSecretFile("OPENVPN_PASSWORD", credentialsRequired, []string{"PASSWORD"})
|
||||
@@ -155,50 +147,58 @@ func (settings *OpenVPN) read(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable OPENVPN_MSSFIX: %w", err)
|
||||
}
|
||||
settings.MSSFix = uint16(mssFix)
|
||||
return settings.readProvider(r)
|
||||
|
||||
settings.IPv6, err = r.env.OnOff("OPENVPN_IPV6", params.Default("off"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable OPENVPN_IPV6: %w", err)
|
||||
}
|
||||
|
||||
func (settings *OpenVPN) readProvider(r reader) error {
|
||||
var readProvider func(r reader) error
|
||||
switch settings.Provider.Name {
|
||||
case "": // custom config
|
||||
readProvider = func(r reader) error { return nil }
|
||||
settings.Interface, err = readInterface(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.EncPreset, err = getPIAEncryptionPreset(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch serviceProvider {
|
||||
case constants.Cyberghost:
|
||||
readProvider = settings.Provider.readCyberghost
|
||||
case constants.Fastestvpn:
|
||||
readProvider = settings.Provider.readFastestvpn
|
||||
case constants.HideMyAss:
|
||||
readProvider = settings.Provider.readHideMyAss
|
||||
case constants.Ipvanish:
|
||||
readProvider = settings.Provider.readIpvanish
|
||||
case constants.Ivpn:
|
||||
readProvider = settings.Provider.readIvpn
|
||||
case constants.Mullvad:
|
||||
readProvider = settings.Provider.readMullvad
|
||||
case constants.Nordvpn:
|
||||
readProvider = settings.Provider.readNordvpn
|
||||
case constants.Privado:
|
||||
readProvider = settings.Provider.readPrivado
|
||||
case constants.PrivateInternetAccess:
|
||||
readProvider = settings.Provider.readPrivateInternetAccess
|
||||
case constants.Privatevpn:
|
||||
readProvider = settings.Provider.readPrivatevpn
|
||||
case constants.Protonvpn:
|
||||
readProvider = settings.Provider.readProtonvpn
|
||||
case constants.Purevpn:
|
||||
readProvider = settings.Provider.readPurevpn
|
||||
case constants.Surfshark:
|
||||
readProvider = settings.Provider.readSurfshark
|
||||
case constants.Torguard:
|
||||
readProvider = settings.Provider.readTorguard
|
||||
err = settings.readCyberghost(r)
|
||||
case constants.VPNUnlimited:
|
||||
readProvider = settings.Provider.readVPNUnlimited
|
||||
case constants.Vyprvpn:
|
||||
readProvider = settings.Provider.readVyprvpn
|
||||
case constants.Windscribe:
|
||||
readProvider = settings.Provider.readWindscribe
|
||||
default:
|
||||
return fmt.Errorf("%w: %s", ErrInvalidVPNProvider, settings.Provider.Name)
|
||||
err = settings.readVPNUnlimited(r)
|
||||
}
|
||||
return readProvider(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readProtocol(env params.Interface) (tcp bool, err error) {
|
||||
protocol, err := env.Inside("PROTOCOL", []string{constants.TCP, constants.UDP}, params.Default(constants.UDP))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("environment variable PROTOCOL: %w", err)
|
||||
}
|
||||
return protocol == constants.TCP, nil
|
||||
}
|
||||
|
||||
const openvpnIntfRegexString = `^.*[0-9]$`
|
||||
|
||||
var openvpnIntfRegexp = regexp.MustCompile(openvpnIntfRegexString)
|
||||
var errInterfaceNameNotValid = errors.New("interface name is not valid")
|
||||
|
||||
func readInterface(env params.Interface) (intf string, err error) {
|
||||
intf, err = env.Get("OPENVPN_INTERFACE", params.Default("tun0"))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("environment variable OPENVPN_INTERFACE: %w", err)
|
||||
}
|
||||
|
||||
if !openvpnIntfRegexp.MatchString(intf) {
|
||||
return "", fmt.Errorf("%w: does not match regex %s: %s",
|
||||
errInterfaceNameNotValid, openvpnIntfRegexString, intf)
|
||||
}
|
||||
|
||||
return intf, nil
|
||||
}
|
||||
|
||||
@@ -13,9 +13,6 @@ func Test_OpenVPN_JSON(t *testing.T) {
|
||||
in := OpenVPN{
|
||||
Root: true,
|
||||
Flags: []string{},
|
||||
Provider: Provider{
|
||||
Name: "name",
|
||||
},
|
||||
}
|
||||
data, err := json.MarshalIndent(in, "", " ")
|
||||
require.NoError(t, err)
|
||||
@@ -28,35 +25,12 @@ func Test_OpenVPN_JSON(t *testing.T) {
|
||||
"run_as_root": true,
|
||||
"cipher": "",
|
||||
"auth": "",
|
||||
"provider": {
|
||||
"name": "name",
|
||||
"server_selection": {
|
||||
"tcp": false,
|
||||
"regions": null,
|
||||
"group": "",
|
||||
"countries": null,
|
||||
"cities": null,
|
||||
"hostnames": null,
|
||||
"names": null,
|
||||
"isps": null,
|
||||
"owned": false,
|
||||
"custom_port": 0,
|
||||
"numbers": null,
|
||||
"encryption_preset": "",
|
||||
"free_only": false,
|
||||
"stream_only": false
|
||||
},
|
||||
"extra_config": {
|
||||
"encryption_preset": "",
|
||||
"openvpn_ipv6": false
|
||||
},
|
||||
"port_forwarding": {
|
||||
"enabled": false,
|
||||
"filepath": ""
|
||||
}
|
||||
},
|
||||
"custom_config": "",
|
||||
"version": ""
|
||||
"version": "",
|
||||
"encryption_preset": "",
|
||||
"ipv6": false,
|
||||
"procuser": "",
|
||||
"interface": ""
|
||||
}`, string(data))
|
||||
var out OpenVPN
|
||||
err = json.Unmarshal(data, &out)
|
||||
|
||||
@@ -6,34 +6,9 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
func (settings *Provider) privadoLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Countries) > 0 {
|
||||
lines = append(lines, lastIndent+"Countries: "+commaJoin(settings.ServerSelection.Countries))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Regions) > 0 {
|
||||
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Cities) > 0 {
|
||||
lines = append(lines, lastIndent+"Cities: "+commaJoin(settings.ServerSelection.Cities))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Hostnames) > 0 {
|
||||
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readPrivado(r reader) (err error) {
|
||||
settings.Name = constants.Privado
|
||||
|
||||
settings.ServerSelection.TCP, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -2,65 +2,19 @@ package configuration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
func (settings *Provider) privateinternetaccessLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Regions) > 0 {
|
||||
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Hostnames) > 0 {
|
||||
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Names) > 0 {
|
||||
lines = append(lines, lastIndent+"Names: "+commaJoin(settings.ServerSelection.Names))
|
||||
}
|
||||
|
||||
lines = append(lines, lastIndent+"Encryption preset: "+settings.ServerSelection.EncryptionPreset)
|
||||
|
||||
if settings.ServerSelection.CustomPort > 0 {
|
||||
lines = append(lines, lastIndent+"Custom port: "+strconv.Itoa(int(settings.ServerSelection.CustomPort)))
|
||||
}
|
||||
|
||||
if settings.PortForwarding.Enabled {
|
||||
lines = append(lines, lastIndent+"Port forwarding:")
|
||||
for _, line := range settings.PortForwarding.lines() {
|
||||
lines = append(lines, indent+line)
|
||||
}
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readPrivateInternetAccess(r reader) (err error) {
|
||||
settings.Name = constants.PrivateInternetAccess
|
||||
|
||||
settings.ServerSelection.TCP, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encryptionPreset, err := r.env.Inside("PIA_ENCRYPTION",
|
||||
[]string{constants.PIAEncryptionPresetNone, constants.PIAEncryptionPresetNormal, constants.PIAEncryptionPresetStrong},
|
||||
params.RetroKeys([]string{"ENCRYPTION"}, r.onRetroActive),
|
||||
params.Default(constants.PIACertificateStrong),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable PIA_ENCRYPTION: %w", err)
|
||||
}
|
||||
settings.ServerSelection.EncryptionPreset = encryptionPreset
|
||||
settings.ExtraConfigOptions.EncryptionPreset = encryptionPreset
|
||||
|
||||
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.PIAGeoChoices())
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable REGION: %w", err)
|
||||
@@ -76,11 +30,6 @@ func (settings *Provider) readPrivateInternetAccess(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable SERVER_NAME: %w", err)
|
||||
}
|
||||
|
||||
settings.ServerSelection.CustomPort, err = readPortOrZero(r.env, "PORT")
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable PORT: %w", err)
|
||||
}
|
||||
|
||||
settings.PortForwarding.Enabled, err = r.env.OnOff("PORT_FORWARDING", params.Default("off"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable PORT_FORWARDING: %w", err)
|
||||
@@ -94,5 +43,32 @@ func (settings *Provider) readPrivateInternetAccess(r reader) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
return settings.ServerSelection.OpenVPN.readPrivateInternetAccess(r)
|
||||
}
|
||||
|
||||
func (settings *OpenVPNSelection) readPrivateInternetAccess(r reader) (err error) {
|
||||
settings.EncPreset, err = getPIAEncryptionPreset(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.CustomPort, err = readPortOrZero(r.env, "PORT")
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable PORT: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getPIAEncryptionPreset(r reader) (encryptionPreset string, err error) {
|
||||
encryptionPreset, err = r.env.Inside("PIA_ENCRYPTION",
|
||||
[]string{constants.PIAEncryptionPresetNone, constants.PIAEncryptionPresetNormal, constants.PIAEncryptionPresetStrong},
|
||||
params.RetroKeys([]string{"ENCRYPTION"}, r.onRetroActive),
|
||||
params.Default(constants.PIACertificateStrong),
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("environment variable PIA_ENCRYPTION: %w", err)
|
||||
}
|
||||
|
||||
return encryptionPreset, nil
|
||||
}
|
||||
|
||||
@@ -6,30 +6,9 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
func (settings *Provider) privatevpnLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Countries) > 0 {
|
||||
lines = append(lines, lastIndent+"Countries: "+commaJoin(settings.ServerSelection.Countries))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Cities) > 0 {
|
||||
lines = append(lines, lastIndent+"Cities: "+commaJoin(settings.ServerSelection.Cities))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Hostnames) > 0 {
|
||||
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readPrivatevpn(r reader) (err error) {
|
||||
settings.Name = constants.Privatevpn
|
||||
|
||||
settings.ServerSelection.TCP, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -50,5 +29,5 @@ func (settings *Provider) readPrivatevpn(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r.env)
|
||||
}
|
||||
|
||||
@@ -7,52 +7,14 @@ import (
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
func (settings *Provider) protonvpnLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Countries) > 0 {
|
||||
lines = append(lines, lastIndent+"Countries: "+commaJoin(settings.ServerSelection.Countries))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Regions) > 0 {
|
||||
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Cities) > 0 {
|
||||
lines = append(lines, lastIndent+"Cities: "+commaJoin(settings.ServerSelection.Cities))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Names) > 0 {
|
||||
lines = append(lines, lastIndent+"Names: "+commaJoin(settings.ServerSelection.Names))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Hostnames) > 0 {
|
||||
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
|
||||
}
|
||||
|
||||
if settings.ServerSelection.FreeOnly {
|
||||
lines = append(lines, lastIndent+"Free only: yes")
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readProtonvpn(r reader) (err error) {
|
||||
settings.Name = constants.Protonvpn
|
||||
|
||||
settings.ServerSelection.TCP, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.CustomPort, err = readPortOrZero(r.env, "PORT")
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable PORT: %w", err)
|
||||
}
|
||||
|
||||
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.ProtonvpnCountryChoices())
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable COUNTRY: %w", err)
|
||||
@@ -83,5 +45,5 @@ func (settings *Provider) readProtonvpn(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable FREE_ONLY: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r.env)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
@@ -13,79 +14,113 @@ import (
|
||||
type Provider struct {
|
||||
Name string `json:"name"`
|
||||
ServerSelection ServerSelection `json:"server_selection"`
|
||||
ExtraConfigOptions ExtraConfigOptions `json:"extra_config"`
|
||||
PortForwarding PortForwarding `json:"port_forwarding"`
|
||||
}
|
||||
|
||||
func (settings *Provider) lines() (lines []string) {
|
||||
if settings.Name == "" { // custom OpenVPN configuration
|
||||
return nil
|
||||
}
|
||||
|
||||
lines = append(lines, lastIndent+strings.Title(settings.Name)+" settings:")
|
||||
|
||||
selection := settings.ServerSelection
|
||||
|
||||
lines = append(lines, indent+lastIndent+"Network protocol: "+protoToString(selection.TCP))
|
||||
|
||||
if selection.TargetIP != nil {
|
||||
lines = append(lines, indent+lastIndent+"Target IP address: "+selection.TargetIP.String())
|
||||
}
|
||||
|
||||
var providerLines []string
|
||||
switch strings.ToLower(settings.Name) {
|
||||
case "cyberghost":
|
||||
providerLines = settings.cyberghostLines()
|
||||
case "fastestvpn":
|
||||
providerLines = settings.fastestvpnLines()
|
||||
case "hidemyass":
|
||||
providerLines = settings.hideMyAssLines()
|
||||
case "ipvanish":
|
||||
providerLines = settings.ipvanishLines()
|
||||
case "ivpn":
|
||||
providerLines = settings.ivpnLines()
|
||||
case "mullvad":
|
||||
providerLines = settings.mullvadLines()
|
||||
case "nordvpn":
|
||||
providerLines = settings.nordvpnLines()
|
||||
case "privado":
|
||||
providerLines = settings.privadoLines()
|
||||
case "privatevpn":
|
||||
providerLines = settings.privatevpnLines()
|
||||
case "private internet access":
|
||||
providerLines = settings.privateinternetaccessLines()
|
||||
case "protonvpn":
|
||||
providerLines = settings.protonvpnLines()
|
||||
case "purevpn":
|
||||
providerLines = settings.purevpnLines()
|
||||
case "surfshark":
|
||||
providerLines = settings.surfsharkLines()
|
||||
case "torguard":
|
||||
providerLines = settings.torguardLines()
|
||||
case strings.ToLower(constants.VPNUnlimited):
|
||||
providerLines = settings.vpnUnlimitedLines()
|
||||
case "vyprvpn":
|
||||
providerLines = settings.vyprvpnLines()
|
||||
case "windscribe":
|
||||
providerLines = settings.windscribeLines()
|
||||
default:
|
||||
panic(`Missing lines method for provider "` +
|
||||
settings.Name + `"! Please create a Github issue.`)
|
||||
}
|
||||
|
||||
for _, line := range providerLines {
|
||||
for _, line := range settings.ServerSelection.toLines() {
|
||||
lines = append(lines, indent+line)
|
||||
}
|
||||
|
||||
if settings.PortForwarding.Enabled { // PIA
|
||||
lines = append(lines, indent+lastIndent+"Port forwarding:")
|
||||
for _, line := range settings.PortForwarding.lines() {
|
||||
lines = append(lines, indent+indent+line)
|
||||
}
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func commaJoin(slice []string) string {
|
||||
return strings.Join(slice, ", ")
|
||||
var (
|
||||
ErrInvalidVPNProvider = errors.New("invalid VPN provider")
|
||||
)
|
||||
|
||||
func (settings *Provider) read(r reader, vpnType string) error {
|
||||
err := settings.readVPNServiceProvider(r, vpnType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
func readProtocol(env params.Env) (tcp bool, err error) {
|
||||
protocol, err := env.Inside("PROTOCOL", []string{constants.TCP, constants.UDP}, params.Default(constants.UDP))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("environment variable PROTOCOL: %w", err)
|
||||
switch settings.Name {
|
||||
case constants.Cyberghost:
|
||||
err = settings.readCyberghost(r)
|
||||
case constants.Fastestvpn:
|
||||
err = settings.readFastestvpn(r)
|
||||
case constants.HideMyAss:
|
||||
err = settings.readHideMyAss(r)
|
||||
case constants.Ipvanish:
|
||||
err = settings.readIpvanish(r)
|
||||
case constants.Ivpn:
|
||||
err = settings.readIvpn(r)
|
||||
case constants.Mullvad:
|
||||
err = settings.readMullvad(r)
|
||||
case constants.Nordvpn:
|
||||
err = settings.readNordvpn(r)
|
||||
case constants.Privado:
|
||||
err = settings.readPrivado(r)
|
||||
case constants.PrivateInternetAccess:
|
||||
err = settings.readPrivateInternetAccess(r)
|
||||
case constants.Privatevpn:
|
||||
err = settings.readPrivatevpn(r)
|
||||
case constants.Protonvpn:
|
||||
err = settings.readProtonvpn(r)
|
||||
case constants.Purevpn:
|
||||
err = settings.readPurevpn(r)
|
||||
case constants.Surfshark:
|
||||
err = settings.readSurfshark(r)
|
||||
case constants.Torguard:
|
||||
err = settings.readTorguard(r)
|
||||
case constants.VPNUnlimited:
|
||||
err = settings.readVPNUnlimited(r)
|
||||
case constants.Vyprvpn:
|
||||
err = settings.readVyprvpn(r)
|
||||
case constants.Windscribe:
|
||||
err = settings.readWindscribe(r)
|
||||
default:
|
||||
return fmt.Errorf("%w: %s", ErrInvalidVPNProvider, settings.Name)
|
||||
}
|
||||
return protocol == constants.TCP, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.VPN = vpnType
|
||||
return nil
|
||||
}
|
||||
|
||||
func (settings *Provider) readVPNServiceProvider(r reader, vpnType string) (err error) {
|
||||
var allowedVPNServiceProviders []string
|
||||
switch vpnType {
|
||||
case constants.OpenVPN:
|
||||
allowedVPNServiceProviders = []string{
|
||||
"cyberghost", "fastestvpn", "hidemyass", "ipvanish", "ivpn", "mullvad", "nordvpn",
|
||||
"privado", "pia", "private internet access", "privatevpn", "protonvpn",
|
||||
"purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn", "windscribe"}
|
||||
case constants.Wireguard:
|
||||
allowedVPNServiceProviders = []string{constants.Mullvad, constants.Windscribe}
|
||||
}
|
||||
|
||||
vpnsp, err := r.env.Inside("VPNSP", allowedVPNServiceProviders,
|
||||
params.Default("private internet access"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable VPNSP: %w", err)
|
||||
}
|
||||
if vpnsp == "pia" { // retro compatibility
|
||||
vpnsp = "private internet access"
|
||||
}
|
||||
settings.Name = vpnsp
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func commaJoin(slice []string) string {
|
||||
return strings.Join(slice, ", ")
|
||||
}
|
||||
|
||||
func protoToString(tcp bool) string {
|
||||
@@ -95,7 +130,7 @@ func protoToString(tcp bool) string {
|
||||
return constants.UDP
|
||||
}
|
||||
|
||||
func readTargetIP(env params.Env) (targetIP net.IP, err error) {
|
||||
func readTargetIP(env params.Interface) (targetIP net.IP, err error) {
|
||||
targetIP, err = readIP(env, "OPENVPN_TARGET_IP")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable OPENVPN_TARGET_IP: %w", err)
|
||||
@@ -103,7 +138,7 @@ func readTargetIP(env params.Env) (targetIP net.IP, err error) {
|
||||
return targetIP, nil
|
||||
}
|
||||
|
||||
func readCustomPort(env params.Env, tcp bool,
|
||||
func readOpenVPNCustomPort(env params.Interface, tcp bool,
|
||||
allowedTCP, allowedUDP []uint16) (port uint16, err error) {
|
||||
port, err = readPortOrZero(env, "PORT")
|
||||
if err != nil {
|
||||
@@ -118,12 +153,42 @@ func readCustomPort(env params.Env, tcp bool,
|
||||
return port, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("environment variable PORT: %w: port %d for TCP protocol", ErrInvalidPort, port)
|
||||
return 0, fmt.Errorf(
|
||||
"environment variable PORT: %w: port %d for TCP protocol, can only be one of %s",
|
||||
ErrInvalidPort, port, portsToString(allowedTCP))
|
||||
}
|
||||
for i := range allowedUDP {
|
||||
if allowedUDP[i] == port {
|
||||
return port, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("environment variable PORT: %w: port %d for UDP protocol", ErrInvalidPort, port)
|
||||
return 0, fmt.Errorf(
|
||||
"environment variable PORT: %w: port %d for UDP protocol, can only be one of %s",
|
||||
ErrInvalidPort, port, portsToString(allowedUDP))
|
||||
}
|
||||
|
||||
func readWireguardCustomPort(env params.Interface, allowed []uint16) (port uint16, err error) {
|
||||
port, err = readPortOrZero(env, "WIREGUARD_PORT")
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("environment variable WIREGUARD_PORT: %w", err)
|
||||
} else if port == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
for i := range allowed {
|
||||
if allowed[i] == port {
|
||||
return port, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf(
|
||||
"environment variable WIREGUARD_PORT: %w: port %d, can only be one of %s",
|
||||
ErrInvalidPort, port, portsToString(allowed))
|
||||
}
|
||||
|
||||
func portsToString(ports []uint16) string {
|
||||
slice := make([]string, len(ports))
|
||||
for i := range ports {
|
||||
slice[i] = fmt.Sprint(ports[i])
|
||||
}
|
||||
return strings.Join(slice, ", ")
|
||||
}
|
||||
|
||||
@@ -24,42 +24,41 @@ func Test_Provider_lines(t *testing.T) {
|
||||
settings: Provider{
|
||||
Name: constants.Cyberghost,
|
||||
ServerSelection: ServerSelection{
|
||||
Group: "group",
|
||||
VPN: constants.OpenVPN,
|
||||
Groups: []string{"group"},
|
||||
Regions: []string{"a", "El country"},
|
||||
},
|
||||
ExtraConfigOptions: ExtraConfigOptions{
|
||||
ClientKey: "a",
|
||||
ClientCertificate: "a",
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Cyberghost settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Server group: group",
|
||||
" |--Server groups: group",
|
||||
" |--Regions: a, El country",
|
||||
" |--Client key is set",
|
||||
" |--Client certificate is set",
|
||||
" |--OpenVPN selection:",
|
||||
" |--Protocol: udp",
|
||||
},
|
||||
},
|
||||
"fastestvpn": {
|
||||
settings: Provider{
|
||||
Name: constants.Fastestvpn,
|
||||
ServerSelection: ServerSelection{
|
||||
VPN: constants.OpenVPN,
|
||||
Hostnames: []string{"a", "b"},
|
||||
Countries: []string{"c", "d"},
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Fastestvpn settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Hostnames: a, b",
|
||||
" |--Countries: c, d",
|
||||
" |--Hostnames: a, b",
|
||||
" |--OpenVPN selection:",
|
||||
" |--Protocol: udp",
|
||||
},
|
||||
},
|
||||
"hidemyass": {
|
||||
settings: Provider{
|
||||
Name: constants.HideMyAss,
|
||||
ServerSelection: ServerSelection{
|
||||
VPN: constants.OpenVPN,
|
||||
Countries: []string{"a", "b"},
|
||||
Cities: []string{"c", "d"},
|
||||
Hostnames: []string{"e", "f"},
|
||||
@@ -67,16 +66,18 @@ func Test_Provider_lines(t *testing.T) {
|
||||
},
|
||||
lines: []string{
|
||||
"|--Hidemyass settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Countries: a, b",
|
||||
" |--Cities: c, d",
|
||||
" |--Hostnames: e, f",
|
||||
" |--OpenVPN selection:",
|
||||
" |--Protocol: udp",
|
||||
},
|
||||
},
|
||||
"ipvanish": {
|
||||
settings: Provider{
|
||||
Name: constants.Ipvanish,
|
||||
ServerSelection: ServerSelection{
|
||||
VPN: constants.OpenVPN,
|
||||
Countries: []string{"a", "b"},
|
||||
Cities: []string{"c", "d"},
|
||||
Hostnames: []string{"e", "f"},
|
||||
@@ -84,16 +85,18 @@ func Test_Provider_lines(t *testing.T) {
|
||||
},
|
||||
lines: []string{
|
||||
"|--Ipvanish settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Countries: a, b",
|
||||
" |--Cities: c, d",
|
||||
" |--Hostnames: e, f",
|
||||
" |--OpenVPN selection:",
|
||||
" |--Protocol: udp",
|
||||
},
|
||||
},
|
||||
"ivpn": {
|
||||
settings: Provider{
|
||||
Name: constants.Ivpn,
|
||||
ServerSelection: ServerSelection{
|
||||
VPN: constants.OpenVPN,
|
||||
Countries: []string{"a", "b"},
|
||||
Cities: []string{"c", "d"},
|
||||
Hostnames: []string{"e", "f"},
|
||||
@@ -101,67 +104,73 @@ func Test_Provider_lines(t *testing.T) {
|
||||
},
|
||||
lines: []string{
|
||||
"|--Ivpn settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Countries: a, b",
|
||||
" |--Cities: c, d",
|
||||
" |--Hostnames: e, f",
|
||||
" |--OpenVPN selection:",
|
||||
" |--Protocol: udp",
|
||||
},
|
||||
},
|
||||
"mullvad": {
|
||||
settings: Provider{
|
||||
Name: constants.Mullvad,
|
||||
ServerSelection: ServerSelection{
|
||||
VPN: constants.OpenVPN,
|
||||
Countries: []string{"a", "b"},
|
||||
Cities: []string{"c", "d"},
|
||||
ISPs: []string{"e", "f"},
|
||||
OpenVPN: OpenVPNSelection{
|
||||
CustomPort: 1,
|
||||
},
|
||||
ExtraConfigOptions: ExtraConfigOptions{
|
||||
OpenVPNIPv6: true,
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Mullvad settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Countries: a, b",
|
||||
" |--Cities: c, d",
|
||||
" |--ISPs: e, f",
|
||||
" |--OpenVPN selection:",
|
||||
" |--Protocol: udp",
|
||||
" |--Custom port: 1",
|
||||
" |--IPv6: enabled",
|
||||
},
|
||||
},
|
||||
"nordvpn": {
|
||||
settings: Provider{
|
||||
Name: constants.Nordvpn,
|
||||
ServerSelection: ServerSelection{
|
||||
VPN: constants.OpenVPN,
|
||||
Regions: []string{"a", "b"},
|
||||
Numbers: []uint16{1, 2},
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Nordvpn settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Regions: a, b",
|
||||
" |--Numbers: 1, 2",
|
||||
" |--OpenVPN selection:",
|
||||
" |--Protocol: udp",
|
||||
},
|
||||
},
|
||||
"privado": {
|
||||
settings: Provider{
|
||||
Name: constants.Privado,
|
||||
ServerSelection: ServerSelection{
|
||||
VPN: constants.OpenVPN,
|
||||
Hostnames: []string{"a", "b"},
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Privado settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Hostnames: a, b",
|
||||
" |--OpenVPN selection:",
|
||||
" |--Protocol: udp",
|
||||
},
|
||||
},
|
||||
"privatevpn": {
|
||||
settings: Provider{
|
||||
Name: constants.Privatevpn,
|
||||
ServerSelection: ServerSelection{
|
||||
VPN: constants.OpenVPN,
|
||||
Hostnames: []string{"a", "b"},
|
||||
Countries: []string{"c", "d"},
|
||||
Cities: []string{"e", "f"},
|
||||
@@ -169,16 +178,18 @@ func Test_Provider_lines(t *testing.T) {
|
||||
},
|
||||
lines: []string{
|
||||
"|--Privatevpn settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Countries: c, d",
|
||||
" |--Cities: e, f",
|
||||
" |--Hostnames: a, b",
|
||||
" |--OpenVPN selection:",
|
||||
" |--Protocol: udp",
|
||||
},
|
||||
},
|
||||
"protonvpn": {
|
||||
settings: Provider{
|
||||
Name: constants.Protonvpn,
|
||||
ServerSelection: ServerSelection{
|
||||
VPN: constants.OpenVPN,
|
||||
Countries: []string{"a", "b"},
|
||||
Regions: []string{"c", "d"},
|
||||
Cities: []string{"e", "f"},
|
||||
@@ -188,22 +199,25 @@ func Test_Provider_lines(t *testing.T) {
|
||||
},
|
||||
lines: []string{
|
||||
"|--Protonvpn settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Countries: a, b",
|
||||
" |--Regions: c, d",
|
||||
" |--Cities: e, f",
|
||||
" |--Names: g, h",
|
||||
" |--Hostnames: i, j",
|
||||
" |--Names: g, h",
|
||||
" |--OpenVPN selection:",
|
||||
" |--Protocol: udp",
|
||||
},
|
||||
},
|
||||
"private internet access": {
|
||||
settings: Provider{
|
||||
Name: constants.PrivateInternetAccess,
|
||||
ServerSelection: ServerSelection{
|
||||
VPN: constants.OpenVPN,
|
||||
Regions: []string{"a", "b"},
|
||||
EncryptionPreset: constants.PIAEncryptionPresetStrong,
|
||||
OpenVPN: OpenVPNSelection{
|
||||
CustomPort: 1,
|
||||
},
|
||||
},
|
||||
PortForwarding: PortForwarding{
|
||||
Enabled: true,
|
||||
Filepath: string("/here"),
|
||||
@@ -211,9 +225,9 @@ func Test_Provider_lines(t *testing.T) {
|
||||
},
|
||||
lines: []string{
|
||||
"|--Private Internet Access settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Regions: a, b",
|
||||
" |--Encryption preset: strong",
|
||||
" |--OpenVPN selection:",
|
||||
" |--Protocol: udp",
|
||||
" |--Custom port: 1",
|
||||
" |--Port forwarding:",
|
||||
" |--File path: /here",
|
||||
@@ -223,6 +237,7 @@ func Test_Provider_lines(t *testing.T) {
|
||||
settings: Provider{
|
||||
Name: constants.Purevpn,
|
||||
ServerSelection: ServerSelection{
|
||||
VPN: constants.OpenVPN,
|
||||
Regions: []string{"a", "b"},
|
||||
Countries: []string{"c", "d"},
|
||||
Cities: []string{"e", "f"},
|
||||
@@ -230,29 +245,33 @@ func Test_Provider_lines(t *testing.T) {
|
||||
},
|
||||
lines: []string{
|
||||
"|--Purevpn settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Regions: a, b",
|
||||
" |--Countries: c, d",
|
||||
" |--Regions: a, b",
|
||||
" |--Cities: e, f",
|
||||
" |--OpenVPN selection:",
|
||||
" |--Protocol: udp",
|
||||
},
|
||||
},
|
||||
"surfshark": {
|
||||
settings: Provider{
|
||||
Name: constants.Surfshark,
|
||||
ServerSelection: ServerSelection{
|
||||
VPN: constants.OpenVPN,
|
||||
Regions: []string{"a", "b"},
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Surfshark settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Regions: a, b",
|
||||
" |--OpenVPN selection:",
|
||||
" |--Protocol: udp",
|
||||
},
|
||||
},
|
||||
"torguard": {
|
||||
settings: Provider{
|
||||
Name: constants.Torguard,
|
||||
ServerSelection: ServerSelection{
|
||||
VPN: constants.OpenVPN,
|
||||
Countries: []string{"a", "b"},
|
||||
Cities: []string{"c", "d"},
|
||||
Hostnames: []string{"e"},
|
||||
@@ -260,66 +279,71 @@ func Test_Provider_lines(t *testing.T) {
|
||||
},
|
||||
lines: []string{
|
||||
"|--Torguard settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Countries: a, b",
|
||||
" |--Cities: c, d",
|
||||
" |--Hostnames: e",
|
||||
" |--OpenVPN selection:",
|
||||
" |--Protocol: udp",
|
||||
},
|
||||
},
|
||||
constants.VPNUnlimited: {
|
||||
settings: Provider{
|
||||
Name: constants.VPNUnlimited,
|
||||
ServerSelection: ServerSelection{
|
||||
VPN: constants.OpenVPN,
|
||||
Countries: []string{"a", "b"},
|
||||
Cities: []string{"c", "d"},
|
||||
Hostnames: []string{"e", "f"},
|
||||
FreeOnly: true,
|
||||
StreamOnly: true,
|
||||
},
|
||||
ExtraConfigOptions: ExtraConfigOptions{
|
||||
ClientKey: "a",
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Vpn Unlimited settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Countries: a, b",
|
||||
" |--Cities: c, d",
|
||||
" |--Hostnames: e, f",
|
||||
" |--Free servers only",
|
||||
" |--Stream servers only",
|
||||
" |--Client key is set",
|
||||
" |--Hostnames: e, f",
|
||||
" |--OpenVPN selection:",
|
||||
" |--Protocol: udp",
|
||||
},
|
||||
},
|
||||
"vyprvpn": {
|
||||
settings: Provider{
|
||||
Name: constants.Vyprvpn,
|
||||
ServerSelection: ServerSelection{
|
||||
VPN: constants.OpenVPN,
|
||||
Regions: []string{"a", "b"},
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Vyprvpn settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Regions: a, b",
|
||||
" |--OpenVPN selection:",
|
||||
" |--Protocol: udp",
|
||||
},
|
||||
},
|
||||
"windscribe": {
|
||||
settings: Provider{
|
||||
Name: constants.Windscribe,
|
||||
ServerSelection: ServerSelection{
|
||||
VPN: constants.OpenVPN,
|
||||
Regions: []string{"a", "b"},
|
||||
Cities: []string{"c", "d"},
|
||||
Hostnames: []string{"e", "f"},
|
||||
OpenVPN: OpenVPNSelection{
|
||||
CustomPort: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Windscribe settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Regions: a, b",
|
||||
" |--Cities: c, d",
|
||||
" |--Hostnames: e, f",
|
||||
" |--OpenVPN selection:",
|
||||
" |--Protocol: udp",
|
||||
" |--Custom port: 1",
|
||||
},
|
||||
},
|
||||
@@ -362,7 +386,7 @@ func Test_readProtocol(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
env := mock_params.NewMockEnv(ctrl)
|
||||
env := mock_params.NewMockInterface(ctrl)
|
||||
env.EXPECT().
|
||||
Inside("PROTOCOL", []string{"tcp", "udp"}, gomock.Any()).
|
||||
Return(testCase.mockStr, testCase.mockErr)
|
||||
|
||||
@@ -6,34 +6,9 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
func (settings *Provider) purevpnLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Regions) > 0 {
|
||||
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Countries) > 0 {
|
||||
lines = append(lines, lastIndent+"Countries: "+commaJoin(settings.ServerSelection.Countries))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Cities) > 0 {
|
||||
lines = append(lines, lastIndent+"Cities: "+commaJoin(settings.ServerSelection.Cities))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Hostnames) > 0 {
|
||||
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readPurevpn(r reader) (err error) {
|
||||
settings.Name = constants.Purevpn
|
||||
|
||||
settings.ServerSelection.TCP, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -59,5 +34,5 @@ func (settings *Provider) readPurevpn(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return settings.ServerSelection.OpenVPN.readProtocolOnly(r.env)
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@ import (
|
||||
)
|
||||
|
||||
type reader struct {
|
||||
env params.Env
|
||||
env params.Interface
|
||||
logger logging.Logger
|
||||
regex verification.Regex
|
||||
}
|
||||
|
||||
func newReader(env params.Env, logger logging.Logger) reader {
|
||||
func newReader(env params.Interface, logger logging.Logger) reader {
|
||||
return reader{
|
||||
env: env,
|
||||
logger: logger,
|
||||
@@ -36,7 +36,7 @@ var (
|
||||
ErrInvalidPort = errors.New("invalid port")
|
||||
)
|
||||
|
||||
func readCSVPorts(env params.Env, key string) (ports []uint16, err error) {
|
||||
func readCSVPorts(env params.Interface, key string) (ports []uint16, err error) {
|
||||
s, err := env.Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -63,7 +63,7 @@ var (
|
||||
ErrInvalidIPNet = errors.New("invalid IP network")
|
||||
)
|
||||
|
||||
func readCSVIPNets(env params.Env, key string, options ...params.OptionSetter) (
|
||||
func readCSVIPNets(env params.Interface, key string, options ...params.OptionSetter) (
|
||||
ipNets []net.IPNet, err error) {
|
||||
s, err := env.Get(key, options...)
|
||||
if err != nil {
|
||||
@@ -92,7 +92,7 @@ var (
|
||||
ErrInvalidIP = errors.New("invalid IP address")
|
||||
)
|
||||
|
||||
func readIP(env params.Env, key string) (ip net.IP, err error) {
|
||||
func readIP(env params.Interface, key string) (ip net.IP, err error) {
|
||||
s, err := env.Get(key)
|
||||
if s == "" {
|
||||
return nil, nil
|
||||
@@ -108,13 +108,13 @@ func readIP(env params.Env, key string) (ip net.IP, err error) {
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
func readPortOrZero(env params.Env, key string) (port uint16, err error) {
|
||||
s, err := env.Get(key)
|
||||
func readPortOrZero(env params.Interface, key string) (port uint16, err error) {
|
||||
s, err := env.Get(key, params.Default("0"))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if s == "" || s == "0" {
|
||||
if s == "0" {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
type ServerSelection struct { //nolint:maligned
|
||||
// Common
|
||||
TCP bool `json:"tcp"` // UDP if TCP is false
|
||||
VPN string `json:"vpn"` // note: this is required
|
||||
TargetIP net.IP `json:"target_ip,omitempty"`
|
||||
// TODO comments
|
||||
// Cyberghost, PIA, Protonvpn, Surfshark, Windscribe, Vyprvpn, NordVPN
|
||||
Regions []string `json:"regions"`
|
||||
|
||||
// Cyberghost
|
||||
Group string `json:"group"`
|
||||
Groups []string `json:"groups"`
|
||||
|
||||
// Fastestvpn, HideMyAss, IPVanish, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited
|
||||
Countries []string `json:"countries"`
|
||||
@@ -27,27 +31,130 @@ type ServerSelection struct { //nolint:maligned
|
||||
ISPs []string `json:"isps"`
|
||||
Owned bool `json:"owned"`
|
||||
|
||||
// Mullvad, Windscribe, PIA
|
||||
CustomPort uint16 `json:"custom_port"`
|
||||
|
||||
// NordVPN
|
||||
Numbers []uint16 `json:"numbers"`
|
||||
|
||||
// PIA
|
||||
EncryptionPreset string `json:"encryption_preset"`
|
||||
|
||||
// ProtonVPN
|
||||
FreeOnly bool `json:"free_only"`
|
||||
|
||||
// VPNUnlimited
|
||||
StreamOnly bool `json:"stream_only"`
|
||||
|
||||
OpenVPN OpenVPNSelection `json:"openvpn"`
|
||||
Wireguard WireguardSelection `json:"wireguard"`
|
||||
}
|
||||
|
||||
type ExtraConfigOptions struct {
|
||||
ClientCertificate string `json:"-"` // Cyberghost
|
||||
ClientKey string `json:"-"` // Cyberghost, VPNUnlimited
|
||||
EncryptionPreset string `json:"encryption_preset"` // PIA
|
||||
OpenVPNIPv6 bool `json:"openvpn_ipv6"` // Mullvad
|
||||
func (selection ServerSelection) toLines() (lines []string) {
|
||||
if selection.TargetIP != nil {
|
||||
lines = append(lines, lastIndent+"Target IP address: "+selection.TargetIP.String())
|
||||
}
|
||||
|
||||
if len(selection.Groups) > 0 {
|
||||
lines = append(lines, lastIndent+"Server groups: "+commaJoin(selection.Groups))
|
||||
}
|
||||
|
||||
if len(selection.Countries) > 0 {
|
||||
lines = append(lines, lastIndent+"Countries: "+commaJoin(selection.Countries))
|
||||
}
|
||||
|
||||
if len(selection.Regions) > 0 {
|
||||
lines = append(lines, lastIndent+"Regions: "+commaJoin(selection.Regions))
|
||||
}
|
||||
|
||||
if len(selection.Cities) > 0 {
|
||||
lines = append(lines, lastIndent+"Cities: "+commaJoin(selection.Cities))
|
||||
}
|
||||
|
||||
if len(selection.ISPs) > 0 {
|
||||
lines = append(lines, lastIndent+"ISPs: "+commaJoin(selection.ISPs))
|
||||
}
|
||||
|
||||
if selection.FreeOnly {
|
||||
lines = append(lines, lastIndent+"Free servers only")
|
||||
}
|
||||
|
||||
if selection.StreamOnly {
|
||||
lines = append(lines, lastIndent+"Stream servers only")
|
||||
}
|
||||
|
||||
if len(selection.Hostnames) > 0 {
|
||||
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(selection.Hostnames))
|
||||
}
|
||||
|
||||
if len(selection.Names) > 0 {
|
||||
lines = append(lines, lastIndent+"Names: "+commaJoin(selection.Names))
|
||||
}
|
||||
|
||||
if len(selection.Numbers) > 0 {
|
||||
numbersString := make([]string, len(selection.Numbers))
|
||||
for i, numberUint16 := range selection.Numbers {
|
||||
numbersString[i] = fmt.Sprint(numberUint16)
|
||||
}
|
||||
lines = append(lines, lastIndent+"Numbers: "+commaJoin(numbersString))
|
||||
}
|
||||
|
||||
if selection.VPN == constants.OpenVPN {
|
||||
lines = append(lines, selection.OpenVPN.lines()...)
|
||||
} else { // wireguard
|
||||
lines = append(lines, selection.Wireguard.lines()...)
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
type OpenVPNSelection struct {
|
||||
TCP bool `json:"tcp"` // UDP if TCP is false
|
||||
CustomPort uint16 `json:"custom_port"` // HideMyAss, Mullvad, PIA, ProtonVPN, Windscribe
|
||||
EncPreset string `json:"encryption_preset"` // PIA - needed to get the port number
|
||||
}
|
||||
|
||||
func (settings *OpenVPNSelection) lines() (lines []string) {
|
||||
lines = append(lines, lastIndent+"OpenVPN selection:")
|
||||
|
||||
lines = append(lines, indent+lastIndent+"Protocol: "+protoToString(settings.TCP))
|
||||
|
||||
if settings.CustomPort != 0 {
|
||||
lines = append(lines, indent+lastIndent+"Custom port: "+fmt.Sprint(settings.CustomPort))
|
||||
}
|
||||
|
||||
if settings.EncPreset != "" {
|
||||
lines = append(lines, indent+lastIndent+"PIA encryption preset: "+settings.EncPreset)
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *OpenVPNSelection) readProtocolOnly(env params.Interface) (err error) {
|
||||
settings.TCP, err = readProtocol(env)
|
||||
return err
|
||||
}
|
||||
|
||||
func (settings *OpenVPNSelection) readProtocolAndPort(env params.Interface) (err error) {
|
||||
settings.TCP, err = readProtocol(env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.CustomPort, err = readPortOrZero(env, "PORT")
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable PORT: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type WireguardSelection struct {
|
||||
CustomPort uint16 `json:"custom_port"` // Mullvad
|
||||
}
|
||||
|
||||
func (settings *WireguardSelection) lines() (lines []string) {
|
||||
lines = append(lines, lastIndent+"Wireguard selection:")
|
||||
|
||||
if settings.CustomPort != 0 {
|
||||
lines = append(lines, indent+lastIndent+"Custom port: "+fmt.Sprint(settings.CustomPort))
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
// PortForwarding contains settings for port forwarding.
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
// Settings contains all settings for the program to run.
|
||||
type Settings struct {
|
||||
OpenVPN OpenVPN
|
||||
VPN VPN
|
||||
System System
|
||||
DNS DNS
|
||||
Firewall Firewall
|
||||
@@ -30,7 +30,7 @@ func (settings *Settings) String() string {
|
||||
|
||||
func (settings *Settings) lines() (lines []string) {
|
||||
lines = append(lines, "Settings summary below:")
|
||||
lines = append(lines, settings.OpenVPN.lines()...)
|
||||
lines = append(lines, settings.VPN.lines()...)
|
||||
lines = append(lines, settings.DNS.lines()...)
|
||||
lines = append(lines, settings.Firewall.lines()...)
|
||||
lines = append(lines, settings.System.lines()...)
|
||||
@@ -47,7 +47,7 @@ func (settings *Settings) lines() (lines []string) {
|
||||
}
|
||||
|
||||
var (
|
||||
ErrOpenvpn = errors.New("cannot read Openvpn settings")
|
||||
ErrVPN = errors.New("cannot read VPN settings")
|
||||
ErrSystem = errors.New("cannot read System settings")
|
||||
ErrDNS = errors.New("cannot read DNS settings")
|
||||
ErrFirewall = errors.New("cannot read firewall settings")
|
||||
@@ -61,7 +61,7 @@ var (
|
||||
|
||||
// Read obtains all configuration options for the program and returns an error as soon
|
||||
// as an error is encountered reading them.
|
||||
func (settings *Settings) Read(env params.Env, logger logging.Logger) (err error) {
|
||||
func (settings *Settings) Read(env params.Interface, logger logging.Logger) (err error) {
|
||||
r := newReader(env, logger)
|
||||
|
||||
settings.VersionInformation, err = r.env.OnOff("VERSION_INFORMATION", params.Default("on"))
|
||||
@@ -69,8 +69,8 @@ func (settings *Settings) Read(env params.Env, logger logging.Logger) (err error
|
||||
return fmt.Errorf("environment variable VERSION_INFORMATION: %w", err)
|
||||
}
|
||||
|
||||
if err := settings.OpenVPN.read(r); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrOpenvpn, err)
|
||||
if err := settings.VPN.read(r); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrVPN, err)
|
||||
}
|
||||
|
||||
if err := settings.System.read(r); err != nil {
|
||||
|
||||
@@ -16,21 +16,31 @@ func Test_Settings_lines(t *testing.T) {
|
||||
}{
|
||||
"default settings": {
|
||||
settings: Settings{
|
||||
OpenVPN: OpenVPN{
|
||||
Version: constants.Openvpn25,
|
||||
VPN: VPN{
|
||||
Type: constants.OpenVPN,
|
||||
Provider: Provider{
|
||||
Name: constants.Mullvad,
|
||||
ServerSelection: ServerSelection{
|
||||
VPN: constants.OpenVPN,
|
||||
},
|
||||
},
|
||||
OpenVPN: OpenVPN{
|
||||
Version: constants.Openvpn25,
|
||||
Interface: "tun",
|
||||
},
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"Settings summary below:",
|
||||
"|--VPN:",
|
||||
" |--Type: openvpn",
|
||||
" |--OpenVPN:",
|
||||
" |--Version: 2.5",
|
||||
" |--Verbosity level: 0",
|
||||
" |--Provider:",
|
||||
" |--Network interface: tun",
|
||||
" |--Mullvad settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--OpenVPN selection:",
|
||||
" |--Protocol: udp",
|
||||
"|--DNS:",
|
||||
"|--Firewall: disabled ⚠️",
|
||||
"|--System:",
|
||||
|
||||
@@ -2,19 +2,16 @@ package configuration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/golibs/params"
|
||||
"github.com/qdm12/ss-server/pkg/tcpudp"
|
||||
)
|
||||
|
||||
// ShadowSocks contains settings to configure the Shadowsocks server.
|
||||
type ShadowSocks struct {
|
||||
Method string
|
||||
Password string
|
||||
Port uint16
|
||||
Enabled bool
|
||||
Log bool
|
||||
tcpudp.Settings
|
||||
}
|
||||
|
||||
func (settings *ShadowSocks) String() string {
|
||||
@@ -28,12 +25,12 @@ func (settings *ShadowSocks) lines() (lines []string) {
|
||||
|
||||
lines = append(lines, lastIndent+"Shadowsocks server:")
|
||||
|
||||
lines = append(lines, indent+lastIndent+"Listening port: "+strconv.Itoa(int(settings.Port)))
|
||||
lines = append(lines, indent+lastIndent+"Listening address: "+settings.Address)
|
||||
|
||||
lines = append(lines, indent+lastIndent+"Method: "+settings.Method)
|
||||
lines = append(lines, indent+lastIndent+"Cipher: "+settings.CipherName)
|
||||
|
||||
if settings.Log {
|
||||
lines = append(lines, indent+lastIndent+"Logging: enabled")
|
||||
if settings.LogAddresses {
|
||||
lines = append(lines, indent+lastIndent+"Log addresses: enabled")
|
||||
}
|
||||
|
||||
return lines
|
||||
@@ -52,24 +49,61 @@ func (settings *ShadowSocks) read(r reader) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.Log, err = r.env.OnOff("SHADOWSOCKS_LOG", params.Default("off"))
|
||||
settings.LogAddresses, err = r.env.OnOff("SHADOWSOCKS_LOG", params.Default("off"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable SHADOWSOCKS_LOG: %w", err)
|
||||
}
|
||||
|
||||
settings.Method, err = r.env.Get("SHADOWSOCKS_METHOD", params.Default("chacha20-ietf-poly1305"))
|
||||
settings.CipherName, err = r.env.Get("SHADOWSOCKS_CIPHER", params.Default("chacha20-ietf-poly1305"),
|
||||
params.RetroKeys([]string{"SHADOWSOCKS_METHOD"}, r.onRetroActive))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable SHADOWSOCKS_METHOD: %w", err)
|
||||
return fmt.Errorf("environment variable SHADOWSOCKS_CIPHER (or SHADOWSOCKS_METHOD): %w", err)
|
||||
}
|
||||
|
||||
var warning string
|
||||
settings.Port, warning, err = r.env.ListeningPort("SHADOWSOCKS_PORT", params.Default("8388"))
|
||||
if len(warning) > 0 {
|
||||
warning, err := settings.getAddress(r.env)
|
||||
if warning != "" {
|
||||
r.logger.Warn(warning)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable SHADOWSOCKS_PORT: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (settings *ShadowSocks) getAddress(env params.Interface) (
|
||||
warning string, err error) {
|
||||
address, err := env.Get("SHADOWSOCKS_LISTENING_ADDRESS")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("environment variable SHADOWSOCKS_LISTENING_ADDRESS: %w", err)
|
||||
}
|
||||
|
||||
if address != "" {
|
||||
address, warning, err := env.ListeningAddress("SHADOWSOCKS_LISTENING_ADDRESS")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("environment variable SHADOWSOCKS_LISTENING_ADDRESS: %w", err)
|
||||
}
|
||||
settings.Address = address
|
||||
return warning, nil
|
||||
}
|
||||
|
||||
// Retro-compatibility
|
||||
const retroWarning = "You are using the old environment variable " +
|
||||
"SHADOWSOCKS_PORT, please consider using " +
|
||||
"SHADOWSOCKS_LISTENING_ADDRESS instead"
|
||||
portStr, err := env.Get("SHADOWSOCKS_PORT")
|
||||
if err != nil {
|
||||
return retroWarning, fmt.Errorf("environment variable SHADOWSOCKS_PORT: %w", err)
|
||||
} else if portStr != "" {
|
||||
port, _, err := env.ListeningPort("SHADOWSOCKS_PORT")
|
||||
if err != nil {
|
||||
return retroWarning, fmt.Errorf("environment variable SHADOWSOCKS_PORT: %w", err)
|
||||
}
|
||||
settings.Address = ":" + fmt.Sprint(port)
|
||||
return retroWarning, nil
|
||||
}
|
||||
|
||||
// Default value
|
||||
settings.Address = ":8388"
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -6,26 +6,9 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
func (settings *Provider) surfsharkLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Regions) > 0 {
|
||||
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Hostnames) > 0 {
|
||||
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readSurfshark(r reader) (err error) {
|
||||
settings.Name = constants.Surfshark
|
||||
|
||||
settings.ServerSelection.TCP, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -41,5 +24,5 @@ func (settings *Provider) readSurfshark(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return settings.ServerSelection.OpenVPN.readProtocolOnly(r.env)
|
||||
}
|
||||
|
||||
@@ -6,30 +6,9 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
func (settings *Provider) torguardLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Countries) > 0 {
|
||||
lines = append(lines, lastIndent+"Countries: "+commaJoin(settings.ServerSelection.Countries))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Cities) > 0 {
|
||||
lines = append(lines, lastIndent+"Cities: "+commaJoin(settings.ServerSelection.Cities))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Hostnames) > 0 {
|
||||
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readTorguard(r reader) (err error) {
|
||||
settings.Name = constants.Torguard
|
||||
|
||||
settings.ServerSelection.TCP, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -50,5 +29,5 @@ func (settings *Provider) readTorguard(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r.env)
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ var (
|
||||
ErrInvalidDNSOverTLSProvider = errors.New("invalid DNS over TLS provider")
|
||||
)
|
||||
|
||||
func (settings *DNS) readUnboundProviders(env params.Env) (err error) {
|
||||
func (settings *DNS) readUnboundProviders(env params.Interface) (err error) {
|
||||
s, err := env.Get("DOT_PROVIDERS", params.Default("cloudflare"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable DOT_PROVIDERS: %w", err)
|
||||
|
||||
@@ -60,7 +60,7 @@ func Test_DNS_readUnboundProviders(t *testing.T) {
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
env := mock_params.NewMockEnv(ctrl)
|
||||
env := mock_params.NewMockInterface(ctrl)
|
||||
env.EXPECT().Get("DOT_PROVIDERS", gomock.Any()).
|
||||
Return(testCase.envValue, testCase.envErr)
|
||||
|
||||
|
||||
89
internal/configuration/vpn.go
Normal file
89
internal/configuration/vpn.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
type VPN struct {
|
||||
Type string `json:"type"`
|
||||
OpenVPN OpenVPN `json:"openvpn"`
|
||||
Wireguard Wireguard `json:"wireguard"`
|
||||
Provider Provider `json:"provider"`
|
||||
}
|
||||
|
||||
func (settings *VPN) String() string {
|
||||
return strings.Join(settings.lines(), "\n")
|
||||
}
|
||||
|
||||
func (settings *VPN) lines() (lines []string) {
|
||||
lines = append(lines, lastIndent+"VPN:")
|
||||
|
||||
lines = append(lines, indent+lastIndent+"Type: "+settings.Type)
|
||||
|
||||
var vpnLines []string
|
||||
switch settings.Type {
|
||||
case constants.OpenVPN:
|
||||
vpnLines = settings.OpenVPN.lines()
|
||||
case constants.Wireguard:
|
||||
vpnLines = settings.Wireguard.lines()
|
||||
}
|
||||
for _, line := range vpnLines {
|
||||
lines = append(lines, indent+line)
|
||||
}
|
||||
|
||||
for _, line := range settings.Provider.lines() {
|
||||
lines = append(lines, indent+line)
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
var (
|
||||
errReadProviderSettings = errors.New("cannot read provider settings")
|
||||
errReadOpenVPNSettings = errors.New("cannot read OpenVPN settings")
|
||||
errReadWireguardSettings = errors.New("cannot read Wireguard settings")
|
||||
)
|
||||
|
||||
func (settings *VPN) read(r reader) (err error) {
|
||||
vpnType, err := r.env.Inside("VPN_TYPE",
|
||||
[]string{constants.OpenVPN, constants.Wireguard},
|
||||
params.Default(constants.OpenVPN))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable VPN_TYPE: %w", err)
|
||||
}
|
||||
settings.Type = vpnType
|
||||
|
||||
if !settings.isOpenVPNCustomConfig(r.env) {
|
||||
if err := settings.Provider.read(r, vpnType); err != nil {
|
||||
return fmt.Errorf("%w: %s", errReadProviderSettings, err)
|
||||
}
|
||||
}
|
||||
|
||||
switch settings.Type {
|
||||
case constants.OpenVPN:
|
||||
err = settings.OpenVPN.read(r, settings.Provider.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", errReadOpenVPNSettings, err)
|
||||
}
|
||||
case constants.Wireguard:
|
||||
err = settings.Wireguard.read(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", errReadWireguardSettings, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (settings VPN) isOpenVPNCustomConfig(env params.Interface) (ok bool) {
|
||||
if settings.Type != constants.OpenVPN {
|
||||
return false
|
||||
}
|
||||
s, err := env.Get("OPENVPN_CUSTOM_CONFIG")
|
||||
return err == nil && s != ""
|
||||
}
|
||||
@@ -7,57 +7,14 @@ import (
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
func (settings *Provider) vpnUnlimitedLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Countries) > 0 {
|
||||
lines = append(lines, lastIndent+"Countries: "+commaJoin(settings.ServerSelection.Countries))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Cities) > 0 {
|
||||
lines = append(lines, lastIndent+"Cities: "+commaJoin(settings.ServerSelection.Cities))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Hostnames) > 0 {
|
||||
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
|
||||
}
|
||||
|
||||
if settings.ServerSelection.FreeOnly {
|
||||
lines = append(lines, lastIndent+"Free servers only")
|
||||
}
|
||||
|
||||
if settings.ServerSelection.StreamOnly {
|
||||
lines = append(lines, lastIndent+"Stream servers only")
|
||||
}
|
||||
|
||||
if settings.ExtraConfigOptions.ClientKey != "" {
|
||||
lines = append(lines, lastIndent+"Client key is set")
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readVPNUnlimited(r reader) (err error) {
|
||||
settings.Name = constants.VPNUnlimited
|
||||
|
||||
settings.ServerSelection.TCP, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ExtraConfigOptions.ClientKey, err = readClientKey(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ExtraConfigOptions.ClientCertificate, err = readClientCertificate(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.VPNUnlimitedCountryChoices())
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable COUNTRY: %w", err)
|
||||
@@ -83,5 +40,19 @@ func (settings *Provider) readVPNUnlimited(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable STREAM_ONLY: %w", err)
|
||||
}
|
||||
|
||||
return settings.ServerSelection.OpenVPN.readProtocolOnly(r.env)
|
||||
}
|
||||
|
||||
func (settings *OpenVPN) readVPNUnlimited(r reader) (err error) {
|
||||
settings.ClientKey, err = readClientKey(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ClientCrt, err = readClientCertificate(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Provider_vpnUnlimitedLines(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
settings Provider
|
||||
lines []string
|
||||
}{
|
||||
"empty settings": {},
|
||||
"full settings": {
|
||||
settings: Provider{
|
||||
ServerSelection: ServerSelection{
|
||||
Countries: []string{"A", "B"},
|
||||
Cities: []string{"C", "D"},
|
||||
Hostnames: []string{"E", "F"},
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Countries: A, B",
|
||||
"|--Cities: C, D",
|
||||
"|--Hostnames: E, F",
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
lines := testCase.settings.vpnUnlimitedLines()
|
||||
|
||||
assert.Equal(t, testCase.lines, lines)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -6,22 +6,9 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
func (settings *Provider) vyprvpnLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Regions) > 0 {
|
||||
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readVyprvpn(r reader) (err error) {
|
||||
settings.Name = constants.Vyprvpn
|
||||
|
||||
settings.ServerSelection.TCP, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -2,39 +2,14 @@ package configuration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
func (settings *Provider) windscribeLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Regions) > 0 {
|
||||
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Cities) > 0 {
|
||||
lines = append(lines, lastIndent+"Cities: "+commaJoin(settings.ServerSelection.Cities))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Hostnames) > 0 {
|
||||
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
|
||||
}
|
||||
|
||||
if settings.ServerSelection.CustomPort > 0 {
|
||||
lines = append(lines, lastIndent+"Custom port: "+strconv.Itoa(int(settings.ServerSelection.CustomPort)))
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readWindscribe(r reader) (err error) {
|
||||
settings.Name = constants.Windscribe
|
||||
|
||||
settings.ServerSelection.TCP, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -55,7 +30,21 @@ func (settings *Provider) readWindscribe(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
||||
}
|
||||
|
||||
settings.ServerSelection.CustomPort, err = readCustomPort(r.env, settings.ServerSelection.TCP,
|
||||
err = settings.ServerSelection.OpenVPN.readWindscribe(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return settings.ServerSelection.Wireguard.readWindscribe(r.env)
|
||||
}
|
||||
|
||||
func (settings *OpenVPNSelection) readWindscribe(env params.Interface) (err error) {
|
||||
settings.TCP, err = readProtocol(env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.CustomPort, err = readOpenVPNCustomPort(env, settings.TCP,
|
||||
[]uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783},
|
||||
[]uint16{53, 80, 123, 443, 1194, 54783})
|
||||
if err != nil {
|
||||
@@ -64,3 +53,13 @@ func (settings *Provider) readWindscribe(r reader) (err error) {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (settings *WireguardSelection) readWindscribe(env params.Interface) (err error) {
|
||||
settings.CustomPort, err = readWireguardCustomPort(env,
|
||||
[]uint16{53, 80, 123, 443, 1194, 65142})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
73
internal/configuration/wireguard.go
Normal file
73
internal/configuration/wireguard.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
// Wireguard contains settings to configure the Wireguard client.
|
||||
type Wireguard struct {
|
||||
PrivateKey string `json:"privatekey"`
|
||||
PreSharedKey string `json:"presharedkey"`
|
||||
Address *net.IPNet `json:"address"`
|
||||
Interface string `json:"interface"`
|
||||
}
|
||||
|
||||
func (settings *Wireguard) String() string {
|
||||
return strings.Join(settings.lines(), "\n")
|
||||
}
|
||||
|
||||
func (settings *Wireguard) lines() (lines []string) {
|
||||
lines = append(lines, lastIndent+"Wireguard:")
|
||||
|
||||
lines = append(lines, indent+lastIndent+"Network interface: "+settings.Interface)
|
||||
|
||||
if settings.PrivateKey != "" {
|
||||
lines = append(lines, indent+lastIndent+"Private key is set")
|
||||
}
|
||||
|
||||
if settings.PreSharedKey != "" {
|
||||
lines = append(lines, indent+lastIndent+"Pre-shared key is set")
|
||||
}
|
||||
|
||||
if settings.Address != nil {
|
||||
lines = append(lines, indent+lastIndent+"Address: "+settings.Address.String())
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Wireguard) read(r reader) (err error) {
|
||||
settings.PrivateKey, err = r.env.Get("WIREGUARD_PRIVATE_KEY",
|
||||
params.CaseSensitiveValue(), params.Unset(), params.Compulsory())
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable WIREGUARD_PRIVATE_KEY: %w", err)
|
||||
}
|
||||
|
||||
settings.PreSharedKey, err = r.env.Get("WIREGUARD_PRESHARED_KEY",
|
||||
params.CaseSensitiveValue(), params.Unset())
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable WIREGUARD_PRESHARED_KEY: %w", err)
|
||||
}
|
||||
|
||||
addressString, err := r.env.Get("WIREGUARD_ADDRESS", params.Compulsory())
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable WIREGUARD_ADDRESS: %w", err)
|
||||
}
|
||||
ip, ipNet, err := net.ParseCIDR(addressString)
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable WIREGUARD_ADDRESS: %w", err)
|
||||
}
|
||||
ipNet.IP = ip
|
||||
settings.Address = ipNet
|
||||
|
||||
settings.Interface, err = r.env.Get("WIREGUARD_INTERFACE", params.Default("wg0"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable WIREGUARD_INTERFACE: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package constants
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
@@ -20,11 +22,20 @@ func CyberghostRegionChoices() (choices []string) {
|
||||
|
||||
func CyberghostGroupChoices() (choices []string) {
|
||||
servers := CyberghostServers()
|
||||
choices = make([]string, len(servers))
|
||||
for i := range servers {
|
||||
choices[i] = servers[i].Group
|
||||
uniqueChoices := map[string]struct{}{}
|
||||
for _, server := range servers {
|
||||
uniqueChoices[server.Group] = struct{}{}
|
||||
}
|
||||
return makeUnique(choices)
|
||||
|
||||
choices = make([]string, 0, len(uniqueChoices))
|
||||
for choice := range uniqueChoices {
|
||||
choices = append(choices, choice)
|
||||
}
|
||||
|
||||
sortable := sort.StringSlice(choices)
|
||||
sortable.Sort()
|
||||
|
||||
return sortable
|
||||
}
|
||||
|
||||
func CyberghostHostnameChoices() (choices []string) {
|
||||
|
||||
18
internal/constants/cyberghost_test.go
Normal file
18
internal/constants/cyberghost_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package constants
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_CyberghostGroupChoices(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
expected := []string{"Premium TCP Asia", "Premium TCP Europe",
|
||||
"Premium TCP USA", "Premium UDP Asia", "Premium UDP Europe",
|
||||
"Premium UDP USA"}
|
||||
choices := CyberghostGroupChoices()
|
||||
|
||||
assert.Equal(t, expected, choices)
|
||||
}
|
||||
@@ -1,10 +1,5 @@
|
||||
package constants
|
||||
|
||||
const (
|
||||
TUN = "tun0"
|
||||
TAP = "tap0"
|
||||
)
|
||||
|
||||
const (
|
||||
AES128cbc = "aes-128-cbc"
|
||||
AES256cbc = "aes-256-cbc"
|
||||
|
||||
@@ -48,3 +48,12 @@ func PIAServers() (servers []models.PIAServer) {
|
||||
copy(servers, allServers.Pia.Servers)
|
||||
return servers
|
||||
}
|
||||
|
||||
func PIAServerWhereName(serverName string) (server models.PIAServer) {
|
||||
for _, server := range PIAServers() {
|
||||
if server.ServerName == serverName {
|
||||
return server
|
||||
}
|
||||
}
|
||||
return server
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -73,7 +73,7 @@ func Test_versions(t *testing.T) {
|
||||
"Mullvad": {
|
||||
model: models.MullvadServer{},
|
||||
version: allServers.Mullvad.Version,
|
||||
digest: "2a009192",
|
||||
digest: "ec56f19d",
|
||||
},
|
||||
"Nordvpn": {
|
||||
model: models.NordvpnServer{},
|
||||
@@ -128,7 +128,7 @@ func Test_versions(t *testing.T) {
|
||||
"Windscribe": {
|
||||
model: models.WindscribeServer{},
|
||||
version: allServers.Windscribe.Version,
|
||||
digest: "6f6c16d6",
|
||||
digest: "4bd0fc4f",
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package constants
|
||||
|
||||
const (
|
||||
OpenVPN = "openvpn"
|
||||
Wireguard = "wireguard"
|
||||
)
|
||||
|
||||
const (
|
||||
// Cyberghost is a VPN provider.
|
||||
Cyberghost = "cyberghost"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -8,18 +9,24 @@ import (
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
func (l *Loop) collectLines(stdout, stderr <-chan string, done chan<- struct{}) {
|
||||
func (l *Loop) collectLines(ctx context.Context, done chan<- struct{},
|
||||
stdout, stderr chan string) {
|
||||
defer close(done)
|
||||
|
||||
var line string
|
||||
var ok bool
|
||||
|
||||
for {
|
||||
select {
|
||||
case line, ok = <-stderr:
|
||||
case line, ok = <-stdout:
|
||||
}
|
||||
if !ok {
|
||||
case <-ctx.Done():
|
||||
// Context should only be canceled after stdout and stderr are done
|
||||
// being written to.
|
||||
close(stdout)
|
||||
close(stderr)
|
||||
return
|
||||
case line = <-stderr:
|
||||
case line = <-stdout:
|
||||
}
|
||||
|
||||
line, level := processLogLine(line)
|
||||
switch level {
|
||||
case logging.LevelDebug:
|
||||
|
||||
@@ -30,12 +30,13 @@ func (l *Loop) setupUnbound(ctx context.Context) (
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
collectLinesDone := make(chan struct{})
|
||||
go l.collectLines(stdoutLines, stderrLines, collectLinesDone)
|
||||
linesCollectionCtx, linesCollectionCancel := context.WithCancel(context.Background())
|
||||
lineCollectionDone := make(chan struct{})
|
||||
go l.collectLines(linesCollectionCtx, lineCollectionDone,
|
||||
stdoutLines, stderrLines)
|
||||
closeStreams = func() {
|
||||
close(stdoutLines)
|
||||
close(stderrLines)
|
||||
<-collectLinesDone
|
||||
linesCollectionCancel()
|
||||
<-lineCollectionDone
|
||||
}
|
||||
|
||||
// use Unbound
|
||||
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -109,10 +107,10 @@ func (c *Config) enable(ctx context.Context) (err error) {
|
||||
if err = c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, c.vpnConnection, remove); err != nil {
|
||||
return fmt.Errorf("cannot enable firewall: %w", err)
|
||||
}
|
||||
}
|
||||
if err = c.acceptOutputThroughInterface(ctx, string(constants.TUN), remove); err != nil {
|
||||
if err = c.acceptOutputThroughInterface(ctx, c.vpnIntf, remove); err != nil {
|
||||
return fmt.Errorf("cannot enable firewall: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, network := range c.localNetworks {
|
||||
if err := c.acceptOutputFromIPToSubnet(ctx, network.InterfaceName, network.IP, *network.IPNet, remove); err != nil {
|
||||
|
||||
@@ -39,7 +39,8 @@ type Config struct { //nolint:maligned
|
||||
|
||||
// State
|
||||
enabled bool
|
||||
vpnConnection models.OpenVPNConnection
|
||||
vpnConnection models.Connection
|
||||
vpnIntf string
|
||||
outboundSubnets []net.IPNet
|
||||
allowedInputPorts map[uint16]string // port to interface mapping
|
||||
stateMutex sync.Mutex
|
||||
|
||||
@@ -150,7 +150,7 @@ func (c *Config) acceptEstablishedRelatedTraffic(ctx context.Context, remove boo
|
||||
}
|
||||
|
||||
func (c *Config) acceptOutputTrafficToVPN(ctx context.Context,
|
||||
defaultInterface string, connection models.OpenVPNConnection, remove bool) error {
|
||||
defaultInterface string, connection models.Connection, remove bool) error {
|
||||
instruction := fmt.Sprintf("%s OUTPUT -d %s -o %s -p %s -m %s --dport %d -j ACCEPT",
|
||||
appendOrDelete(remove), connection.IP, defaultInterface, connection.Protocol,
|
||||
connection.Protocol, connection.Port)
|
||||
|
||||
@@ -8,10 +8,12 @@ import (
|
||||
)
|
||||
|
||||
type VPNConnectionSetter interface {
|
||||
SetVPNConnection(ctx context.Context, connection models.OpenVPNConnection) error
|
||||
SetVPNConnection(ctx context.Context,
|
||||
connection models.Connection, vpnIntf string) error
|
||||
}
|
||||
|
||||
func (c *Config) SetVPNConnection(ctx context.Context, connection models.OpenVPNConnection) (err error) {
|
||||
func (c *Config) SetVPNConnection(ctx context.Context,
|
||||
connection models.Connection, vpnIntf string) (err error) {
|
||||
c.stateMutex.Lock()
|
||||
defer c.stateMutex.Unlock()
|
||||
|
||||
@@ -33,11 +35,26 @@ func (c *Config) SetVPNConnection(ctx context.Context, connection models.OpenVPN
|
||||
c.logger.Error("cannot remove outdated VPN connection through firewall: " + err.Error())
|
||||
}
|
||||
}
|
||||
c.vpnConnection = models.OpenVPNConnection{}
|
||||
c.vpnConnection = models.Connection{}
|
||||
|
||||
if c.vpnIntf != "" {
|
||||
if err = c.acceptOutputThroughInterface(ctx, c.vpnIntf, remove); err != nil {
|
||||
c.logger.Error("cannot remove outdated VPN interface from firewall: " + err.Error())
|
||||
}
|
||||
}
|
||||
c.vpnIntf = ""
|
||||
|
||||
remove = false
|
||||
|
||||
if err := c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, connection, remove); err != nil {
|
||||
return fmt.Errorf("cannot set VPN connection through firewall: %w", err)
|
||||
}
|
||||
c.vpnConnection = connection
|
||||
|
||||
if err = c.acceptOutputThroughInterface(ctx, vpnIntf, remove); err != nil {
|
||||
return fmt.Errorf("cannot accept output traffic through interface %s: %w", vpnIntf, err)
|
||||
}
|
||||
c.vpnIntf = vpnIntf
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
func (s *Server) runHealthcheckLoop(ctx context.Context, done chan<- struct{}) {
|
||||
defer close(done)
|
||||
|
||||
s.openvpn.healthyTimer = time.NewTimer(s.openvpn.healthyWait)
|
||||
s.vpn.healthyTimer = time.NewTimer(s.vpn.healthyWait)
|
||||
|
||||
for {
|
||||
previousErr := s.handler.getErr()
|
||||
@@ -22,12 +22,12 @@ func (s *Server) runHealthcheckLoop(ctx context.Context, done chan<- struct{}) {
|
||||
|
||||
if previousErr != nil && err == nil {
|
||||
s.logger.Info("healthy!")
|
||||
s.openvpn.healthyTimer.Stop()
|
||||
s.openvpn.healthyWait = s.config.OpenVPN.Initial
|
||||
s.vpn.healthyTimer.Stop()
|
||||
s.vpn.healthyWait = s.config.OpenVPN.Initial
|
||||
} else if previousErr == nil && err != nil {
|
||||
s.logger.Info("unhealthy: " + err.Error())
|
||||
s.openvpn.healthyTimer.Stop()
|
||||
s.openvpn.healthyTimer = time.NewTimer(s.openvpn.healthyWait)
|
||||
s.vpn.healthyTimer.Stop()
|
||||
s.vpn.healthyTimer = time.NewTimer(s.vpn.healthyWait)
|
||||
}
|
||||
|
||||
if err != nil { // try again after 1 second
|
||||
@@ -39,7 +39,7 @@ func (s *Server) runHealthcheckLoop(ctx context.Context, done chan<- struct{}) {
|
||||
}
|
||||
return
|
||||
case <-timer.C:
|
||||
case <-s.openvpn.healthyTimer.C:
|
||||
case <-s.vpn.healthyTimer.C:
|
||||
s.onUnhealthyOpenvpn(ctx)
|
||||
}
|
||||
continue
|
||||
|
||||
@@ -5,20 +5,20 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/openvpn"
|
||||
"github.com/qdm12/gluetun/internal/vpn"
|
||||
)
|
||||
|
||||
type openvpnHealth struct {
|
||||
looper openvpn.Looper
|
||||
type vpnHealth struct {
|
||||
looper vpn.Looper
|
||||
healthyWait time.Duration
|
||||
healthyTimer *time.Timer
|
||||
}
|
||||
|
||||
func (s *Server) onUnhealthyOpenvpn(ctx context.Context) {
|
||||
s.logger.Info("program has been unhealthy for " +
|
||||
s.openvpn.healthyWait.String() + ": restarting OpenVPN")
|
||||
_, _ = s.openvpn.looper.ApplyStatus(ctx, constants.Stopped)
|
||||
_, _ = s.openvpn.looper.ApplyStatus(ctx, constants.Running)
|
||||
s.openvpn.healthyWait += s.config.OpenVPN.Addition
|
||||
s.openvpn.healthyTimer = time.NewTimer(s.openvpn.healthyWait)
|
||||
s.vpn.healthyWait.String() + ": restarting OpenVPN")
|
||||
_, _ = s.vpn.looper.ApplyStatus(ctx, constants.Stopped)
|
||||
_, _ = s.vpn.looper.ApplyStatus(ctx, constants.Running)
|
||||
s.vpn.healthyWait += s.config.OpenVPN.Addition
|
||||
s.vpn.healthyTimer = time.NewTimer(s.vpn.healthyWait)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration"
|
||||
"github.com/qdm12/gluetun/internal/openvpn"
|
||||
"github.com/qdm12/gluetun/internal/vpn"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
@@ -20,18 +20,18 @@ type Server struct {
|
||||
handler *handler
|
||||
resolver *net.Resolver
|
||||
config configuration.Health
|
||||
openvpn openvpnHealth
|
||||
vpn vpnHealth
|
||||
}
|
||||
|
||||
func NewServer(config configuration.Health,
|
||||
logger logging.Logger, openvpnLooper openvpn.Looper) *Server {
|
||||
logger logging.Logger, vpnLooper vpn.Looper) *Server {
|
||||
return &Server{
|
||||
logger: logger,
|
||||
handler: newHandler(logger),
|
||||
resolver: net.DefaultResolver,
|
||||
config: config,
|
||||
openvpn: openvpnHealth{
|
||||
looper: openvpnLooper,
|
||||
vpn: vpnHealth{
|
||||
looper: vpnLooper,
|
||||
healthyWait: config.OpenVPN.Initial,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ func (s *State) ApplyStatus(ctx context.Context, status models.LoopStatus) (
|
||||
switch existingStatus {
|
||||
case constants.Stopped, constants.Completed:
|
||||
default:
|
||||
s.statusMu.Unlock()
|
||||
return "already " + existingStatus.String(), nil
|
||||
}
|
||||
|
||||
@@ -50,11 +51,13 @@ func (s *State) ApplyStatus(ctx context.Context, status models.LoopStatus) (
|
||||
case <-ctx.Done():
|
||||
case newStatus = <-s.running:
|
||||
}
|
||||
|
||||
s.SetStatus(newStatus)
|
||||
|
||||
return newStatus.String(), nil
|
||||
case constants.Stopped:
|
||||
if existingStatus != constants.Running {
|
||||
s.statusMu.Unlock()
|
||||
return "already " + existingStatus.String(), nil
|
||||
}
|
||||
|
||||
@@ -73,6 +76,7 @@ func (s *State) ApplyStatus(ctx context.Context, status models.LoopStatus) (
|
||||
|
||||
return newStatus.String(), nil
|
||||
default:
|
||||
s.statusMu.Unlock()
|
||||
return "", fmt.Errorf("%w: %s: it can only be one of: %s, %s",
|
||||
ErrInvalidStatus, status, constants.Running, constants.Stopped)
|
||||
}
|
||||
|
||||
51
internal/models/connection.go
Normal file
51
internal/models/connection.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
type Connection struct {
|
||||
// Type is the connection type and can be "openvpn" or "wireguard"
|
||||
Type string `json:"type"`
|
||||
// IP is the VPN server IP address.
|
||||
IP net.IP `json:"ip"`
|
||||
// Port is the VPN server port.
|
||||
Port uint16 `json:"port"`
|
||||
// Protocol can be "tcp" or "udp".
|
||||
Protocol string `json:"protocol"`
|
||||
// Hostname is used for IPVanish, IVPN, Privado
|
||||
// and Windscribe for TLS verification.
|
||||
Hostname string `json:"hostname"`
|
||||
// PubKey is the public key of the VPN server,
|
||||
// used only for Wireguard.
|
||||
PubKey string `json:"pubkey"`
|
||||
}
|
||||
|
||||
func (c *Connection) Equal(other Connection) bool {
|
||||
return c.IP.Equal(other.IP) && c.Port == other.Port &&
|
||||
c.Protocol == other.Protocol && c.Hostname == other.Hostname &&
|
||||
c.PubKey == other.PubKey
|
||||
}
|
||||
|
||||
func (c Connection) OpenVPNRemoteLine() (line string) {
|
||||
return "remote " + c.IP.String() + " " + fmt.Sprint(c.Port)
|
||||
}
|
||||
|
||||
func (c Connection) OpenVPNProtoLine() (line string) {
|
||||
return "proto " + c.Protocol
|
||||
}
|
||||
|
||||
// UpdateEmptyWith updates each field of the connection where the
|
||||
// value is not set using the value given as arguments.
|
||||
func (c *Connection) UpdateEmptyWith(ip net.IP, port uint16, protocol string) {
|
||||
if c.IP == nil {
|
||||
c.IP = ip
|
||||
}
|
||||
if c.Port == 0 {
|
||||
c.Port = port
|
||||
}
|
||||
if c.Protocol == "" {
|
||||
c.Protocol = protocol
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type OpenVPNConnection struct {
|
||||
IP net.IP `json:"ip"`
|
||||
Port uint16 `json:"port"`
|
||||
Protocol string `json:"protocol"`
|
||||
// Hostname is used for IPVanish, IVPN, Privado
|
||||
// and Windscribe for TLS verification
|
||||
Hostname string `json:"hostname"`
|
||||
}
|
||||
|
||||
func (o *OpenVPNConnection) Equal(other OpenVPNConnection) bool {
|
||||
return o.IP.Equal(other.IP) && o.Port == other.Port && o.Protocol == other.Protocol &&
|
||||
o.Hostname == other.Hostname
|
||||
}
|
||||
|
||||
func (o OpenVPNConnection) RemoteLine() (line string) {
|
||||
return "remote " + o.IP.String() + " " + strconv.Itoa(int(o.Port))
|
||||
}
|
||||
|
||||
func (o OpenVPNConnection) ProtoLine() (line string) {
|
||||
return "proto " + o.Protocol
|
||||
}
|
||||
@@ -48,6 +48,7 @@ type IvpnServer struct {
|
||||
}
|
||||
|
||||
type MullvadServer struct {
|
||||
VPN string `json:"vpn"`
|
||||
IPs []net.IP `json:"ips"`
|
||||
IPsV6 []net.IP `json:"ipsv6"`
|
||||
Country string `json:"country"`
|
||||
@@ -55,6 +56,7 @@ type MullvadServer struct {
|
||||
Hostname string `json:"hostname"`
|
||||
ISP string `json:"isp"`
|
||||
Owned bool `json:"owned"`
|
||||
WgPubKey string `json:"wgpubkey,omitempty"`
|
||||
}
|
||||
|
||||
type NordvpnServer struct { //nolint:maligned
|
||||
@@ -149,9 +151,11 @@ type VyprvpnServer struct {
|
||||
}
|
||||
|
||||
type WindscribeServer struct {
|
||||
VPN string `json:"vpn"`
|
||||
Region string `json:"region"`
|
||||
City string `json:"city"`
|
||||
Hostname string `json:"hostname"`
|
||||
OvpnX509 string `json:"x509"`
|
||||
OvpnX509 string `json:"x509,omitempty"`
|
||||
WgPubKey string `json:"wgpubkey,omitempty"`
|
||||
IPs []net.IP `json:"ips"`
|
||||
}
|
||||
|
||||
7
internal/netlink/address.go
Normal file
7
internal/netlink/address.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package netlink
|
||||
|
||||
import "github.com/vishvananda/netlink"
|
||||
|
||||
func (n *NetLink) AddrAdd(link netlink.Link, addr *netlink.Addr) error {
|
||||
return netlink.AddrAdd(link, addr)
|
||||
}
|
||||
14
internal/netlink/interface.go
Normal file
14
internal/netlink/interface.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package netlink
|
||||
|
||||
import "github.com/vishvananda/netlink"
|
||||
|
||||
//go:generate mockgen -destination=mock_$GOPACKAGE/$GOFILE . NetLinker
|
||||
|
||||
var _ NetLinker = (*NetLink)(nil)
|
||||
|
||||
type NetLinker interface {
|
||||
AddrAdd(link netlink.Link, addr *netlink.Addr) error
|
||||
RouteAdd(route *netlink.Route) error
|
||||
RuleAdd(rule *netlink.Rule) error
|
||||
RuleDel(rule *netlink.Rule) error
|
||||
}
|
||||
7
internal/netlink/netlink.go
Normal file
7
internal/netlink/netlink.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package netlink
|
||||
|
||||
type NetLink struct{}
|
||||
|
||||
func New() *NetLink {
|
||||
return &NetLink{}
|
||||
}
|
||||
7
internal/netlink/route.go
Normal file
7
internal/netlink/route.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package netlink
|
||||
|
||||
import "github.com/vishvananda/netlink"
|
||||
|
||||
func (n *NetLink) RouteAdd(route *netlink.Route) error {
|
||||
return netlink.RouteAdd(route)
|
||||
}
|
||||
11
internal/netlink/rule.go
Normal file
11
internal/netlink/rule.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package netlink
|
||||
|
||||
import "github.com/vishvananda/netlink"
|
||||
|
||||
func (n *NetLink) RuleAdd(rule *netlink.Rule) error {
|
||||
return netlink.RuleAdd(rule)
|
||||
}
|
||||
|
||||
func (n *NetLink) RuleDel(rule *netlink.Rule) error {
|
||||
return netlink.RuleDel(rule)
|
||||
}
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
)
|
||||
|
||||
type AuthWriter interface {
|
||||
WriteAuthFile(user, password string, puid, pgid int) error
|
||||
WriteAuthFile(user, password string) error
|
||||
}
|
||||
|
||||
// WriteAuthFile writes the OpenVPN auth file to disk with the right permissions.
|
||||
func (c *configurator) WriteAuthFile(user, password string, puid, pgid int) error {
|
||||
func (c *Configurator) WriteAuthFile(user, password string) error {
|
||||
file, err := os.Open(c.authFilePath)
|
||||
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
@@ -28,7 +28,7 @@ func (c *configurator) WriteAuthFile(user, password string, puid, pgid int) erro
|
||||
_ = file.Close()
|
||||
return err
|
||||
}
|
||||
err = file.Chown(puid, pgid)
|
||||
err = file.Chown(c.puid, c.pgid)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return err
|
||||
@@ -60,7 +60,7 @@ func (c *configurator) WriteAuthFile(user, password string, puid, pgid int) erro
|
||||
_ = file.Close()
|
||||
return err
|
||||
}
|
||||
err = file.Chown(puid, pgid)
|
||||
err = file.Chown(c.puid, c.pgid)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return err
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
package openvpn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
var ErrVersionUnknown = errors.New("OpenVPN version is unknown")
|
||||
|
||||
const (
|
||||
binOpenvpn24 = "openvpn2.4"
|
||||
binOpenvpn25 = "openvpn"
|
||||
)
|
||||
|
||||
type Starter interface {
|
||||
Start(ctx context.Context, version string, flags []string) (
|
||||
stdoutLines, stderrLines chan string, waitError chan error, err error)
|
||||
}
|
||||
|
||||
func (c *configurator) Start(ctx context.Context, version string, flags []string) (
|
||||
stdoutLines, stderrLines chan string, waitError chan error, err error) {
|
||||
var bin string
|
||||
switch version {
|
||||
case constants.Openvpn24:
|
||||
bin = binOpenvpn24
|
||||
case constants.Openvpn25:
|
||||
bin = binOpenvpn25
|
||||
default:
|
||||
return nil, nil, nil, fmt.Errorf("%w: %s", ErrVersionUnknown, version)
|
||||
}
|
||||
|
||||
c.logger.Info("starting OpenVPN " + version)
|
||||
|
||||
args := []string{"--config", constants.OpenVPNConf}
|
||||
args = append(args, flags...)
|
||||
cmd := exec.CommandContext(ctx, bin, args...)
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
|
||||
return c.cmder.Start(cmd)
|
||||
}
|
||||
|
||||
type VersionGetter interface {
|
||||
Version24(ctx context.Context) (version string, err error)
|
||||
Version25(ctx context.Context) (version string, err error)
|
||||
}
|
||||
|
||||
func (c *configurator) Version24(ctx context.Context) (version string, err error) {
|
||||
return c.version(ctx, binOpenvpn24)
|
||||
}
|
||||
|
||||
func (c *configurator) Version25(ctx context.Context) (version string, err error) {
|
||||
return c.version(ctx, binOpenvpn25)
|
||||
}
|
||||
|
||||
var ErrVersionTooShort = errors.New("version output is too short")
|
||||
|
||||
func (c *configurator) version(ctx context.Context, binName string) (version string, err error) {
|
||||
cmd := exec.CommandContext(ctx, binName, "--version")
|
||||
output, err := c.cmder.Run(cmd)
|
||||
if err != nil && err.Error() != "exit status 1" {
|
||||
return "", err
|
||||
}
|
||||
firstLine := strings.Split(output, "\n")[0]
|
||||
words := strings.Fields(firstLine)
|
||||
const minWords = 2
|
||||
if len(words) < minWords {
|
||||
return "", fmt.Errorf("%w: %s", ErrVersionTooShort, firstLine)
|
||||
}
|
||||
return words[1], nil
|
||||
}
|
||||
@@ -5,14 +5,26 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (l *Loop) writeOpenvpnConf(lines []string) error {
|
||||
file, err := os.OpenFile(l.targetConfPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
||||
type Writer interface {
|
||||
WriteConfig(lines []string) error
|
||||
}
|
||||
|
||||
func (c *Configurator) WriteConfig(lines []string) error {
|
||||
file, err := os.OpenFile(c.configPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = file.WriteString(strings.Join(lines, "\n"))
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
err = file.Chown(c.puid, c.pgid)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
return file.Close()
|
||||
}
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
package openvpn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||
)
|
||||
|
||||
var errProcessCustomConfig = errors.New("cannot process custom config")
|
||||
|
||||
func (l *Loop) processCustomConfig(settings configuration.OpenVPN) (
|
||||
lines []string, connection models.OpenVPNConnection, err error) {
|
||||
lines, err = readCustomConfigLines(settings.Config)
|
||||
if err != nil {
|
||||
return nil, connection, fmt.Errorf("%w: %s", errProcessCustomConfig, err)
|
||||
}
|
||||
|
||||
lines = modifyCustomConfig(lines, l.username, settings)
|
||||
|
||||
connection, err = extractConnectionFromLines(lines)
|
||||
if err != nil {
|
||||
return nil, connection, fmt.Errorf("%w: %s", errProcessCustomConfig, err)
|
||||
}
|
||||
|
||||
lines = setConnectionToLines(lines, connection)
|
||||
return lines, connection, nil
|
||||
}
|
||||
|
||||
func readCustomConfigLines(filepath string) (
|
||||
lines []string, err error) {
|
||||
file, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
b, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := file.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return strings.Split(string(b), "\n"), nil
|
||||
}
|
||||
|
||||
func modifyCustomConfig(lines []string, username string,
|
||||
settings configuration.OpenVPN) (modified []string) {
|
||||
// Remove some lines
|
||||
for _, line := range lines {
|
||||
switch {
|
||||
case strings.HasPrefix(line, "up "),
|
||||
strings.HasPrefix(line, "down "),
|
||||
strings.HasPrefix(line, "verb "),
|
||||
strings.HasPrefix(line, "auth-user-pass "),
|
||||
len(settings.Cipher) > 0 && strings.HasPrefix(line, "cipher "),
|
||||
len(settings.Cipher) > 0 && strings.HasPrefix(line, "data-ciphers"),
|
||||
len(settings.Auth) > 0 && strings.HasPrefix(line, "auth "),
|
||||
settings.MSSFix > 0 && strings.HasPrefix(line, "mssfix "),
|
||||
!settings.Provider.ExtraConfigOptions.OpenVPNIPv6 && strings.HasPrefix(line, "tun-ipv6"):
|
||||
default:
|
||||
modified = append(modified, line)
|
||||
}
|
||||
}
|
||||
|
||||
// Add values
|
||||
modified = append(modified, "mute-replay-warnings")
|
||||
modified = append(modified, "auth-nocache")
|
||||
modified = append(modified, "pull-filter ignore \"auth-token\"") // prevent auth failed loop
|
||||
modified = append(modified, "auth-retry nointeract")
|
||||
modified = append(modified, "suppress-timestamps")
|
||||
if settings.User != "" {
|
||||
modified = append(modified, "auth-user-pass "+constants.OpenVPNAuthConf)
|
||||
}
|
||||
modified = append(modified, "verb "+strconv.Itoa(settings.Verbosity))
|
||||
if len(settings.Cipher) > 0 {
|
||||
modified = append(modified, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||
}
|
||||
if len(settings.Auth) > 0 {
|
||||
modified = append(modified, "auth "+settings.Auth)
|
||||
}
|
||||
if settings.MSSFix > 0 {
|
||||
modified = append(modified, "mssfix "+strconv.Itoa(int(settings.MSSFix)))
|
||||
}
|
||||
if !settings.Provider.ExtraConfigOptions.OpenVPNIPv6 {
|
||||
modified = append(modified, `pull-filter ignore "route-ipv6"`)
|
||||
modified = append(modified, `pull-filter ignore "ifconfig-ipv6"`)
|
||||
}
|
||||
if !settings.Root {
|
||||
modified = append(modified, "user "+username)
|
||||
}
|
||||
|
||||
return modified
|
||||
}
|
||||
|
||||
var errExtractConnection = errors.New("cannot extract connection")
|
||||
|
||||
// extractConnectionFromLines always takes the first remote line only.
|
||||
func extractConnectionFromLines(lines []string) ( //nolint:gocognit
|
||||
connection models.OpenVPNConnection, err error) {
|
||||
for _, line := range lines {
|
||||
switch {
|
||||
case strings.HasPrefix(line, "proto "):
|
||||
fields := strings.Fields(line)
|
||||
if n := len(fields); n != 2 { //nolint:gomnd
|
||||
return connection, fmt.Errorf(
|
||||
"%w: proto line has %d fields instead of 2: %s",
|
||||
errExtractConnection, n, line)
|
||||
}
|
||||
connection.Protocol = fields[1]
|
||||
|
||||
// only take the first remote line
|
||||
case strings.HasPrefix(line, "remote ") && connection.IP == nil:
|
||||
fields := strings.Fields(line)
|
||||
n := len(fields)
|
||||
//nolint:gomnd
|
||||
if n < 2 {
|
||||
return connection, fmt.Errorf(
|
||||
"%w: remote line has not enough fields: %s",
|
||||
errExtractConnection, line)
|
||||
}
|
||||
|
||||
host := fields[1]
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
connection.IP = ip
|
||||
} else {
|
||||
return connection, fmt.Errorf(
|
||||
"%w: for now, the remote line must contain an IP adddress: %s",
|
||||
errExtractConnection, line)
|
||||
// TODO resolve hostname once there is an option to allow it through
|
||||
// the firewall before the VPN is up.
|
||||
}
|
||||
|
||||
if n > 2 { //nolint:gomnd
|
||||
port, err := strconv.Atoi(fields[2])
|
||||
if err != nil {
|
||||
return connection, fmt.Errorf(
|
||||
"%w: remote line has an invalid port: %s",
|
||||
errExtractConnection, line)
|
||||
}
|
||||
connection.Port = uint16(port)
|
||||
}
|
||||
|
||||
if n > 3 { //nolint:gomnd
|
||||
connection.Protocol = strings.ToLower(fields[3])
|
||||
}
|
||||
|
||||
if n > 4 { //nolint:gomnd
|
||||
return connection, fmt.Errorf(
|
||||
"%w: remote line has too many fields: %s",
|
||||
errExtractConnection, line)
|
||||
}
|
||||
}
|
||||
|
||||
if connection.Protocol != "" && connection.IP != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if connection.IP == nil {
|
||||
return connection, fmt.Errorf("%w: remote line not found", errExtractConnection)
|
||||
}
|
||||
|
||||
switch connection.Protocol {
|
||||
case "":
|
||||
connection.Protocol = "udp"
|
||||
case "tcp", "udp":
|
||||
default:
|
||||
return connection, fmt.Errorf("%w: network protocol not supported: %s", errExtractConnection, connection.Protocol)
|
||||
}
|
||||
|
||||
if connection.Port == 0 {
|
||||
if connection.Protocol == "tcp" {
|
||||
const defaultPort uint16 = 443
|
||||
connection.Port = defaultPort
|
||||
} else {
|
||||
const defaultPort uint16 = 1194
|
||||
connection.Port = defaultPort
|
||||
}
|
||||
}
|
||||
|
||||
return connection, nil
|
||||
}
|
||||
|
||||
func setConnectionToLines(lines []string, connection models.OpenVPNConnection) (modified []string) {
|
||||
for i, line := range lines {
|
||||
switch {
|
||||
case strings.HasPrefix(line, "proto "):
|
||||
lines[i] = connection.ProtoLine()
|
||||
|
||||
case strings.HasPrefix(line, "remote "):
|
||||
lines[i] = connection.RemoteLine()
|
||||
}
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
35
internal/openvpn/custom/custom.go
Normal file
35
internal/openvpn/custom/custom.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrReadCustomConfig = errors.New("cannot read custom configuration file")
|
||||
ErrExtractConnection = errors.New("cannot extract connection from custom configuration file")
|
||||
)
|
||||
|
||||
func BuildConfig(settings configuration.OpenVPN) (
|
||||
lines []string, connection models.Connection, intf string, err error) {
|
||||
lines, err = readCustomConfigLines(settings.Config)
|
||||
if err != nil {
|
||||
return nil, connection, "", fmt.Errorf("%w: %s", ErrReadCustomConfig, err)
|
||||
}
|
||||
|
||||
connection, intf, err = extractDataFromLines(lines)
|
||||
if err != nil {
|
||||
return nil, connection, "", fmt.Errorf("%w: %s", ErrExtractConnection, err)
|
||||
}
|
||||
|
||||
if intf == "" {
|
||||
intf = settings.Interface
|
||||
}
|
||||
|
||||
lines = modifyCustomConfig(lines, settings, connection, intf)
|
||||
|
||||
return lines, connection, intf, nil
|
||||
}
|
||||
67
internal/openvpn/custom/custom_test.go
Normal file
67
internal/openvpn/custom/custom_test.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_BuildConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
file, err := os.CreateTemp("", "")
|
||||
require.NoError(t, err)
|
||||
defer removeFile(t, file.Name())
|
||||
defer file.Close()
|
||||
|
||||
_, err = file.WriteString("remote 1.9.8.7\nkeep me\ncipher remove")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = file.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
settings := configuration.OpenVPN{
|
||||
Cipher: "cipher",
|
||||
MSSFix: 999,
|
||||
Config: file.Name(),
|
||||
Interface: "tun0",
|
||||
}
|
||||
|
||||
lines, connection, intf, err := BuildConfig(settings)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedLines := []string{
|
||||
"keep me",
|
||||
"proto udp",
|
||||
"remote 1.9.8.7 1194",
|
||||
"dev tun0",
|
||||
"mute-replay-warnings",
|
||||
"auth-nocache",
|
||||
"pull-filter ignore \"auth-token\"",
|
||||
"auth-retry nointeract",
|
||||
"suppress-timestamps",
|
||||
"verb 0",
|
||||
"data-ciphers-fallback cipher",
|
||||
"data-ciphers cipher",
|
||||
"mssfix 999",
|
||||
"pull-filter ignore \"route-ipv6\"",
|
||||
"pull-filter ignore \"ifconfig-ipv6\"",
|
||||
"user ",
|
||||
}
|
||||
assert.Equal(t, expectedLines, lines)
|
||||
|
||||
expectedConnection := models.Connection{
|
||||
IP: net.IPv4(1, 9, 8, 7),
|
||||
Port: 1194,
|
||||
Protocol: constants.UDP,
|
||||
}
|
||||
assert.Equal(t, expectedConnection, connection)
|
||||
|
||||
assert.Equal(t, "tun0", intf)
|
||||
}
|
||||
162
internal/openvpn/custom/extract.go
Normal file
162
internal/openvpn/custom/extract.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
var (
|
||||
errRemoteLineNotFound = errors.New("remote line not found")
|
||||
)
|
||||
|
||||
func extractDataFromLines(lines []string) (
|
||||
connection models.Connection, intf string, err error) {
|
||||
for i, line := range lines {
|
||||
ip, port, protocol, intfFound, err := extractDataFromLine(line)
|
||||
if err != nil {
|
||||
return connection, "", fmt.Errorf("on line %d: %w", i+1, err)
|
||||
}
|
||||
|
||||
intf = intfFound
|
||||
connection.UpdateEmptyWith(ip, port, protocol)
|
||||
|
||||
if connection.Protocol != "" && connection.IP != nil && intf != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if connection.IP == nil {
|
||||
return connection, "", errRemoteLineNotFound
|
||||
}
|
||||
|
||||
if connection.Protocol == "" {
|
||||
connection.Protocol = constants.UDP
|
||||
}
|
||||
|
||||
if connection.Port == 0 {
|
||||
connection.Port = 1194
|
||||
if connection.Protocol == constants.TCP {
|
||||
connection.Port = 443
|
||||
}
|
||||
}
|
||||
|
||||
return connection, intf, nil
|
||||
}
|
||||
|
||||
var (
|
||||
errExtractProto = errors.New("failed extracting protocol from proto line")
|
||||
errExtractRemote = errors.New("failed extracting from remote line")
|
||||
errExtractDev = errors.New("failed extracting network interface from dev line")
|
||||
)
|
||||
|
||||
func extractDataFromLine(line string) (
|
||||
ip net.IP, port uint16, protocol, intf string, err error) {
|
||||
switch {
|
||||
case strings.HasPrefix(line, "proto "):
|
||||
protocol, err = extractProto(line)
|
||||
if err != nil {
|
||||
return nil, 0, "", "", fmt.Errorf("%w: %s", errExtractProto, err)
|
||||
}
|
||||
return nil, 0, protocol, "", nil
|
||||
|
||||
case strings.HasPrefix(line, "remote "):
|
||||
ip, port, protocol, err = extractRemote(line)
|
||||
if err != nil {
|
||||
return nil, 0, "", "", fmt.Errorf("%w: %s", errExtractRemote, err)
|
||||
}
|
||||
return ip, port, protocol, "", nil
|
||||
|
||||
case strings.HasPrefix(line, "dev "):
|
||||
intf, err = extractInterfaceFromLine(line)
|
||||
if err != nil {
|
||||
return nil, 0, "", "", fmt.Errorf("%w: %s", errExtractDev, err)
|
||||
}
|
||||
return nil, 0, "", intf, nil
|
||||
}
|
||||
|
||||
return nil, 0, "", "", nil
|
||||
}
|
||||
|
||||
var (
|
||||
errProtoLineFieldsCount = errors.New("proto line has not 2 fields as expected")
|
||||
errProtocolNotSupported = errors.New("network protocol not supported")
|
||||
)
|
||||
|
||||
func extractProto(line string) (protocol string, err error) {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) != 2 { //nolint:gomnd
|
||||
return "", fmt.Errorf("%w: %s", errProtoLineFieldsCount, line)
|
||||
}
|
||||
|
||||
switch fields[1] {
|
||||
case "tcp", "udp":
|
||||
default:
|
||||
return "", fmt.Errorf("%w: %s", errProtocolNotSupported, fields[1])
|
||||
}
|
||||
|
||||
return fields[1], nil
|
||||
}
|
||||
|
||||
var (
|
||||
errRemoteLineFieldsCount = errors.New("remote line has not 2 fields as expected")
|
||||
errHostNotIP = errors.New("host is not an an IP address")
|
||||
errPortNotValid = errors.New("port is not valid")
|
||||
)
|
||||
|
||||
func extractRemote(line string) (ip net.IP, port uint16,
|
||||
protocol string, err error) {
|
||||
fields := strings.Fields(line)
|
||||
n := len(fields)
|
||||
|
||||
if n < 2 || n > 4 {
|
||||
return nil, 0, "", fmt.Errorf("%w: %s", errRemoteLineFieldsCount, line)
|
||||
}
|
||||
|
||||
host := fields[1]
|
||||
ip = net.ParseIP(host)
|
||||
if ip == nil {
|
||||
return nil, 0, "", fmt.Errorf("%w: %s", errHostNotIP, host)
|
||||
// TODO resolve hostname once there is an option to allow it through
|
||||
// the firewall before the VPN is up.
|
||||
}
|
||||
|
||||
if n > 2 { //nolint:gomnd
|
||||
portInt, err := strconv.Atoi(fields[2])
|
||||
if err != nil {
|
||||
return nil, 0, "", fmt.Errorf("%w: %s", errPortNotValid, line)
|
||||
} else if portInt < 1 || portInt > 65535 {
|
||||
return nil, 0, "", fmt.Errorf("%w: not between 1 and 65535: %d", errPortNotValid, portInt)
|
||||
}
|
||||
port = uint16(portInt)
|
||||
}
|
||||
|
||||
if n > 3 { //nolint:gomnd
|
||||
switch fields[3] {
|
||||
case "tcp", "udp":
|
||||
protocol = fields[3]
|
||||
default:
|
||||
return nil, 0, "", fmt.Errorf("%w: %s", errProtocolNotSupported, fields[3])
|
||||
}
|
||||
}
|
||||
|
||||
return ip, port, protocol, nil
|
||||
}
|
||||
|
||||
var (
|
||||
errDevLineFieldsCount = errors.New("dev line has not 2 fields as expected")
|
||||
)
|
||||
|
||||
func extractInterfaceFromLine(line string) (intf string, err error) {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) != 2 { //nolint:gomnd
|
||||
return "", fmt.Errorf("%w: %s", errDevLineFieldsCount, line)
|
||||
}
|
||||
|
||||
return fields[1], nil
|
||||
}
|
||||
316
internal/openvpn/custom/extract_test.go
Normal file
316
internal/openvpn/custom/extract_test.go
Normal file
@@ -0,0 +1,316 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_extractDataFromLines(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
lines []string
|
||||
connection models.Connection
|
||||
intf string
|
||||
err error
|
||||
}{
|
||||
"success": {
|
||||
lines: []string{"bla bla", "proto tcp", "remote 1.2.3.4 1194 tcp", "dev tun6"},
|
||||
connection: models.Connection{
|
||||
IP: net.IPv4(1, 2, 3, 4),
|
||||
Port: 1194,
|
||||
Protocol: constants.TCP,
|
||||
},
|
||||
intf: "tun6",
|
||||
},
|
||||
"extraction error": {
|
||||
lines: []string{"bla bla", "proto bad", "remote 1.2.3.4 1194 tcp"},
|
||||
err: errors.New("on line 2: failed extracting protocol from proto line: network protocol not supported: bad"),
|
||||
},
|
||||
"only use first values found": {
|
||||
lines: []string{"proto udp", "proto tcp", "remote 1.2.3.4 443 tcp", "remote 5.2.3.4 1194 udp"},
|
||||
connection: models.Connection{
|
||||
IP: net.IPv4(1, 2, 3, 4),
|
||||
Port: 443,
|
||||
Protocol: constants.UDP,
|
||||
},
|
||||
},
|
||||
"no IP found": {
|
||||
lines: []string{"proto tcp"},
|
||||
connection: models.Connection{
|
||||
Protocol: constants.TCP,
|
||||
},
|
||||
err: errRemoteLineNotFound,
|
||||
},
|
||||
"default TCP port": {
|
||||
lines: []string{"remote 1.2.3.4", "proto tcp"},
|
||||
connection: models.Connection{
|
||||
IP: net.IPv4(1, 2, 3, 4),
|
||||
Port: 443,
|
||||
Protocol: constants.TCP,
|
||||
},
|
||||
},
|
||||
"default UDP port": {
|
||||
lines: []string{"remote 1.2.3.4", "proto udp"},
|
||||
connection: models.Connection{
|
||||
IP: net.IPv4(1, 2, 3, 4),
|
||||
Port: 1194,
|
||||
Protocol: constants.UDP,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
connection, intf, err := extractDataFromLines(testCase.lines)
|
||||
|
||||
if testCase.err != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, testCase.err.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, testCase.connection, connection)
|
||||
assert.Equal(t, testCase.intf, intf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_extractDataFromLine(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
line string
|
||||
ip net.IP
|
||||
port uint16
|
||||
protocol string
|
||||
intf string
|
||||
isErr error
|
||||
}{
|
||||
"irrelevant line": {
|
||||
line: "bla bla",
|
||||
},
|
||||
"extract proto error": {
|
||||
line: "proto bad",
|
||||
isErr: errExtractProto,
|
||||
},
|
||||
"extract proto success": {
|
||||
line: "proto tcp",
|
||||
protocol: constants.TCP,
|
||||
},
|
||||
"extract intf error": {
|
||||
line: "dev ",
|
||||
isErr: errExtractDev,
|
||||
},
|
||||
"extract intf success": {
|
||||
line: "dev tun3",
|
||||
intf: "tun3",
|
||||
},
|
||||
"extract remote error": {
|
||||
line: "remote bad",
|
||||
isErr: errExtractRemote,
|
||||
},
|
||||
"extract remote success": {
|
||||
line: "remote 1.2.3.4 1194 udp",
|
||||
ip: net.IPv4(1, 2, 3, 4),
|
||||
port: 1194,
|
||||
protocol: constants.UDP,
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ip, port, protocol, intf, err := extractDataFromLine(testCase.line)
|
||||
|
||||
if testCase.isErr != nil {
|
||||
assert.ErrorIs(t, err, testCase.isErr)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, testCase.ip, ip)
|
||||
assert.Equal(t, testCase.port, port)
|
||||
assert.Equal(t, testCase.protocol, protocol)
|
||||
assert.Equal(t, testCase.intf, intf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_extractProto(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
line string
|
||||
protocol string
|
||||
err error
|
||||
}{
|
||||
"fields error": {
|
||||
line: "proto one two",
|
||||
err: errors.New("proto line has not 2 fields as expected: proto one two"),
|
||||
},
|
||||
"bad protocol": {
|
||||
line: "proto bad",
|
||||
err: errors.New("network protocol not supported: bad"),
|
||||
},
|
||||
"udp": {
|
||||
line: "proto udp",
|
||||
protocol: constants.UDP,
|
||||
},
|
||||
"tcp": {
|
||||
line: "proto tcp",
|
||||
protocol: constants.TCP,
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
protocol, err := extractProto(testCase.line)
|
||||
|
||||
if testCase.err != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, testCase.err.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, testCase.protocol, protocol)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_extractRemote(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
line string
|
||||
ip net.IP
|
||||
port uint16
|
||||
protocol string
|
||||
err error
|
||||
}{
|
||||
"not enough fields": {
|
||||
line: "remote",
|
||||
err: errors.New("remote line has not 2 fields as expected: remote"),
|
||||
},
|
||||
"too many fields": {
|
||||
line: "remote one two three four",
|
||||
err: errors.New("remote line has not 2 fields as expected: remote one two three four"),
|
||||
},
|
||||
"host is not an IP": {
|
||||
line: "remote somehost.com",
|
||||
err: errors.New("host is not an an IP address: somehost.com"),
|
||||
},
|
||||
"only IP host": {
|
||||
line: "remote 1.2.3.4",
|
||||
ip: net.IPv4(1, 2, 3, 4),
|
||||
},
|
||||
"port not an integer": {
|
||||
line: "remote 1.2.3.4 bad",
|
||||
err: errors.New("port is not valid: remote 1.2.3.4 bad"),
|
||||
},
|
||||
"port is zero": {
|
||||
line: "remote 1.2.3.4 0",
|
||||
err: errors.New("port is not valid: not between 1 and 65535: 0"),
|
||||
},
|
||||
"port is minus one": {
|
||||
line: "remote 1.2.3.4 -1",
|
||||
err: errors.New("port is not valid: not between 1 and 65535: -1"),
|
||||
},
|
||||
"port is over 65535": {
|
||||
line: "remote 1.2.3.4 65536",
|
||||
err: errors.New("port is not valid: not between 1 and 65535: 65536"),
|
||||
},
|
||||
"IP host and port": {
|
||||
line: "remote 1.2.3.4 8000",
|
||||
ip: net.IPv4(1, 2, 3, 4),
|
||||
port: 8000,
|
||||
},
|
||||
"invalid protocol": {
|
||||
line: "remote 1.2.3.4 8000 bad",
|
||||
err: errors.New("network protocol not supported: bad"),
|
||||
},
|
||||
"IP host and port and protocol": {
|
||||
line: "remote 1.2.3.4 8000 udp",
|
||||
ip: net.IPv4(1, 2, 3, 4),
|
||||
port: 8000,
|
||||
protocol: constants.UDP,
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ip, port, protocol, err := extractRemote(testCase.line)
|
||||
|
||||
if testCase.err != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, testCase.err.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, testCase.ip, ip)
|
||||
assert.Equal(t, testCase.port, port)
|
||||
assert.Equal(t, testCase.protocol, protocol)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_extractInterface(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
line string
|
||||
intf string
|
||||
err error
|
||||
}{
|
||||
"found": {
|
||||
line: "dev tun3",
|
||||
intf: "tun3",
|
||||
},
|
||||
"not enough fields": {
|
||||
line: "dev ",
|
||||
err: errors.New("dev line has not 2 fields as expected: dev "),
|
||||
},
|
||||
"too many fields": {
|
||||
line: "dev one two",
|
||||
err: errors.New("dev line has not 2 fields as expected: dev one two"),
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
intf, err := extractInterfaceFromLine(testCase.line)
|
||||
|
||||
if testCase.err != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, testCase.err.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, testCase.intf, intf)
|
||||
})
|
||||
}
|
||||
}
|
||||
14
internal/openvpn/custom/helpers_test.go
Normal file
14
internal/openvpn/custom/helpers_test.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func removeFile(t *testing.T, filename string) {
|
||||
t.Helper()
|
||||
err := os.RemoveAll(filename)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
67
internal/openvpn/custom/modify.go
Normal file
67
internal/openvpn/custom/modify.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||
)
|
||||
|
||||
func modifyCustomConfig(lines []string, settings configuration.OpenVPN,
|
||||
connection models.Connection, intf string) (modified []string) {
|
||||
// Remove some lines
|
||||
for _, line := range lines {
|
||||
switch {
|
||||
case strings.HasPrefix(line, "up "),
|
||||
strings.HasPrefix(line, "down "),
|
||||
strings.HasPrefix(line, "verb "),
|
||||
strings.HasPrefix(line, "auth-user-pass "),
|
||||
strings.HasPrefix(line, "user "),
|
||||
strings.HasPrefix(line, "proto "),
|
||||
strings.HasPrefix(line, "remote "),
|
||||
strings.HasPrefix(line, "dev "),
|
||||
settings.Cipher != "" && strings.HasPrefix(line, "cipher "),
|
||||
settings.Cipher != "" && strings.HasPrefix(line, "data-ciphers "),
|
||||
settings.Auth != "" && strings.HasPrefix(line, "auth "),
|
||||
settings.MSSFix > 0 && strings.HasPrefix(line, "mssfix "),
|
||||
!settings.IPv6 && strings.HasPrefix(line, "tun-ipv6"):
|
||||
default:
|
||||
modified = append(modified, line)
|
||||
}
|
||||
}
|
||||
|
||||
// Add values
|
||||
modified = append(modified, connection.OpenVPNProtoLine())
|
||||
modified = append(modified, connection.OpenVPNRemoteLine())
|
||||
modified = append(modified, "dev "+intf)
|
||||
modified = append(modified, "mute-replay-warnings")
|
||||
modified = append(modified, "auth-nocache")
|
||||
modified = append(modified, "pull-filter ignore \"auth-token\"") // prevent auth failed loop
|
||||
modified = append(modified, "auth-retry nointeract")
|
||||
modified = append(modified, "suppress-timestamps")
|
||||
if settings.User != "" {
|
||||
modified = append(modified, "auth-user-pass "+constants.OpenVPNAuthConf)
|
||||
}
|
||||
modified = append(modified, "verb "+strconv.Itoa(settings.Verbosity))
|
||||
if settings.Cipher != "" {
|
||||
modified = append(modified, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||
}
|
||||
if settings.Auth != "" {
|
||||
modified = append(modified, "auth "+settings.Auth)
|
||||
}
|
||||
if settings.MSSFix > 0 {
|
||||
modified = append(modified, "mssfix "+strconv.Itoa(int(settings.MSSFix)))
|
||||
}
|
||||
if !settings.IPv6 {
|
||||
modified = append(modified, `pull-filter ignore "route-ipv6"`)
|
||||
modified = append(modified, `pull-filter ignore "ifconfig-ipv6"`)
|
||||
}
|
||||
if !settings.Root {
|
||||
modified = append(modified, "user "+settings.ProcUser)
|
||||
}
|
||||
|
||||
return modified
|
||||
}
|
||||
80
internal/openvpn/custom/modify_test.go
Normal file
80
internal/openvpn/custom/modify_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_modifyCustomConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
lines []string
|
||||
settings configuration.OpenVPN
|
||||
connection models.Connection
|
||||
intf string
|
||||
modified []string
|
||||
}{
|
||||
"mixed": {
|
||||
lines: []string{
|
||||
"up bla",
|
||||
"proto tcp",
|
||||
"remote 5.5.5.5",
|
||||
"cipher bla",
|
||||
"tun-ipv6",
|
||||
"keep me here",
|
||||
"auth bla",
|
||||
},
|
||||
settings: configuration.OpenVPN{
|
||||
User: "user",
|
||||
Cipher: "cipher",
|
||||
Auth: "auth",
|
||||
MSSFix: 1000,
|
||||
ProcUser: "procuser",
|
||||
},
|
||||
connection: models.Connection{
|
||||
IP: net.IPv4(1, 2, 3, 4),
|
||||
Port: 1194,
|
||||
Protocol: constants.UDP,
|
||||
},
|
||||
intf: "tun3",
|
||||
modified: []string{
|
||||
"keep me here",
|
||||
"proto udp",
|
||||
"remote 1.2.3.4 1194",
|
||||
"dev tun3",
|
||||
"mute-replay-warnings",
|
||||
"auth-nocache",
|
||||
"pull-filter ignore \"auth-token\"",
|
||||
"auth-retry nointeract",
|
||||
"suppress-timestamps",
|
||||
"auth-user-pass /etc/openvpn/auth.conf",
|
||||
"verb 0",
|
||||
"data-ciphers-fallback cipher",
|
||||
"data-ciphers cipher",
|
||||
"auth auth",
|
||||
"mssfix 1000",
|
||||
"pull-filter ignore \"route-ipv6\"",
|
||||
"pull-filter ignore \"ifconfig-ipv6\"",
|
||||
"user procuser",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
modified := modifyCustomConfig(testCase.lines,
|
||||
testCase.settings, testCase.connection, testCase.intf)
|
||||
|
||||
assert.Equal(t, testCase.modified, modified)
|
||||
})
|
||||
}
|
||||
}
|
||||
27
internal/openvpn/custom/read.go
Normal file
27
internal/openvpn/custom/read.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func readCustomConfigLines(filepath string) (
|
||||
lines []string, err error) {
|
||||
file, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := file.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return strings.Split(string(b), "\n"), nil
|
||||
}
|
||||
32
internal/openvpn/custom/read_test.go
Normal file
32
internal/openvpn/custom/read_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_readCustomConfigLines(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
file, err := os.CreateTemp("", "")
|
||||
require.NoError(t, err)
|
||||
defer removeFile(t, file.Name())
|
||||
defer file.Close()
|
||||
|
||||
_, err = file.WriteString("line one\nline two\nline three\n")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = file.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
lines, err := readCustomConfigLines(file.Name())
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedLines := []string{
|
||||
"line one", "line two", "line three", "",
|
||||
}
|
||||
assert.Equal(t, expectedLines, lines)
|
||||
}
|
||||
@@ -8,44 +8,6 @@ import (
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
func (l *Loop) collectLines(stdout, stderr <-chan string, done chan<- struct{}) {
|
||||
defer close(done)
|
||||
var line string
|
||||
var ok, errLine bool
|
||||
|
||||
for {
|
||||
errLine = false
|
||||
select {
|
||||
case line, ok = <-stdout:
|
||||
case line, ok = <-stderr:
|
||||
errLine = true
|
||||
}
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
line, level := processLogLine(line)
|
||||
if line == "" {
|
||||
continue // filtered out
|
||||
}
|
||||
if errLine {
|
||||
level = logging.LevelError
|
||||
}
|
||||
switch level {
|
||||
case logging.LevelDebug:
|
||||
l.logger.Debug(line)
|
||||
case logging.LevelInfo:
|
||||
l.logger.Info(line)
|
||||
case logging.LevelWarn:
|
||||
l.logger.Warn(line)
|
||||
case logging.LevelError:
|
||||
l.logger.Error(line)
|
||||
}
|
||||
if strings.Contains(line, "Initialization Sequence Completed") {
|
||||
l.tunnelReady <- struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func processLogLine(s string) (filtered string, level logging.Level) {
|
||||
for _, ignored := range []string{
|
||||
"WARNING: you are using user/group/chroot/setcon without persist-tun -- this may cause restarts to fail",
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
package openvpn
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/firewall"
|
||||
"github.com/qdm12/gluetun/internal/loopstate"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/openvpn/state"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
var _ Looper = (*Loop)(nil)
|
||||
|
||||
type Looper interface {
|
||||
Runner
|
||||
loopstate.Getter
|
||||
loopstate.Applier
|
||||
SettingsGetSetter
|
||||
ServersGetterSetter
|
||||
PortForwadedGetter
|
||||
PortForwader
|
||||
}
|
||||
|
||||
type Loop struct {
|
||||
statusManager loopstate.Manager
|
||||
state state.Manager
|
||||
// Fixed parameters
|
||||
username string
|
||||
puid int
|
||||
pgid int
|
||||
targetConfPath string
|
||||
// Configurators
|
||||
conf StarterAuthWriter
|
||||
fw firewallConfigurer
|
||||
// Other objects
|
||||
logger, pfLogger logging.Logger
|
||||
client *http.Client
|
||||
tunnelReady chan<- struct{}
|
||||
// Internal channels and values
|
||||
stop <-chan struct{}
|
||||
stopped chan<- struct{}
|
||||
start <-chan struct{}
|
||||
running chan<- models.LoopStatus
|
||||
portForwardSignals chan net.IP
|
||||
userTrigger bool
|
||||
// Internal constant values
|
||||
backoffTime time.Duration
|
||||
}
|
||||
|
||||
type firewallConfigurer interface {
|
||||
firewall.VPNConnectionSetter
|
||||
firewall.PortAllower
|
||||
}
|
||||
|
||||
const (
|
||||
defaultBackoffTime = 15 * time.Second
|
||||
)
|
||||
|
||||
func NewLoop(settings configuration.OpenVPN, username string,
|
||||
puid, pgid int, allServers models.AllServers, conf Configurator,
|
||||
fw firewallConfigurer, logger logging.ParentLogger,
|
||||
client *http.Client, tunnelReady chan<- struct{}) *Loop {
|
||||
start := make(chan struct{})
|
||||
running := make(chan models.LoopStatus)
|
||||
stop := make(chan struct{})
|
||||
stopped := make(chan struct{})
|
||||
|
||||
statusManager := loopstate.New(constants.Stopped, start, running, stop, stopped)
|
||||
state := state.New(statusManager, settings, allServers)
|
||||
|
||||
return &Loop{
|
||||
statusManager: statusManager,
|
||||
state: state,
|
||||
username: username,
|
||||
puid: puid,
|
||||
pgid: pgid,
|
||||
targetConfPath: constants.OpenVPNConf,
|
||||
conf: conf,
|
||||
fw: fw,
|
||||
logger: logger.NewChild(logging.Settings{Prefix: "openvpn: "}),
|
||||
pfLogger: logger.NewChild(logging.Settings{Prefix: "port forwarding: "}),
|
||||
client: client,
|
||||
tunnelReady: tunnelReady,
|
||||
start: start,
|
||||
running: running,
|
||||
stop: stop,
|
||||
stopped: stopped,
|
||||
portForwardSignals: make(chan net.IP),
|
||||
userTrigger: true,
|
||||
backoffTime: defaultBackoffTime,
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,35 @@
|
||||
// Package openvpn defines interfaces to interact with openvpn
|
||||
// and run it in a stateful loop.
|
||||
package openvpn
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/unix"
|
||||
"github.com/qdm12/golibs/command"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
type Configurator interface {
|
||||
var _ Interface = (*Configurator)(nil)
|
||||
|
||||
type Interface interface {
|
||||
VersionGetter
|
||||
AuthWriter
|
||||
TUNCheckCreater
|
||||
Starter
|
||||
Writer
|
||||
}
|
||||
|
||||
type StarterAuthWriter interface {
|
||||
Starter
|
||||
AuthWriter
|
||||
}
|
||||
|
||||
type configurator struct {
|
||||
type Configurator struct {
|
||||
logger logging.Logger
|
||||
cmder command.RunStarter
|
||||
unix unix.Unix
|
||||
configPath string
|
||||
authFilePath string
|
||||
tunDevPath string
|
||||
puid, pgid int
|
||||
}
|
||||
|
||||
func NewConfigurator(logger logging.Logger, unix unix.Unix,
|
||||
cmder command.RunStarter) Configurator {
|
||||
return &configurator{
|
||||
func New(logger logging.Logger,
|
||||
cmder command.RunStarter, puid, pgid int) *Configurator {
|
||||
return &Configurator{
|
||||
logger: logger,
|
||||
cmder: cmder,
|
||||
unix: unix,
|
||||
configPath: constants.OpenVPNConf,
|
||||
authFilePath: constants.OpenVPNAuthConf,
|
||||
tunDevPath: constants.TunnelDevice,
|
||||
puid: puid,
|
||||
pgid: pgid,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
package openvpn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/openvpn/state"
|
||||
"github.com/qdm12/gluetun/internal/provider"
|
||||
)
|
||||
|
||||
type PortForwadedGetter = state.PortForwardedGetter
|
||||
|
||||
func (l *Loop) GetPortForwarded() (port uint16) {
|
||||
return l.state.GetPortForwarded()
|
||||
}
|
||||
|
||||
type PortForwader interface {
|
||||
PortForward(vpnGatewayIP net.IP)
|
||||
}
|
||||
|
||||
func (l *Loop) PortForward(vpnGateway net.IP) { l.portForwardSignals <- vpnGateway }
|
||||
|
||||
// portForward is a blocking operation which may or may not be infinite.
|
||||
// You should therefore always call it in a goroutine.
|
||||
func (l *Loop) portForward(ctx context.Context,
|
||||
providerConf provider.Provider, client *http.Client, gateway net.IP) {
|
||||
settings := l.state.GetSettings()
|
||||
if !settings.Provider.PortForwarding.Enabled {
|
||||
return
|
||||
}
|
||||
syncState := func(port uint16) (pfFilepath string) {
|
||||
l.state.SetPortForwarded(port)
|
||||
settings := l.state.GetSettings()
|
||||
return settings.Provider.PortForwarding.Filepath
|
||||
}
|
||||
providerConf.PortForward(ctx, client, l.pfLogger,
|
||||
gateway, l.fw, syncState)
|
||||
}
|
||||
@@ -2,147 +2,50 @@ package openvpn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider"
|
||||
"github.com/qdm12/gluetun/internal/configuration"
|
||||
"github.com/qdm12/golibs/command"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
type Runner interface {
|
||||
Run(ctx context.Context, done chan<- struct{})
|
||||
type Runner struct {
|
||||
settings configuration.OpenVPN
|
||||
starter command.Starter
|
||||
logger logging.Logger
|
||||
}
|
||||
|
||||
func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
||||
defer close(done)
|
||||
func NewRunner(settings configuration.OpenVPN, starter command.Starter,
|
||||
logger logging.Logger) *Runner {
|
||||
return &Runner{
|
||||
starter: starter,
|
||||
logger: logger,
|
||||
settings: settings,
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case <-l.start:
|
||||
case <-ctx.Done():
|
||||
func (r *Runner) Run(ctx context.Context, errCh chan<- error, ready chan<- struct{}) {
|
||||
stdoutLines, stderrLines, waitError, err := start(ctx, r.starter, r.settings.Version, r.settings.Flags)
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
|
||||
for ctx.Err() == nil {
|
||||
settings, allServers := l.state.GetSettingsAndServers()
|
||||
streamCtx, streamCancel := context.WithCancel(context.Background())
|
||||
streamDone := make(chan struct{})
|
||||
go streamLines(streamCtx, streamDone, r.logger,
|
||||
stdoutLines, stderrLines, ready)
|
||||
|
||||
providerConf := provider.New(settings.Provider.Name, allServers, time.Now)
|
||||
|
||||
var connection models.OpenVPNConnection
|
||||
var lines []string
|
||||
var err error
|
||||
if settings.Config == "" {
|
||||
connection, err = providerConf.GetOpenVPNConnection(settings.Provider.ServerSelection)
|
||||
if err != nil {
|
||||
l.signalOrSetStatus(constants.Crashed)
|
||||
l.logAndWait(ctx, err)
|
||||
continue
|
||||
}
|
||||
lines = providerConf.BuildConf(connection, l.username, settings)
|
||||
} else {
|
||||
lines, connection, err = l.processCustomConfig(settings)
|
||||
if err != nil {
|
||||
l.signalOrSetStatus(constants.Crashed)
|
||||
l.logAndWait(ctx, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if err := l.writeOpenvpnConf(lines); err != nil {
|
||||
l.signalOrSetStatus(constants.Crashed)
|
||||
l.logAndWait(ctx, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if settings.User != "" {
|
||||
err := l.conf.WriteAuthFile(
|
||||
settings.User, settings.Password, l.puid, l.pgid)
|
||||
if err != nil {
|
||||
l.signalOrSetStatus(constants.Crashed)
|
||||
l.logAndWait(ctx, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if err := l.fw.SetVPNConnection(ctx, connection); err != nil {
|
||||
l.signalOrSetStatus(constants.Crashed)
|
||||
l.logAndWait(ctx, err)
|
||||
continue
|
||||
}
|
||||
|
||||
openvpnCtx, openvpnCancel := context.WithCancel(context.Background())
|
||||
|
||||
stdoutLines, stderrLines, waitError, err := l.conf.Start(
|
||||
openvpnCtx, settings.Version, settings.Flags)
|
||||
if err != nil {
|
||||
openvpnCancel()
|
||||
l.signalOrSetStatus(constants.Crashed)
|
||||
l.logAndWait(ctx, err)
|
||||
continue
|
||||
}
|
||||
|
||||
lineCollectionDone := make(chan struct{})
|
||||
go l.collectLines(stdoutLines, stderrLines, lineCollectionDone)
|
||||
closeStreams := func() {
|
||||
close(stdoutLines)
|
||||
close(stderrLines)
|
||||
<-lineCollectionDone
|
||||
}
|
||||
|
||||
// Needs the stream line from main.go to know when the tunnel is up
|
||||
portForwardDone := make(chan struct{})
|
||||
go func(ctx context.Context) {
|
||||
defer close(portForwardDone)
|
||||
select {
|
||||
// TODO have a way to disable pf with a context
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case gateway := <-l.portForwardSignals:
|
||||
l.portForward(ctx, providerConf, l.client, gateway)
|
||||
}
|
||||
}(openvpnCtx)
|
||||
|
||||
l.backoffTime = defaultBackoffTime
|
||||
l.signalOrSetStatus(constants.Running)
|
||||
|
||||
stayHere := true
|
||||
for stayHere {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
openvpnCancel()
|
||||
<-waitError
|
||||
close(waitError)
|
||||
closeStreams()
|
||||
<-portForwardDone
|
||||
return
|
||||
case <-l.stop:
|
||||
l.userTrigger = true
|
||||
l.logger.Info("stopping")
|
||||
openvpnCancel()
|
||||
<-waitError
|
||||
// do not close waitError or the waitError
|
||||
// select case will trigger
|
||||
closeStreams()
|
||||
<-portForwardDone
|
||||
l.stopped <- struct{}{}
|
||||
case <-l.start:
|
||||
l.userTrigger = true
|
||||
l.logger.Info("starting")
|
||||
stayHere = false
|
||||
case err := <-waitError: // unexpected error
|
||||
streamCancel()
|
||||
<-streamDone
|
||||
errCh <- ctx.Err()
|
||||
case err := <-waitError:
|
||||
close(waitError)
|
||||
closeStreams()
|
||||
|
||||
l.statusManager.Lock() // prevent SetStatus from running in parallel
|
||||
|
||||
openvpnCancel()
|
||||
l.statusManager.SetStatus(constants.Crashed)
|
||||
<-portForwardDone
|
||||
l.logAndWait(ctx, err)
|
||||
stayHere = false
|
||||
|
||||
l.statusManager.Unlock()
|
||||
}
|
||||
}
|
||||
openvpnCancel()
|
||||
streamCancel()
|
||||
<-streamDone
|
||||
errCh <- err
|
||||
}
|
||||
}
|
||||
|
||||
39
internal/openvpn/start.go
Normal file
39
internal/openvpn/start.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package openvpn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/golibs/command"
|
||||
)
|
||||
|
||||
var ErrVersionUnknown = errors.New("OpenVPN version is unknown")
|
||||
|
||||
const (
|
||||
binOpenvpn24 = "openvpn2.4"
|
||||
binOpenvpn25 = "openvpn"
|
||||
)
|
||||
|
||||
func start(ctx context.Context, starter command.Starter, version string, flags []string) (
|
||||
stdoutLines, stderrLines chan string, waitError chan error, err error) {
|
||||
var bin string
|
||||
switch version {
|
||||
case constants.Openvpn24:
|
||||
bin = binOpenvpn24
|
||||
case constants.Openvpn25:
|
||||
bin = binOpenvpn25
|
||||
default:
|
||||
return nil, nil, nil, fmt.Errorf("%w: %s", ErrVersionUnknown, version)
|
||||
}
|
||||
|
||||
args := []string{"--config", constants.OpenVPNConf}
|
||||
args = append(args, flags...)
|
||||
cmd := exec.CommandContext(ctx, bin, args...)
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
|
||||
return starter.Start(cmd)
|
||||
}
|
||||
53
internal/openvpn/stream.go
Normal file
53
internal/openvpn/stream.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package openvpn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
func streamLines(ctx context.Context, done chan<- struct{},
|
||||
logger logging.Logger, stdout, stderr chan string,
|
||||
tunnelReady chan<- struct{}) {
|
||||
defer close(done)
|
||||
|
||||
var line string
|
||||
|
||||
for {
|
||||
errLine := false
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Context should only be canceled after stdout and stderr are done
|
||||
// being written to.
|
||||
close(stdout)
|
||||
close(stderr)
|
||||
return
|
||||
case line = <-stdout:
|
||||
case line = <-stderr:
|
||||
errLine = true
|
||||
}
|
||||
line, level := processLogLine(line)
|
||||
if line == "" {
|
||||
continue // filtered out
|
||||
}
|
||||
if errLine {
|
||||
level = logging.LevelError
|
||||
}
|
||||
switch level {
|
||||
case logging.LevelDebug:
|
||||
logger.Debug(line)
|
||||
case logging.LevelInfo:
|
||||
logger.Info(line)
|
||||
case logging.LevelWarn:
|
||||
logger.Warn(line)
|
||||
case logging.LevelError:
|
||||
logger.Error(line)
|
||||
}
|
||||
if strings.Contains(line, "Initialization Sequence Completed") {
|
||||
// do not close tunnelReady in case the initialization
|
||||
// happens multiple times without Openvpn restarting
|
||||
tunnelReady <- struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package openvpn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/unix"
|
||||
)
|
||||
|
||||
type TUNCheckCreater interface {
|
||||
TUNChecker
|
||||
TUNCreater
|
||||
}
|
||||
|
||||
type TUNChecker interface {
|
||||
CheckTUN() error
|
||||
}
|
||||
|
||||
// CheckTUN checks the tunnel device is present and accessible.
|
||||
func (c *configurator) CheckTUN() error {
|
||||
c.logger.Info("checking for device " + c.tunDevPath)
|
||||
f, err := os.OpenFile(c.tunDevPath, os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("TUN device is not available: %w", err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
c.logger.Warn("Could not close TUN device file: " + err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type TUNCreater interface {
|
||||
CreateTUN() error
|
||||
}
|
||||
|
||||
func (c *configurator) CreateTUN() error {
|
||||
c.logger.Info("creating " + c.tunDevPath)
|
||||
|
||||
parentDir := filepath.Dir(c.tunDevPath)
|
||||
if err := os.MkdirAll(parentDir, 0751); err != nil { //nolint:gomnd
|
||||
return err
|
||||
}
|
||||
|
||||
const (
|
||||
major = 10
|
||||
minor = 200
|
||||
)
|
||||
dev := c.unix.Mkdev(major, minor)
|
||||
if err := c.unix.Mknod(c.tunDevPath, unix.S_IFCHR, int(dev)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
const readWriteAllPerms os.FileMode = 0666
|
||||
file, err := os.OpenFile(c.tunDevPath, os.O_WRONLY, readWriteAllPerms)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return file.Close()
|
||||
}
|
||||
39
internal/openvpn/version.go
Normal file
39
internal/openvpn/version.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package openvpn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type VersionGetter interface {
|
||||
Version24(ctx context.Context) (version string, err error)
|
||||
Version25(ctx context.Context) (version string, err error)
|
||||
}
|
||||
|
||||
func (c *Configurator) Version24(ctx context.Context) (version string, err error) {
|
||||
return c.version(ctx, binOpenvpn24)
|
||||
}
|
||||
|
||||
func (c *Configurator) Version25(ctx context.Context) (version string, err error) {
|
||||
return c.version(ctx, binOpenvpn25)
|
||||
}
|
||||
|
||||
var ErrVersionTooShort = errors.New("version output is too short")
|
||||
|
||||
func (c *Configurator) version(ctx context.Context, binName string) (version string, err error) {
|
||||
cmd := exec.CommandContext(ctx, binName, "--version")
|
||||
output, err := c.cmder.Run(cmd)
|
||||
if err != nil && err.Error() != "exit status 1" {
|
||||
return "", err
|
||||
}
|
||||
firstLine := strings.Split(output, "\n")[0]
|
||||
words := strings.Fields(firstLine)
|
||||
const minWords = 2
|
||||
if len(words) < minWords {
|
||||
return "", fmt.Errorf("%w: %s", ErrVersionTooShort, firstLine)
|
||||
}
|
||||
return words[1], nil
|
||||
}
|
||||
32
internal/portforward/firewall.go
Normal file
32
internal/portforward/firewall.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package portforward
|
||||
|
||||
import "context"
|
||||
|
||||
// firewallBlockPort obtains the state port thread safely and blocks
|
||||
// it in the firewall if it is not the zero value (0).
|
||||
func (l *Loop) firewallBlockPort(ctx context.Context) {
|
||||
port := l.state.GetPortForwarded()
|
||||
if port == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
err := l.portAllower.RemoveAllowedPort(ctx, port)
|
||||
if err != nil {
|
||||
l.logger.Error("cannot block previous port in firewall: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// firewallAllowPort obtains the state port thread safely and allows
|
||||
// it in the firewall if it is not the zero value (0).
|
||||
func (l *Loop) firewallAllowPort(ctx context.Context) {
|
||||
port := l.state.GetPortForwarded()
|
||||
if port == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
startData := l.state.GetStartData()
|
||||
err := l.portAllower.SetAllowedPort(ctx, port, startData.Interface)
|
||||
if err != nil {
|
||||
l.logger.Error("cannot allow port through firewall: " + err.Error())
|
||||
}
|
||||
}
|
||||
37
internal/portforward/fs.go
Normal file
37
internal/portforward/fs.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package portforward
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func (l *Loop) removePortForwardedFile() {
|
||||
filepath := l.state.GetSettings().Filepath
|
||||
l.logger.Info("removing port file " + filepath)
|
||||
if err := os.Remove(filepath); err != nil {
|
||||
l.logger.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Loop) writePortForwardedFile(port uint16) {
|
||||
filepath := l.state.GetSettings().Filepath
|
||||
l.logger.Info("writing port file " + filepath)
|
||||
if err := writePortForwardedToFile(filepath, port); err != nil {
|
||||
l.logger.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func writePortForwardedToFile(filepath string, port uint16) (err error) {
|
||||
file, err := os.OpenFile(filepath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = file.Write([]byte(fmt.Sprint(port)))
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
return file.Close()
|
||||
}
|
||||
9
internal/portforward/get.go
Normal file
9
internal/portforward/get.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package portforward
|
||||
|
||||
import "github.com/qdm12/gluetun/internal/portforward/state"
|
||||
|
||||
type Getter = state.PortForwardedGetter
|
||||
|
||||
func (l *Loop) GetPortForwarded() (port uint16) {
|
||||
return l.state.GetPortForwarded()
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user