Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16ecf48b89 | ||
|
|
8fa4fd1b64 | ||
|
|
4db6d1ecf9 | ||
|
|
3b86927ca7 | ||
|
|
8bfa2f9b27 | ||
|
|
fe2a3e4d11 | ||
|
|
b0451d8e50 | ||
|
|
a0b9044fd3 | ||
|
|
c7a841f4b4 | ||
|
|
4ba159e483 | ||
|
|
63a696d7e7 | ||
|
|
d457342b46 | ||
|
|
c246dae2cc | ||
|
|
0f4a2e5224 | ||
|
|
db262050d5 | ||
|
|
227cdea0c8 | ||
|
|
33a6f1c01b | ||
|
|
f6f3c110f0 | ||
|
|
27a3f2c846 | ||
|
|
62169baeea | ||
|
|
4b18636a91 | ||
|
|
51432ca05f | ||
|
|
b5ebdcd040 | ||
|
|
416c1ee113 | ||
|
|
fe97e28461 | ||
|
|
cbd8711a21 | ||
|
|
7578e52ed5 | ||
|
|
0df68f76d5 | ||
|
|
9a528c42f8 | ||
|
|
5607916af6 | ||
|
|
4ad7a2a444 | ||
|
|
ab5dbdca97 | ||
|
|
a97fcda283 | ||
|
|
e955adc1e1 | ||
|
|
ac5141b411 | ||
|
|
f8c189e48a | ||
|
|
2f2a904c64 | ||
|
|
9261dca8ab | ||
|
|
7b5d5c3884 | ||
|
|
7c80d80904 | ||
|
|
ea40b84ec0 | ||
|
|
4e6ef649c4 | ||
|
|
dd40f1d2e6 | ||
|
|
490693bb26 | ||
|
|
c8d33ca5f3 | ||
|
|
e6df026332 | ||
|
|
7a30343053 | ||
|
|
fc02ae9c13 | ||
|
|
f70f0aca9c | ||
|
|
16acd1b162 | ||
|
|
2e3eb1fd7b | ||
|
|
a4cf17f81e | ||
|
|
c0a301611d | ||
|
|
cc934f5c68 | ||
|
|
74426f6202 | ||
|
|
03ed3cb1c8 | ||
|
|
1b1335835b | ||
|
|
5070dbcf7f | ||
|
|
90b9d85742 | ||
|
|
7a3b9941aa | ||
|
|
698095f0a0 | ||
|
|
5a06d8e155 | ||
|
|
7421dcb45f | ||
|
|
554a6cdb92 | ||
|
|
5aa39be973 | ||
|
|
192a7a56a3 | ||
|
|
1d1657e9be | ||
|
|
49b7301295 |
@@ -1,2 +1,2 @@
|
|||||||
FROM qmcgaw/godevcontainer
|
FROM qmcgaw/godevcontainer
|
||||||
RUN apk add wireguard-tools htop
|
RUN apk add wireguard-tools htop openssl
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ services:
|
|||||||
# Docker configuration
|
# Docker configuration
|
||||||
- ~/.docker:/root/.docker
|
- ~/.docker:/root/.docker
|
||||||
# SSH directory for Linux, OSX and WSL
|
# SSH directory for Linux, OSX and WSL
|
||||||
- ~/.ssh:/root/.ssh
|
# On Linux and OSX, a symlink /mnt/ssh <-> ~/.ssh is
|
||||||
# For Windows without WSL, a copy will be made
|
# created in the container. On Windows, files are copied
|
||||||
# from /tmp/.ssh to ~/.ssh to fix permissions
|
# from /mnt/ssh to ~/.ssh to fix permissions.
|
||||||
#- ~/.ssh:/tmp/.ssh:ro
|
- ~/.ssh:/mnt/ssh
|
||||||
# Shell history persistence
|
# Shell history persistence
|
||||||
- ~/.zsh_history:/root/.zsh_history
|
- ~/.zsh_history:/root/.zsh_history
|
||||||
# Git config
|
# Git config
|
||||||
|
|||||||
31
.github/labels.yml
vendored
31
.github/labels.yml
vendored
@@ -1,18 +1,13 @@
|
|||||||
- name: "Bug :bug:"
|
# Temporary status
|
||||||
color: "b60205"
|
- name: "🗯️ Waiting for feedback"
|
||||||
description: ""
|
color: "aadefa"
|
||||||
- name: "Feature request :bulb:"
|
|
||||||
color: "0e8a16"
|
|
||||||
description: ""
|
|
||||||
- name: "Help wanted :pray:"
|
|
||||||
color: "4caf50"
|
|
||||||
description: ""
|
|
||||||
- name: "Documentation :memo:"
|
|
||||||
color: "c5def5"
|
|
||||||
description: ""
|
|
||||||
- name: "Needs more info :thinking:"
|
|
||||||
color: "795548"
|
|
||||||
description: ""
|
description: ""
|
||||||
|
- name: "🔴 Blocked"
|
||||||
|
color: "ff3f14"
|
||||||
|
description: "Blocked by another issue or pull request"
|
||||||
|
- name: "🔒 After next release"
|
||||||
|
color: "e8f274"
|
||||||
|
description: "Will be done after the next release"
|
||||||
|
|
||||||
# Priority
|
# Priority
|
||||||
- name: "🚨 Urgent"
|
- name: "🚨 Urgent"
|
||||||
@@ -22,6 +17,14 @@
|
|||||||
color: "4285f4"
|
color: "4285f4"
|
||||||
description: ""
|
description: ""
|
||||||
|
|
||||||
|
# Complexity
|
||||||
|
- name: "☣️ Hard to do"
|
||||||
|
color: "7d0008"
|
||||||
|
description: ""
|
||||||
|
- name: "🟩 Easy to do"
|
||||||
|
color: "34cf43"
|
||||||
|
description: ""
|
||||||
|
|
||||||
# VPN providers
|
# VPN providers
|
||||||
- name: ":cloud: AirVPN"
|
- name: ":cloud: AirVPN"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
|
|||||||
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@@ -93,6 +93,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
actions: read
|
actions: read
|
||||||
contents: read
|
contents: read
|
||||||
|
packages: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -106,6 +107,7 @@ jobs:
|
|||||||
flavor: |
|
flavor: |
|
||||||
latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
||||||
images: |
|
images: |
|
||||||
|
ghcr.io/qdm12/gluetun
|
||||||
qmcgaw/gluetun
|
qmcgaw/gluetun
|
||||||
qmcgaw/private-internet-access
|
qmcgaw/private-internet-access
|
||||||
tags: |
|
tags: |
|
||||||
@@ -123,12 +125,18 @@ jobs:
|
|||||||
username: qmcgaw
|
username: qmcgaw
|
||||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
|
||||||
|
- uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: qdm12
|
||||||
|
password: ${{ github.token }}
|
||||||
|
|
||||||
- name: Short commit
|
- name: Short commit
|
||||||
id: shortcommit
|
id: shortcommit
|
||||||
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
|
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
|
||||||
|
|
||||||
- name: Build and push final image
|
- name: Build and push final image
|
||||||
uses: docker/build-push-action@v3.2.0
|
uses: docker/build-push-action@v4.0.0
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|||||||
12
Dockerfile
12
Dockerfile
@@ -1,8 +1,8 @@
|
|||||||
ARG ALPINE_VERSION=3.16
|
ARG ALPINE_VERSION=3.17
|
||||||
ARG GO_ALPINE_VERSION=3.16
|
ARG GO_ALPINE_VERSION=3.17
|
||||||
ARG GO_VERSION=1.19
|
ARG GO_VERSION=1.20
|
||||||
ARG XCPUTRANSLATE_VERSION=v0.6.0
|
ARG XCPUTRANSLATE_VERSION=v0.6.0
|
||||||
ARG GOLANGCI_LINT_VERSION=v1.49.0
|
ARG GOLANGCI_LINT_VERSION=v1.51.2
|
||||||
ARG MOCKGEN_VERSION=v1.6.0
|
ARG MOCKGEN_VERSION=v1.6.0
|
||||||
ARG BUILDPLATFORM=linux/amd64
|
ARG BUILDPLATFORM=linux/amd64
|
||||||
|
|
||||||
@@ -97,6 +97,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
WIREGUARD_PRESHARED_KEY= \
|
WIREGUARD_PRESHARED_KEY= \
|
||||||
WIREGUARD_PUBLIC_KEY= \
|
WIREGUARD_PUBLIC_KEY= \
|
||||||
WIREGUARD_ADDRESSES= \
|
WIREGUARD_ADDRESSES= \
|
||||||
|
WIREGUARD_IMPLEMENTATION=auto \
|
||||||
# VPN server filtering
|
# VPN server filtering
|
||||||
SERVER_REGIONS= \
|
SERVER_REGIONS= \
|
||||||
SERVER_COUNTRIES= \
|
SERVER_COUNTRIES= \
|
||||||
@@ -196,8 +197,9 @@ ENTRYPOINT ["/gluetun-entrypoint"]
|
|||||||
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
|
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
|
||||||
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheck
|
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheck
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
RUN apk add --no-cache --update -l apk-tools && \
|
RUN apk add --no-cache --update -l wget && \
|
||||||
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.12-r0 && \
|
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.12-r0 && \
|
||||||
|
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.16/main" openssl\~1.1 && \
|
||||||
mv /usr/sbin/openvpn /usr/sbin/openvpn2.4 && \
|
mv /usr/sbin/openvpn /usr/sbin/openvpn2.4 && \
|
||||||
apk del openvpn && \
|
apk del openvpn && \
|
||||||
apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \
|
apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Based on Alpine 3.16 for a small Docker image of 29MB
|
- Based on Alpine 3.17 for a small Docker image of 42MB
|
||||||
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
|
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
|
||||||
- Supports OpenVPN for all providers listed
|
- Supports OpenVPN for all providers listed
|
||||||
- Supports Wireguard both kernelspace and userspace
|
- Supports Wireguard both kernelspace and userspace
|
||||||
@@ -118,8 +118,13 @@ services:
|
|||||||
# - WIREGUARD_ADDRESSES=10.64.222.21/32
|
# - WIREGUARD_ADDRESSES=10.64.222.21/32
|
||||||
# Timezone for accurate log times
|
# Timezone for accurate log times
|
||||||
- TZ=
|
- TZ=
|
||||||
|
# Server list updater. See https://github.com/qdm12/gluetun/wiki/Updating-Servers#periodic-update
|
||||||
|
- UPDATER_PERIOD=
|
||||||
|
- UPDATER_VPN_SERVICE_PROVIDERS=
|
||||||
```
|
```
|
||||||
|
|
||||||
|
🆕 Image also available as `ghcr.io/qdm12/gluetun`
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[](https://github.com/qdm12/gluetun/master/LICENSE)
|
[](https://github.com/qdm12/gluetun/master/LICENSE)
|
||||||
|
|||||||
@@ -77,7 +77,8 @@ func main() {
|
|||||||
|
|
||||||
args := os.Args
|
args := os.Args
|
||||||
tun := tun.New()
|
tun := tun.New()
|
||||||
netLinker := netlink.New()
|
netLinkDebugLogger := logger.New(log.SetComponent("netlink"))
|
||||||
|
netLinker := netlink.New(netLinkDebugLogger)
|
||||||
cli := cli.New()
|
cli := cli.New()
|
||||||
cmder := command.NewCmder()
|
cmder := command.NewCmder()
|
||||||
|
|
||||||
@@ -91,12 +92,13 @@ func main() {
|
|||||||
errorCh <- _main(ctx, buildInfo, args, logger, muxReader, tun, netLinker, cmder, cli)
|
errorCh <- _main(ctx, buildInfo, args, logger, muxReader, tun, netLinker, cmder, cli)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
var err error
|
||||||
select {
|
select {
|
||||||
case signal := <-signalCh:
|
case signal := <-signalCh:
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
logger.Warn("Caught OS signal " + signal.String() + ", shutting down")
|
logger.Warn("Caught OS signal " + signal.String() + ", shutting down")
|
||||||
cancel()
|
cancel()
|
||||||
case err := <-errorCh:
|
case err = <-errorCh:
|
||||||
close(errorCh)
|
close(errorCh)
|
||||||
if err == nil { // expected exit such as healthcheck
|
if err == nil { // expected exit such as healthcheck
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
@@ -108,23 +110,28 @@ func main() {
|
|||||||
const shutdownGracePeriod = 5 * time.Second
|
const shutdownGracePeriod = 5 * time.Second
|
||||||
timer := time.NewTimer(shutdownGracePeriod)
|
timer := time.NewTimer(shutdownGracePeriod)
|
||||||
select {
|
select {
|
||||||
case err := <-errorCh:
|
case shutdownErr := <-errorCh:
|
||||||
if !timer.Stop() {
|
if !timer.Stop() {
|
||||||
<-timer.C
|
<-timer.C
|
||||||
}
|
}
|
||||||
if err == nil {
|
if shutdownErr != nil {
|
||||||
logger.Info("Shutdown successful")
|
logger.Warnf("Shutdown not completed gracefully: %s", shutdownErr)
|
||||||
os.Exit(0)
|
os.Exit(1)
|
||||||
}
|
|
||||||
logger.Warnf("Shutdown not completed gracefully: %s", err)
|
|
||||||
case <-timer.C:
|
|
||||||
logger.Warn("Shutdown timed out")
|
|
||||||
case signal := <-signalCh:
|
|
||||||
logger.Warn("Caught OS signal " + signal.String() + ", forcing shut down")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.Info("Shutdown successful")
|
||||||
|
if err != nil {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
case <-timer.C:
|
||||||
|
logger.Warn("Shutdown timed out")
|
||||||
|
os.Exit(1)
|
||||||
|
case signal := <-signalCh:
|
||||||
|
logger.Warn("Caught OS signal " + signal.String() + ", forcing shut down")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errCommandUnknown = errors.New("command is unknown")
|
errCommandUnknown = errors.New("command is unknown")
|
||||||
@@ -183,6 +190,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
// - firewall Debug and Enabled are booleans parsed from source
|
// - firewall Debug and Enabled are booleans parsed from source
|
||||||
|
|
||||||
logger.Patch(log.SetLevel(*allSettings.Log.Level))
|
logger.Patch(log.SetLevel(*allSettings.Log.Level))
|
||||||
|
netLinker.PatchLoggerLevel(*allSettings.Log.Level)
|
||||||
|
|
||||||
routingLogger := logger.New(log.SetComponent("routing"))
|
routingLogger := logger.New(log.SetComponent("routing"))
|
||||||
if *allSettings.Firewall.Debug { // To remove in v4
|
if *allSettings.Firewall.Debug { // To remove in v4
|
||||||
@@ -224,7 +232,12 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = allSettings.Validate(storage)
|
ipv6Supported, err := netLinker.IsIPv6Supported()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("checking for IPv6 support: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = allSettings.Validate(storage, ipv6Supported)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -232,7 +245,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
allSettings.Pprof.HTTPServer.Logger = logger.New(log.SetComponent("pprof"))
|
allSettings.Pprof.HTTPServer.Logger = logger.New(log.SetComponent("pprof"))
|
||||||
pprofServer, err := pprof.New(allSettings.Pprof)
|
pprofServer, err := pprof.New(allSettings.Pprof)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot create Pprof server: %w", err)
|
return fmt.Errorf("creating Pprof server: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
puid, pgid := int(*allSettings.System.PUID), int(*allSettings.System.PGID)
|
puid, pgid := int(*allSettings.System.PUID), int(*allSettings.System.PGID)
|
||||||
@@ -264,6 +277,10 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
|
|
||||||
logger.Info(allSettings.String())
|
logger.Info(allSettings.String())
|
||||||
|
|
||||||
|
for _, warning := range allSettings.Warnings() {
|
||||||
|
logger.Warn(warning)
|
||||||
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll("/tmp/gluetun", 0644); err != nil {
|
if err := os.MkdirAll("/tmp/gluetun", 0644); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -274,7 +291,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
const defaultUsername = "nonrootuser"
|
const defaultUsername = "nonrootuser"
|
||||||
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
|
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot create user: %w", err)
|
return fmt.Errorf("creating user: %w", err)
|
||||||
}
|
}
|
||||||
if nonRootUsername != defaultUsername {
|
if nonRootUsername != defaultUsername {
|
||||||
logger.Info("using existing username " + nonRootUsername + " corresponding to user id " + fmt.Sprint(puid))
|
logger.Info("using existing username " + nonRootUsername + " corresponding to user id " + fmt.Sprint(puid))
|
||||||
@@ -288,22 +305,11 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ipv6Supported, err := netLinker.IsIPv6Supported()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("checking for IPv6 support: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ipv6Supported {
|
|
||||||
logger.Info("IPv6 is supported")
|
|
||||||
} else {
|
|
||||||
logger.Info("IPv6 is not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := routingConf.Setup(); err != nil {
|
if err := routingConf.Setup(); err != nil {
|
||||||
if strings.Contains(err.Error(), "operation not permitted") {
|
if strings.Contains(err.Error(), "operation not permitted") {
|
||||||
logger.Warn("💡 Tip: Are you passing NET_ADMIN capability to gluetun?")
|
logger.Warn("💡 Tip: Are you passing NET_ADMIN capability to gluetun?")
|
||||||
}
|
}
|
||||||
return fmt.Errorf("cannot setup routing: %w", err)
|
return fmt.Errorf("setting up routing: %w", err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
routingLogger.Info("routing cleanup...")
|
routingLogger.Info("routing cleanup...")
|
||||||
@@ -354,11 +360,14 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupOptions...)
|
tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupOptions...)
|
||||||
otherGroupHandler := goshutdown.NewGroupHandler("other", defaultGroupOptions...)
|
otherGroupHandler := goshutdown.NewGroupHandler("other", defaultGroupOptions...)
|
||||||
|
|
||||||
|
if *allSettings.Pprof.Enabled {
|
||||||
|
// TODO run in run loop so this can be patched at runtime
|
||||||
pprofReady := make(chan struct{})
|
pprofReady := make(chan struct{})
|
||||||
pprofHandler, pprofCtx, pprofDone := goshutdown.NewGoRoutineHandler("pprof server")
|
pprofHandler, pprofCtx, pprofDone := goshutdown.NewGoRoutineHandler("pprof server")
|
||||||
go pprofServer.Run(pprofCtx, pprofReady, pprofDone)
|
go pprofServer.Run(pprofCtx, pprofReady, pprofDone)
|
||||||
otherGroupHandler.Add(pprofHandler)
|
otherGroupHandler.Add(pprofHandler)
|
||||||
<-pprofReady
|
<-pprofReady
|
||||||
|
}
|
||||||
|
|
||||||
portForwardLogger := logger.New(log.SetComponent("port forwarding"))
|
portForwardLogger := logger.New(log.SetComponent("port forwarding"))
|
||||||
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
|
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
|
||||||
@@ -446,9 +455,10 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
"http server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
"http server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
httpServer, err := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
|
httpServer, err := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
|
||||||
logger.New(log.SetComponent("http server")),
|
logger.New(log.SetComponent("http server")),
|
||||||
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper, storage)
|
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper,
|
||||||
|
storage, ipv6Supported)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot setup control server: %w", err)
|
return fmt.Errorf("setting up control server: %w", err)
|
||||||
}
|
}
|
||||||
httpServerReady := make(chan struct{})
|
httpServerReady := make(chan struct{})
|
||||||
go httpServer.Run(httpServerCtx, httpServerReady, httpServerDone)
|
go httpServer.Run(httpServerCtx, httpServerReady, httpServerDone)
|
||||||
@@ -495,7 +505,7 @@ func printVersions(ctx context.Context, logger infoer,
|
|||||||
for _, element := range elements {
|
for _, element := range elements {
|
||||||
version, err := element.getVersion(ctx)
|
version, err := element.getVersion(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("getting %s version: %w", element.name, err)
|
||||||
}
|
}
|
||||||
logger.Info(element.name + " version: " + version)
|
logger.Info(element.name + " version: " + version)
|
||||||
}
|
}
|
||||||
@@ -510,6 +520,7 @@ type netLinker interface {
|
|||||||
Linker
|
Linker
|
||||||
IsWireguardSupported() (ok bool, err error)
|
IsWireguardSupported() (ok bool, err error)
|
||||||
IsIPv6Supported() (ok bool, err error)
|
IsIPv6Supported() (ok bool, err error)
|
||||||
|
PatchLoggerLevel(level log.Level)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Addresser interface {
|
type Addresser interface {
|
||||||
|
|||||||
19
go.mod
19
go.mod
@@ -1,10 +1,10 @@
|
|||||||
module github.com/qdm12/gluetun
|
module github.com/qdm12/gluetun
|
||||||
|
|
||||||
go 1.19
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/breml/rootcerts v0.2.8
|
github.com/breml/rootcerts v0.2.10
|
||||||
github.com/fatih/color v1.13.0
|
github.com/fatih/color v1.14.1
|
||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/qdm12/dns v1.11.0
|
github.com/qdm12/dns v1.11.0
|
||||||
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
|
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
|
||||||
@@ -15,11 +15,12 @@ require (
|
|||||||
github.com/qdm12/log v0.1.0
|
github.com/qdm12/log v0.1.0
|
||||||
github.com/qdm12/ss-server v0.4.0
|
github.com/qdm12/ss-server v0.4.0
|
||||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
|
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.2
|
||||||
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5
|
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
|
||||||
golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2
|
golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f
|
golang.org/x/sys v0.6.0
|
||||||
golang.org/x/text v0.4.0
|
golang.org/x/text v0.8.0
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478
|
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b
|
||||||
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722
|
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722
|
||||||
@@ -29,8 +30,8 @@ require (
|
|||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/google/go-cmp v0.5.7 // indirect
|
github.com/google/go-cmp v0.5.7 // indirect
|
||||||
github.com/josharian/native v1.0.0 // indirect
|
github.com/josharian/native v1.0.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.9 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||||
github.com/mdlayher/genetlink v1.2.0 // indirect
|
github.com/mdlayher/genetlink v1.2.0 // indirect
|
||||||
github.com/mdlayher/netlink v1.6.0 // indirect
|
github.com/mdlayher/netlink v1.6.0 // indirect
|
||||||
github.com/mdlayher/socket v0.2.3 // indirect
|
github.com/mdlayher/socket v0.2.3 // indirect
|
||||||
@@ -40,7 +41,7 @@ require (
|
|||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
|
||||||
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 // indirect
|
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 // indirect
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
|
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||||
|
|||||||
38
go.sum
38
go.sum
@@ -4,8 +4,8 @@ github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/g
|
|||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
github.com/breml/rootcerts v0.2.8 h1:hNPyNa+MghU9ZKqWy+MYxvouNqE70jvOBIa5v70z/P8=
|
github.com/breml/rootcerts v0.2.10 h1:UGVZ193UTSUASpGtg6pbDwzOd7XQP+at0Ssg1/2E4h8=
|
||||||
github.com/breml/rootcerts v0.2.8/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
|
github.com/breml/rootcerts v0.2.10/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
@@ -14,8 +14,8 @@ github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
|
|||||||
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
||||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
|
||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||||
@@ -56,11 +56,12 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|||||||
github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=
|
github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=
|
||||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||||
|
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU=
|
github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU=
|
||||||
github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ=
|
github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ=
|
||||||
github.com/mdlayher/netlink v1.6.0 h1:rOHX5yl7qnlpiVkFWoqccueppMtXzeziFjWAjLg6sz0=
|
github.com/mdlayher/netlink v1.6.0 h1:rOHX5yl7qnlpiVkFWoqccueppMtXzeziFjWAjLg6sz0=
|
||||||
@@ -115,26 +116,29 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
|||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5 h1:b/k/BVWzWRS5v6AB0gf2ckFSbFsHN5jR0HoNso1pN+w=
|
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5 h1:b/k/BVWzWRS5v6AB0gf2ckFSbFsHN5jR0HoNso1pN+w=
|
||||||
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
|
||||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||||
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
||||||
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
|
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 h1:VFTf+jjIgsldaz/Mr00VaCSswHJrI2hIjQygE/W4IMg=
|
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 h1:VFTf+jjIgsldaz/Mr00VaCSswHJrI2hIjQygE/W4IMg=
|
||||||
go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
|
go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4=
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 h1:QJ/xcIANMLApehfgPCHnfK1hZiaMmbaTVmPv7DAoTbo=
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/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/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
@@ -175,12 +179,12 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@@ -188,8 +192,8 @@ 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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
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=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ func (c *CLI) FormatServers(args []string) error {
|
|||||||
logger := newNoopLogger()
|
logger := newNoopLogger()
|
||||||
storage, err := storage.New(logger, constants.ServersData)
|
storage, err := storage.New(logger, constants.ServersData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot create servers storage: %w", err)
|
return fmt.Errorf("creating servers storage: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
formatted := storage.FormatToMarkdown(providerToFormat)
|
formatted := storage.FormatToMarkdown(providerToFormat)
|
||||||
@@ -81,18 +81,18 @@ func (c *CLI) FormatServers(args []string) error {
|
|||||||
output = filepath.Clean(output)
|
output = filepath.Clean(output)
|
||||||
file, err := os.OpenFile(output, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644)
|
file, err := os.OpenFile(output, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot open output file: %w", err)
|
return fmt.Errorf("opening output file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = fmt.Fprint(file, formatted)
|
_, err = fmt.Fprint(file, formatted)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
return fmt.Errorf("cannot write to output file: %w", err)
|
return fmt.Errorf("writing to output file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = file.Close()
|
err = file.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot close output file: %w", err)
|
return fmt.Errorf("closing output file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ func (c *CLI) HealthCheck(ctx context.Context, source Source, warner Warner) err
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.SetDefaults()
|
||||||
|
|
||||||
err = config.Validate()
|
err = config.Validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -51,15 +51,15 @@ func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source Source,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = allSettings.Validate(storage); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ipv6Supported, err := ipv6Checker.IsIPv6Supported()
|
ipv6Supported, err := ipv6Checker.IsIPv6Supported()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("checking for IPv6 support: %w", err)
|
return fmt.Errorf("checking for IPv6 support: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = allSettings.Validate(storage, ipv6Supported); err != nil {
|
||||||
|
return fmt.Errorf("validating settings: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Unused by this CLI command
|
// Unused by this CLI command
|
||||||
unzipper := (Unzipper)(nil)
|
unzipper := (Unzipper)(nil)
|
||||||
client := (*http.Client)(nil)
|
client := (*http.Client)(nil)
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
|
|||||||
|
|
||||||
storage, err := storage.New(logger, constants.ServersData)
|
storage, err := storage.New(logger, constants.ServersData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot create servers storage: %w", err)
|
return fmt.Errorf("creating servers storage: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
const clientTimeout = 10 * time.Second
|
const clientTimeout = 10 * time.Second
|
||||||
@@ -88,13 +88,13 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
|
|||||||
updater := updater.New(httpClient, storage, providers, logger)
|
updater := updater.New(httpClient, storage, providers, logger)
|
||||||
err = updater.UpdateServers(ctx, options.Providers, options.MinRatio)
|
err = updater.UpdateServers(ctx, options.Providers, options.MinRatio)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot update server information: %w", err)
|
return fmt.Errorf("updating server information: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if maintainerMode {
|
if maintainerMode {
|
||||||
err := storage.FlushToFile(c.repoServersPath)
|
err := storage.FlushToFile(c.repoServersPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot write servers data to embedded JSON file: %w", err)
|
return fmt.Errorf("writing servers data to embedded JSON file: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ type DNS struct {
|
|||||||
func (d DNS) validate() (err error) {
|
func (d DNS) validate() (err error) {
|
||||||
err = d.DoT.validate()
|
err = d.DoT.validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed validating DoT settings: %w", err)
|
return fmt.Errorf("validating DoT settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -39,9 +39,11 @@ var (
|
|||||||
ErrWireguardEndpointPortNotSet = errors.New("endpoint port is not set")
|
ErrWireguardEndpointPortNotSet = errors.New("endpoint port is not set")
|
||||||
ErrWireguardEndpointPortSet = errors.New("endpoint port is set")
|
ErrWireguardEndpointPortSet = errors.New("endpoint port is set")
|
||||||
ErrWireguardInterfaceAddressNotSet = errors.New("interface address is not set")
|
ErrWireguardInterfaceAddressNotSet = errors.New("interface address is not set")
|
||||||
|
ErrWireguardInterfaceAddressIPv6 = errors.New("interface address is IPv6 but IPv6 is not supported")
|
||||||
ErrWireguardInterfaceNotValid = errors.New("interface name is not valid")
|
ErrWireguardInterfaceNotValid = errors.New("interface name is not valid")
|
||||||
ErrWireguardPreSharedKeyNotSet = errors.New("pre-shared key is not set")
|
ErrWireguardPreSharedKeyNotSet = errors.New("pre-shared key is not set")
|
||||||
ErrWireguardPrivateKeyNotSet = errors.New("private key is not set")
|
ErrWireguardPrivateKeyNotSet = errors.New("private key is not set")
|
||||||
ErrWireguardPublicKeyNotSet = errors.New("public key is not set")
|
ErrWireguardPublicKeyNotSet = errors.New("public key is not set")
|
||||||
ErrWireguardPublicKeyNotValid = errors.New("public key is not valid")
|
ErrWireguardPublicKeyNotValid = errors.New("public key is not valid")
|
||||||
|
ErrWireguardImplementationNotValid = errors.New("implementation is not valid")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -109,7 +109,8 @@ func (f Firewall) toLinesNode() (node *gotree.Node) {
|
|||||||
if len(f.OutboundSubnets) > 0 {
|
if len(f.OutboundSubnets) > 0 {
|
||||||
outboundSubnets := node.Appendf("Outbound subnets:")
|
outboundSubnets := node.Appendf("Outbound subnets:")
|
||||||
for _, subnet := range f.OutboundSubnets {
|
for _, subnet := range f.OutboundSubnets {
|
||||||
outboundSubnets.Appendf("%s", subnet)
|
subnet := subnet
|
||||||
|
outboundSubnets.Appendf("%s", &subnet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,18 +42,18 @@ type OpenVPN struct {
|
|||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
// It is ignored if it is set to the empty string.
|
// It is ignored if it is set to the empty string.
|
||||||
Auth *string
|
Auth *string
|
||||||
// Cert is the OpenVPN certificate for the <cert> block.
|
// Cert is the base64 encoded DER of an OpenVPN certificate for the <cert> block.
|
||||||
// This is notably used by Cyberghost and VPN secure.
|
// This is notably used by Cyberghost and VPN secure.
|
||||||
// It can be set to the empty string to be ignored.
|
// It can be set to the empty string to be ignored.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
Cert *string
|
Cert *string
|
||||||
// Key is the OpenVPN key.
|
// Key is the base64 encoded DER of an OpenVPN key.
|
||||||
// This is used by Cyberghost and VPN Unlimited.
|
// This is used by Cyberghost and VPN Unlimited.
|
||||||
// It can be set to the empty string to be ignored.
|
// It can be set to the empty string to be ignored.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
Key *string
|
Key *string
|
||||||
// EncryptedKey is the content of an encrypted
|
// EncryptedKey is the base64 encoded DER of an encrypted key for OpenVPN.
|
||||||
// key for OpenVPN. It is used by VPN secure.
|
// It is used by VPN secure.
|
||||||
// It defaults to the empty string meaning it is not
|
// It defaults to the empty string meaning it is not
|
||||||
// to be used. KeyPassphrase must be set if this one is set.
|
// to be used. KeyPassphrase must be set if this one is set.
|
||||||
EncryptedKey *string
|
EncryptedKey *string
|
||||||
@@ -171,7 +171,7 @@ func validateOpenVPNConfigFilepath(isCustom bool,
|
|||||||
extractor := extract.New()
|
extractor := extract.New()
|
||||||
_, _, err = extractor.Data(confFile)
|
_, _, err = extractor.Data(confFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed extracting information from custom configuration file: %w", err)
|
return fmt.Errorf("extracting information from custom configuration file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ package settings
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants/openvpn"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/pprof"
|
"github.com/qdm12/gluetun/internal/pprof"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
@@ -31,7 +35,7 @@ type Storage interface {
|
|||||||
// Validate validates all the settings and returns an error
|
// Validate validates all the settings and returns an error
|
||||||
// if one of them is not valid.
|
// if one of them is not valid.
|
||||||
// TODO v4 remove pointer for receiver (because of Surfshark).
|
// TODO v4 remove pointer for receiver (because of Surfshark).
|
||||||
func (s *Settings) Validate(storage Storage) (err error) {
|
func (s *Settings) Validate(storage Storage, ipv6Supported bool) (err error) {
|
||||||
nameToValidation := map[string]func() error{
|
nameToValidation := map[string]func() error{
|
||||||
"control server": s.ControlServer.validate,
|
"control server": s.ControlServer.validate,
|
||||||
"dns": s.DNS.validate,
|
"dns": s.DNS.validate,
|
||||||
@@ -46,7 +50,7 @@ func (s *Settings) Validate(storage Storage) (err error) {
|
|||||||
"version": s.Version.validate,
|
"version": s.Version.validate,
|
||||||
// Pprof validation done in pprof constructor
|
// Pprof validation done in pprof constructor
|
||||||
"VPN": func() error {
|
"VPN": func() error {
|
||||||
return s.VPN.Validate(storage)
|
return s.VPN.Validate(storage, ipv6Supported)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +99,7 @@ func (s *Settings) MergeWith(other Settings) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Settings) OverrideWith(other Settings,
|
func (s *Settings) OverrideWith(other Settings,
|
||||||
storage Storage) (err error) {
|
storage Storage, ipv6Supported bool) (err error) {
|
||||||
patchedSettings := s.copy()
|
patchedSettings := s.copy()
|
||||||
patchedSettings.ControlServer.overrideWith(other.ControlServer)
|
patchedSettings.ControlServer.overrideWith(other.ControlServer)
|
||||||
patchedSettings.DNS.overrideWith(other.DNS)
|
patchedSettings.DNS.overrideWith(other.DNS)
|
||||||
@@ -110,7 +114,7 @@ func (s *Settings) OverrideWith(other Settings,
|
|||||||
patchedSettings.Version.overrideWith(other.Version)
|
patchedSettings.Version.overrideWith(other.Version)
|
||||||
patchedSettings.VPN.OverrideWith(other.VPN)
|
patchedSettings.VPN.OverrideWith(other.VPN)
|
||||||
patchedSettings.Pprof.OverrideWith(other.Pprof)
|
patchedSettings.Pprof.OverrideWith(other.Pprof)
|
||||||
err = patchedSettings.Validate(storage)
|
err = patchedSettings.Validate(storage, ipv6Supported)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -157,3 +161,37 @@ func (s Settings) toLinesNode() (node *gotree.Node) {
|
|||||||
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s Settings) Warnings() (warnings []string) {
|
||||||
|
if *s.VPN.Provider.Name == providers.HideMyAss {
|
||||||
|
warnings = append(warnings, "HideMyAss dropped support for Linux OpenVPN "+
|
||||||
|
" so this will likely not work anymore. See https://github.com/qdm12/gluetun/issues/1498.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if helpers.IsOneOf(*s.VPN.Provider.Name, providers.SlickVPN) &&
|
||||||
|
s.VPN.Type == vpn.OpenVPN {
|
||||||
|
if s.VPN.OpenVPN.Version == openvpn.Openvpn24 {
|
||||||
|
warnings = append(warnings, "OpenVPN 2.4 uses OpenSSL 1.1.1 "+
|
||||||
|
"which allows the usage of weak security in today's standards. "+
|
||||||
|
"This can be ok if good security is enforced by the VPN provider. "+
|
||||||
|
"However, "+*s.VPN.Provider.Name+" uses weak security so you should use "+
|
||||||
|
"OpenVPN 2.5 to enforce good security practices.")
|
||||||
|
} else {
|
||||||
|
warnings = append(warnings, "OpenVPN 2.5 uses OpenSSL 3 "+
|
||||||
|
"which prohibits the usage of weak security in today's standards. "+
|
||||||
|
*s.VPN.Provider.Name+" uses weak security which is out "+
|
||||||
|
"of Gluetun's control so the only workaround is to allow such weaknesses "+
|
||||||
|
`using the OpenVPN option tls-cipher "DEFAULT:@SECLEVEL=0". `+
|
||||||
|
"You might want to reach to your provider so they upgrade their certificates. "+
|
||||||
|
"Once this is done, you will have to let the Gluetun maintainers know "+
|
||||||
|
"by creating an issue, attaching the new certificate and we will update Gluetun.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.VPN.OpenVPN.Version == openvpn.Openvpn24 {
|
||||||
|
warnings = append(warnings, "OpenVPN 2.4 will be removed in release v3.34.0 (around June 2023). "+
|
||||||
|
"Please create an issue if you have a compelling reason to keep it.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return warnings
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type VPN struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO v4 remove pointer for receiver (because of Surfshark).
|
// TODO v4 remove pointer for receiver (because of Surfshark).
|
||||||
func (v *VPN) Validate(storage Storage) (err error) {
|
func (v *VPN) Validate(storage Storage, ipv6Supported bool) (err error) {
|
||||||
// Validate Type
|
// Validate Type
|
||||||
validVPNTypes := []string{vpn.OpenVPN, vpn.Wireguard}
|
validVPNTypes := []string{vpn.OpenVPN, vpn.Wireguard}
|
||||||
if !helpers.IsOneOf(v.Type, validVPNTypes...) {
|
if !helpers.IsOneOf(v.Type, validVPNTypes...) {
|
||||||
@@ -39,7 +39,7 @@ func (v *VPN) Validate(storage Storage) (err error) {
|
|||||||
return fmt.Errorf("OpenVPN settings: %w", err)
|
return fmt.Errorf("OpenVPN settings: %w", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err := v.Wireguard.validate(*v.Provider.Name)
|
err := v.Wireguard.validate(*v.Provider.Name, ipv6Supported)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Wireguard settings: %w", err)
|
return fmt.Errorf("Wireguard settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,13 +27,18 @@ type Wireguard struct {
|
|||||||
// to create. It cannot be the empty string in the
|
// to create. It cannot be the empty string in the
|
||||||
// internal state.
|
// internal state.
|
||||||
Interface string
|
Interface string
|
||||||
|
// Implementation is the Wireguard implementation to use.
|
||||||
|
// It can be "auto", "userspace" or "kernelspace".
|
||||||
|
// It defaults to "auto" and cannot be the empty string
|
||||||
|
// in the internal state.
|
||||||
|
Implementation string
|
||||||
}
|
}
|
||||||
|
|
||||||
var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
|
var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
|
||||||
|
|
||||||
// Validate validates Wireguard settings.
|
// Validate validates Wireguard settings.
|
||||||
// It should only be ran if the VPN type chosen is Wireguard.
|
// It should only be ran if the VPN type chosen is Wireguard.
|
||||||
func (w Wireguard) validate(vpnProvider string) (err error) {
|
func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error) {
|
||||||
if !helpers.IsOneOf(vpnProvider,
|
if !helpers.IsOneOf(vpnProvider,
|
||||||
providers.Custom,
|
providers.Custom,
|
||||||
providers.Ivpn,
|
providers.Ivpn,
|
||||||
@@ -77,6 +82,12 @@ func (w Wireguard) validate(vpnProvider string) (err error) {
|
|||||||
return fmt.Errorf("%w: for address at index %d: %s",
|
return fmt.Errorf("%w: for address at index %d: %s",
|
||||||
ErrWireguardInterfaceAddressNotSet, i, ipNet.String())
|
ErrWireguardInterfaceAddressNotSet, i, ipNet.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipv6Net := ipNet.IP.To4() == nil
|
||||||
|
if ipv6Net && !ipv6Supported {
|
||||||
|
return fmt.Errorf("%w: address %s",
|
||||||
|
ErrWireguardInterfaceAddressIPv6, ipNet)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate interface
|
// Validate interface
|
||||||
@@ -85,6 +96,12 @@ func (w Wireguard) validate(vpnProvider string) (err error) {
|
|||||||
ErrWireguardInterfaceNotValid, w.Interface, regexpInterfaceName)
|
ErrWireguardInterfaceNotValid, w.Interface, regexpInterfaceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validImplementations := []string{"auto", "userspace", "kernelspace"}
|
||||||
|
if !helpers.IsOneOf(w.Implementation, validImplementations...) {
|
||||||
|
return fmt.Errorf("%w: %s must be one of %s", ErrWireguardImplementationNotValid,
|
||||||
|
w.Implementation, helpers.ChoicesOrString(validImplementations))
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +111,7 @@ func (w *Wireguard) copy() (copied Wireguard) {
|
|||||||
PreSharedKey: helpers.CopyStringPtr(w.PreSharedKey),
|
PreSharedKey: helpers.CopyStringPtr(w.PreSharedKey),
|
||||||
Addresses: helpers.CopyIPNetSlice(w.Addresses),
|
Addresses: helpers.CopyIPNetSlice(w.Addresses),
|
||||||
Interface: w.Interface,
|
Interface: w.Interface,
|
||||||
|
Implementation: w.Implementation,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,6 +120,7 @@ func (w *Wireguard) mergeWith(other Wireguard) {
|
|||||||
w.PreSharedKey = helpers.MergeWithStringPtr(w.PreSharedKey, other.PreSharedKey)
|
w.PreSharedKey = helpers.MergeWithStringPtr(w.PreSharedKey, other.PreSharedKey)
|
||||||
w.Addresses = helpers.MergeIPNetsSlices(w.Addresses, other.Addresses)
|
w.Addresses = helpers.MergeIPNetsSlices(w.Addresses, other.Addresses)
|
||||||
w.Interface = helpers.MergeWithString(w.Interface, other.Interface)
|
w.Interface = helpers.MergeWithString(w.Interface, other.Interface)
|
||||||
|
w.Implementation = helpers.MergeWithString(w.Implementation, other.Implementation)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Wireguard) overrideWith(other Wireguard) {
|
func (w *Wireguard) overrideWith(other Wireguard) {
|
||||||
@@ -109,12 +128,14 @@ func (w *Wireguard) overrideWith(other Wireguard) {
|
|||||||
w.PreSharedKey = helpers.OverrideWithStringPtr(w.PreSharedKey, other.PreSharedKey)
|
w.PreSharedKey = helpers.OverrideWithStringPtr(w.PreSharedKey, other.PreSharedKey)
|
||||||
w.Addresses = helpers.OverrideWithIPNetsSlice(w.Addresses, other.Addresses)
|
w.Addresses = helpers.OverrideWithIPNetsSlice(w.Addresses, other.Addresses)
|
||||||
w.Interface = helpers.OverrideWithString(w.Interface, other.Interface)
|
w.Interface = helpers.OverrideWithString(w.Interface, other.Interface)
|
||||||
|
w.Implementation = helpers.OverrideWithString(w.Implementation, other.Implementation)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Wireguard) setDefaults() {
|
func (w *Wireguard) setDefaults() {
|
||||||
w.PrivateKey = helpers.DefaultStringPtr(w.PrivateKey, "")
|
w.PrivateKey = helpers.DefaultStringPtr(w.PrivateKey, "")
|
||||||
w.PreSharedKey = helpers.DefaultStringPtr(w.PreSharedKey, "")
|
w.PreSharedKey = helpers.DefaultStringPtr(w.PreSharedKey, "")
|
||||||
w.Interface = helpers.DefaultString(w.Interface, "wg0")
|
w.Interface = helpers.DefaultString(w.Interface, "wg0")
|
||||||
|
w.Implementation = helpers.DefaultString(w.Implementation, "auto")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w Wireguard) String() string {
|
func (w Wireguard) String() string {
|
||||||
@@ -141,5 +162,9 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
|
|||||||
|
|
||||||
node.Appendf("Network interface: %s", w.Interface)
|
node.Appendf("Network interface: %s", w.Interface)
|
||||||
|
|
||||||
|
if w.Implementation != "auto" {
|
||||||
|
node.Appendf("Implementation: %s", w.Implementation)
|
||||||
|
}
|
||||||
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ func stringsToIPNets(ss []string) (ipNets []net.IPNet, err error) {
|
|||||||
for i, s := range ss {
|
for i, s := range ss {
|
||||||
ip, ipNet, err := net.ParseCIDR(s)
|
ip, ipNet, err := net.ParseCIDR(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse IP network %q: %w", s, err)
|
return nil, fmt.Errorf("parsing IP network %q: %w", s, err)
|
||||||
}
|
}
|
||||||
ipNet.IP = ip
|
ipNet.IP = ip
|
||||||
ipNets[i] = *ipNet
|
ipNets[i] = *ipNet
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ func unsetEnvKeys(envKeys []string, err error) (newErr error) {
|
|||||||
for _, envKey := range envKeys {
|
for _, envKey := range envKeys {
|
||||||
unsetErr := os.Unsetenv(envKey)
|
unsetErr := os.Unsetenv(envKey)
|
||||||
if unsetErr != nil && newErr == nil {
|
if unsetErr != nil && newErr == nil {
|
||||||
newErr = fmt.Errorf("cannot unset environment variable %s: %w", envKey, unsetErr)
|
newErr = fmt.Errorf("unsetting environment variable %s: %w", envKey, unsetErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return newErr
|
return newErr
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package env
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
@@ -15,6 +16,7 @@ func (s *Source) readWireguard() (wireguard settings.Wireguard, err error) {
|
|||||||
wireguard.PrivateKey = envToStringPtr("WIREGUARD_PRIVATE_KEY")
|
wireguard.PrivateKey = envToStringPtr("WIREGUARD_PRIVATE_KEY")
|
||||||
wireguard.PreSharedKey = envToStringPtr("WIREGUARD_PRESHARED_KEY")
|
wireguard.PreSharedKey = envToStringPtr("WIREGUARD_PRESHARED_KEY")
|
||||||
_, wireguard.Interface = s.getEnvWithRetro("VPN_INTERFACE", "WIREGUARD_INTERFACE")
|
_, wireguard.Interface = s.getEnvWithRetro("VPN_INTERFACE", "WIREGUARD_INTERFACE")
|
||||||
|
wireguard.Implementation = os.Getenv("WIREGUARD_IMPLEMENTATION")
|
||||||
wireguard.Addresses, err = s.readWireguardAddresses()
|
wireguard.Addresses, err = s.readWireguardAddresses()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wireguard, err // already wrapped
|
return wireguard, err // already wrapped
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ func readHTTPProxy() (settings settings.HTTPProxy, err error) {
|
|||||||
"/run/secrets/httpproxy_user",
|
"/run/secrets/httpproxy_user",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, fmt.Errorf("cannot read HTTP proxy user secret file: %w", err)
|
return settings, fmt.Errorf("reading HTTP proxy user secret file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.Password, err = readSecretFileAsStringPtr(
|
settings.Password, err = readSecretFileAsStringPtr(
|
||||||
@@ -20,7 +20,7 @@ func readHTTPProxy() (settings settings.HTTPProxy, err error) {
|
|||||||
"/run/secrets/httpproxy_password",
|
"/run/secrets/httpproxy_password",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, fmt.Errorf("cannot read OpenVPN password secret file: %w", err)
|
return settings, fmt.Errorf("reading OpenVPN password secret file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return settings, nil
|
return settings, nil
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ func readOpenVPN() (
|
|||||||
"/run/secrets/openvpn_user",
|
"/run/secrets/openvpn_user",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, fmt.Errorf("cannot read user file: %w", err)
|
return settings, fmt.Errorf("reading user file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.Password, err = readSecretFileAsStringPtr(
|
settings.Password, err = readSecretFileAsStringPtr(
|
||||||
@@ -21,7 +21,7 @@ func readOpenVPN() (
|
|||||||
"/run/secrets/openvpn_password",
|
"/run/secrets/openvpn_password",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, fmt.Errorf("cannot read password file: %w", err)
|
return settings, fmt.Errorf("reading password file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.Key, err = readPEMSecretFile(
|
settings.Key, err = readPEMSecretFile(
|
||||||
@@ -29,7 +29,7 @@ func readOpenVPN() (
|
|||||||
"/run/secrets/openvpn_clientkey",
|
"/run/secrets/openvpn_clientkey",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, fmt.Errorf("cannot read client key file: %w", err)
|
return settings, fmt.Errorf("reading client key file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.EncryptedKey, err = readPEMSecretFile(
|
settings.EncryptedKey, err = readPEMSecretFile(
|
||||||
@@ -53,7 +53,7 @@ func readOpenVPN() (
|
|||||||
"/run/secrets/openvpn_clientcrt",
|
"/run/secrets/openvpn_clientcrt",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, fmt.Errorf("cannot read client certificate file: %w", err)
|
return settings, fmt.Errorf("reading client certificate file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return settings, nil
|
return settings, nil
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ func readShadowsocks() (settings settings.Shadowsocks, err error) {
|
|||||||
"/run/secrets/shadowsocks_password",
|
"/run/secrets/shadowsocks_password",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, fmt.Errorf("cannot read Shadowsocks password secret file: %w", err)
|
return settings, fmt.Errorf("reading Shadowsocks password secret file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return settings, nil
|
return settings, nil
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
func readVPN() (vpn settings.VPN, err error) {
|
func readVPN() (vpn settings.VPN, err error) {
|
||||||
vpn.OpenVPN, err = readOpenVPN()
|
vpn.OpenVPN, err = readOpenVPN()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return vpn, fmt.Errorf("cannot read OpenVPN settings: %w", err)
|
return vpn, fmt.Errorf("reading OpenVPN settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return vpn, nil
|
return vpn, nil
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func (cm *cmdMatcher) String() string {
|
|||||||
return fmt.Sprintf("path %s, argument regular expressions %v", cm.path, cm.argsRegex)
|
return fmt.Sprintf("path %s, argument regular expressions %v", cm.path, cm.argsRegex)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCmdMatcher(path string, argsRegex ...string) *cmdMatcher { //nolint:unparam
|
func newCmdMatcher(path string, argsRegex ...string) *cmdMatcher {
|
||||||
argsRegexp := make([]*regexp.Regexp, len(argsRegex))
|
argsRegexp := make([]*regexp.Regexp, len(argsRegex))
|
||||||
for i, argRegex := range argsRegex {
|
for i, argRegex := range argsRegex {
|
||||||
argsRegexp[i] = regexp.MustCompile(argRegex)
|
argsRegexp[i] = regexp.MustCompile(argRegex)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func (c *Config) SetEnabled(ctx context.Context, enabled bool) (err error) {
|
|||||||
if !enabled {
|
if !enabled {
|
||||||
c.logger.Info("disabling...")
|
c.logger.Info("disabling...")
|
||||||
if err = c.disable(ctx); err != nil {
|
if err = c.disable(ctx); err != nil {
|
||||||
return fmt.Errorf("cannot disable firewall: %w", err)
|
return fmt.Errorf("disabling firewall: %w", err)
|
||||||
}
|
}
|
||||||
c.enabled = false
|
c.enabled = false
|
||||||
c.logger.Info("disabled successfully")
|
c.logger.Info("disabled successfully")
|
||||||
@@ -31,7 +31,7 @@ func (c *Config) SetEnabled(ctx context.Context, enabled bool) (err error) {
|
|||||||
c.logger.Info("enabling...")
|
c.logger.Info("enabling...")
|
||||||
|
|
||||||
if err := c.enable(ctx); err != nil {
|
if err := c.enable(ctx); err != nil {
|
||||||
return fmt.Errorf("cannot enable firewall: %w", err)
|
return fmt.Errorf("enabling firewall: %w", err)
|
||||||
}
|
}
|
||||||
c.enabled = true
|
c.enabled = true
|
||||||
c.logger.Info("enabled successfully")
|
c.logger.Info("enabled successfully")
|
||||||
@@ -41,13 +41,13 @@ func (c *Config) SetEnabled(ctx context.Context, enabled bool) (err error) {
|
|||||||
|
|
||||||
func (c *Config) disable(ctx context.Context) (err error) {
|
func (c *Config) disable(ctx context.Context) (err error) {
|
||||||
if err = c.clearAllRules(ctx); err != nil {
|
if err = c.clearAllRules(ctx); err != nil {
|
||||||
return fmt.Errorf("cannot clear all rules: %w", err)
|
return fmt.Errorf("clearing all rules: %w", err)
|
||||||
}
|
}
|
||||||
if err = c.setIPv4AllPolicies(ctx, "ACCEPT"); err != nil {
|
if err = c.setIPv4AllPolicies(ctx, "ACCEPT"); err != nil {
|
||||||
return fmt.Errorf("cannot set ipv4 policies: %w", err)
|
return fmt.Errorf("setting ipv4 policies: %w", err)
|
||||||
}
|
}
|
||||||
if err = c.setIPv6AllPolicies(ctx, "ACCEPT"); err != nil {
|
if err = c.setIPv6AllPolicies(ctx, "ACCEPT"); err != nil {
|
||||||
return fmt.Errorf("cannot set ipv6 policies: %w", err)
|
return fmt.Errorf("setting ipv6 policies: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -123,7 +123,7 @@ func (c *Config) enable(ctx context.Context) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := c.runUserPostRules(ctx, c.customRulesPath, remove); err != nil {
|
if err := c.runUserPostRules(ctx, c.customRulesPath, remove); err != nil {
|
||||||
return fmt.Errorf("cannot run user defined post firewall rules: %w", err)
|
return fmt.Errorf("running user defined post firewall rules: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -138,7 +138,7 @@ func (c *Config) allowVPNIP(ctx context.Context) (err error) {
|
|||||||
for _, defaultRoute := range c.defaultRoutes {
|
for _, defaultRoute := range c.defaultRoutes {
|
||||||
err = c.acceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, c.vpnConnection, remove)
|
err = c.acceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, c.vpnConnection, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot accept output traffic through VPN: %w", err)
|
return fmt.Errorf("accepting output traffic through VPN: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,7 +165,7 @@ func (c *Config) allowInputPorts(ctx context.Context) (err error) {
|
|||||||
const remove = false
|
const remove = false
|
||||||
err = c.acceptInputToPort(ctx, netInterface, port, remove)
|
err = c.acceptInputToPort(ctx, netInterface, port, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot accept input port %d on interface %s: %w",
|
return fmt.Errorf("accepting input port %d on interface %s: %w",
|
||||||
port, netInterface, err)
|
port, netInterface, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ func (c *Config) runUserPostRules(ctx context.Context, filepath string, remove b
|
|||||||
case ipv4:
|
case ipv4:
|
||||||
err = c.runIptablesInstruction(ctx, rule)
|
err = c.runIptablesInstruction(ctx, rule)
|
||||||
case c.ip6Tables == "":
|
case c.ip6Tables == "":
|
||||||
err = fmt.Errorf("cannot run user ip6tables rule: %w", ErrNeedIP6Tables)
|
err = fmt.Errorf("running user ip6tables rule: %w", ErrNeedIP6Tables)
|
||||||
default: // ipv6
|
default: // ipv6
|
||||||
err = c.runIP6tablesInstruction(ctx, rule)
|
err = c.runIP6tablesInstruction(ctx, rule)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func (c *Config) SetOutboundSubnets(ctx context.Context, subnets []net.IPNet) (e
|
|||||||
|
|
||||||
c.removeOutboundSubnets(ctx, subnetsToRemove)
|
c.removeOutboundSubnets(ctx, subnetsToRemove)
|
||||||
if err := c.addOutboundSubnets(ctx, subnetsToAdd); err != nil {
|
if err := c.addOutboundSubnets(ctx, subnetsToAdd); err != nil {
|
||||||
return fmt.Errorf("cannot set allowed outbound subnets: %w", err)
|
return fmt.Errorf("setting allowed outbound subnets: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -36,10 +36,11 @@ func (c *Config) SetAllowedPort(ctx context.Context, port uint16, intf string) (
|
|||||||
|
|
||||||
const remove = false
|
const remove = false
|
||||||
if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil {
|
if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil {
|
||||||
return fmt.Errorf("cannot allow input to port %d through interface %s: %w",
|
return fmt.Errorf("allowing input to port %d through interface %s: %w",
|
||||||
port, intf, err)
|
port, intf, err)
|
||||||
}
|
}
|
||||||
netInterfaces[intf] = struct{}{}
|
netInterfaces[intf] = struct{}{}
|
||||||
|
c.allowedInputPorts[port] = netInterfaces
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -69,7 +70,7 @@ func (c *Config) RemoveAllowedPort(ctx context.Context, port uint16) (err error)
|
|||||||
for netInterface := range interfacesSet {
|
for netInterface := range interfacesSet {
|
||||||
err := c.acceptInputToPort(ctx, netInterface, port, remove)
|
err := c.acceptInputToPort(ctx, netInterface, port, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot remove allowed port %d on interface %s: %w",
|
return fmt.Errorf("removing allowed port %d on interface %s: %w",
|
||||||
port, netInterface, err)
|
port, netInterface, err)
|
||||||
}
|
}
|
||||||
delete(interfacesSet, netInterface)
|
delete(interfacesSet, netInterface)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/golibs/command"
|
"github.com/qdm12/golibs/command"
|
||||||
@@ -20,7 +21,7 @@ var (
|
|||||||
|
|
||||||
func checkIptablesSupport(ctx context.Context, runner command.Runner,
|
func checkIptablesSupport(ctx context.Context, runner command.Runner,
|
||||||
iptablesPathsToTry ...string) (iptablesPath string, err error) {
|
iptablesPathsToTry ...string) (iptablesPath string, err error) {
|
||||||
var lastUnsupportedMessage string
|
iptablesPathToUnsupportedMessage := make(map[string]string, len(iptablesPathsToTry))
|
||||||
for _, pathToTest := range iptablesPathsToTry {
|
for _, pathToTest := range iptablesPathsToTry {
|
||||||
ok, unsupportedMessage, err := testIptablesPath(ctx, pathToTest, runner)
|
ok, unsupportedMessage, err := testIptablesPath(ctx, pathToTest, runner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -29,19 +30,37 @@ func checkIptablesSupport(ctx context.Context, runner command.Runner,
|
|||||||
iptablesPath = pathToTest
|
iptablesPath = pathToTest
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
iptablesPathToUnsupportedMessage[pathToTest] = unsupportedMessage
|
||||||
lastUnsupportedMessage = unsupportedMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
if iptablesPath == "" { // all iptables to try failed
|
|
||||||
return "", fmt.Errorf("%w: from %s: last error is: %s",
|
|
||||||
ErrIPTablesNotSupported, strings.Join(iptablesPathsToTry, ", "),
|
|
||||||
lastUnsupportedMessage)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if iptablesPath != "" {
|
||||||
|
// some paths may be unsupported but that does not matter
|
||||||
|
// since we found one working.
|
||||||
return iptablesPath, nil
|
return iptablesPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allArePermissionDenied := true
|
||||||
|
allUnsupportedMessages := make(sort.StringSlice, 0, len(iptablesPathToUnsupportedMessage))
|
||||||
|
for iptablesPath, unsupportedMessage := range iptablesPathToUnsupportedMessage {
|
||||||
|
if !isPermissionDenied(unsupportedMessage) {
|
||||||
|
allArePermissionDenied = false
|
||||||
|
}
|
||||||
|
unsupportedMessage = iptablesPath + ": " + unsupportedMessage
|
||||||
|
allUnsupportedMessages = append(allUnsupportedMessages, unsupportedMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
allUnsupportedMessages.Sort() // predictable order for tests
|
||||||
|
|
||||||
|
if allArePermissionDenied {
|
||||||
|
// If the error is related to a denied permission for all iptables path,
|
||||||
|
// return an error describing what to do from an end-user perspective.
|
||||||
|
return "", fmt.Errorf("%w: %s", ErrNetAdminMissing, strings.Join(allUnsupportedMessages, "; "))
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("%w: errors encountered are: %s",
|
||||||
|
ErrIPTablesNotSupported, strings.Join(allUnsupportedMessages, "; "))
|
||||||
|
}
|
||||||
|
|
||||||
func testIptablesPath(ctx context.Context, path string,
|
func testIptablesPath(ctx context.Context, path string,
|
||||||
runner command.Runner) (ok bool, unsupportedMessage string,
|
runner command.Runner) (ok bool, unsupportedMessage string,
|
||||||
criticalErr error) {
|
criticalErr error) {
|
||||||
@@ -56,14 +75,6 @@ func testIptablesPath(ctx context.Context, path string,
|
|||||||
"-A", "OUTPUT", "-o", testInterfaceName, "-j", "DROP")
|
"-A", "OUTPUT", "-o", testInterfaceName, "-j", "DROP")
|
||||||
output, err := runner.Run(cmd)
|
output, err := runner.Run(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isPermissionDenied(output) {
|
|
||||||
// If the error is related to a denied permission,
|
|
||||||
// return an error describing what to do from an end-user
|
|
||||||
// perspective. This is a critical error and likely
|
|
||||||
// applies to all iptables.
|
|
||||||
criticalErr = fmt.Errorf("%w: %s", ErrNetAdminMissing, output)
|
|
||||||
return false, "", criticalErr
|
|
||||||
}
|
|
||||||
unsupportedMessage = fmt.Sprintf("%s (%s)", output, err)
|
unsupportedMessage = fmt.Sprintf("%s (%s)", output, err)
|
||||||
return false, unsupportedMessage, nil
|
return false, unsupportedMessage, nil
|
||||||
}
|
}
|
||||||
@@ -84,10 +95,6 @@ func testIptablesPath(ctx context.Context, path string,
|
|||||||
cmd = exec.CommandContext(ctx, path, "-L", "INPUT")
|
cmd = exec.CommandContext(ctx, path, "-L", "INPUT")
|
||||||
output, err = runner.Run(cmd)
|
output, err = runner.Run(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isPermissionDenied(output) {
|
|
||||||
criticalErr = fmt.Errorf("%w: %s", ErrNetAdminMissing, output)
|
|
||||||
return false, "", criticalErr
|
|
||||||
}
|
|
||||||
unsupportedMessage = fmt.Sprintf("%s (%s)", output, err)
|
unsupportedMessage = fmt.Sprintf("%s (%s)", output, err)
|
||||||
return false, unsupportedMessage, nil
|
return false, unsupportedMessage, nil
|
||||||
}
|
}
|
||||||
@@ -109,10 +116,6 @@ func testIptablesPath(ctx context.Context, path string,
|
|||||||
cmd = exec.CommandContext(ctx, path, "--policy", "INPUT", inputPolicy)
|
cmd = exec.CommandContext(ctx, path, "--policy", "INPUT", inputPolicy)
|
||||||
output, err = runner.Run(cmd)
|
output, err = runner.Run(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isPermissionDenied(output) {
|
|
||||||
criticalErr = fmt.Errorf("%w: %s", ErrNetAdminMissing, output)
|
|
||||||
return false, "", criticalErr
|
|
||||||
}
|
|
||||||
unsupportedMessage = fmt.Sprintf("%s (%s)", output, err)
|
unsupportedMessage = fmt.Sprintf("%s (%s)", output, err)
|
||||||
return false, unsupportedMessage, nil
|
return false, unsupportedMessage, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,130 @@ import (
|
|||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/qdm12/golibs/command"
|
"github.com/qdm12/golibs/command"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate mockgen -destination=runner_mock_test.go -package $GOPACKAGE github.com/qdm12/golibs/command Runner
|
//go:generate mockgen -destination=runner_mock_test.go -package $GOPACKAGE github.com/qdm12/golibs/command Runner
|
||||||
|
|
||||||
|
func newAppendTestRuleMatcher(path string) *cmdMatcher {
|
||||||
|
return newCmdMatcher(path,
|
||||||
|
"^-A$", "^OUTPUT$", "^-o$", "^[a-z0-9]{15}$",
|
||||||
|
"^-j$", "^DROP$")
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDeleteTestRuleMatcher(path string) *cmdMatcher {
|
||||||
|
return newCmdMatcher(path,
|
||||||
|
"^-D$", "^OUTPUT$", "^-o$", "^[a-z0-9]{15}$",
|
||||||
|
"^-j$", "^DROP$")
|
||||||
|
}
|
||||||
|
|
||||||
|
func newListInputRulesMatcher(path string) *cmdMatcher {
|
||||||
|
return newCmdMatcher(path,
|
||||||
|
"^-L$", "^INPUT$")
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSetPolicyMatcher(path, inputPolicy string) *cmdMatcher { //nolint:unparam
|
||||||
|
return newCmdMatcher(path,
|
||||||
|
"^--policy$", "^INPUT$", "^"+inputPolicy+"$")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_checkIptablesSupport(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
errDummy := errors.New("exit code 4")
|
||||||
|
const inputPolicy = "ACCEPT"
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
buildRunner func(ctrl *gomock.Controller) command.Runner
|
||||||
|
iptablesPathsToTry []string
|
||||||
|
iptablesPath string
|
||||||
|
errSentinel error
|
||||||
|
errMessage string
|
||||||
|
}{
|
||||||
|
"critical error when checking": {
|
||||||
|
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
||||||
|
runner := NewMockRunner(ctrl)
|
||||||
|
runner.EXPECT().Run(newAppendTestRuleMatcher("path1")).
|
||||||
|
Return("", nil)
|
||||||
|
runner.EXPECT().Run(newDeleteTestRuleMatcher("path1")).
|
||||||
|
Return("output", errDummy)
|
||||||
|
return runner
|
||||||
|
},
|
||||||
|
iptablesPathsToTry: []string{"path1", "path2"},
|
||||||
|
errSentinel: ErrTestRuleCleanup,
|
||||||
|
errMessage: "for path1: failed cleaning up test rule: " +
|
||||||
|
"output (exit code 4)",
|
||||||
|
},
|
||||||
|
"found valid path": {
|
||||||
|
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
||||||
|
runner := NewMockRunner(ctrl)
|
||||||
|
runner.EXPECT().Run(newAppendTestRuleMatcher("path1")).
|
||||||
|
Return("", nil)
|
||||||
|
runner.EXPECT().Run(newDeleteTestRuleMatcher("path1")).
|
||||||
|
Return("", nil)
|
||||||
|
runner.EXPECT().Run(newListInputRulesMatcher("path1")).
|
||||||
|
Return("Chain INPUT (policy "+inputPolicy+")", nil)
|
||||||
|
runner.EXPECT().Run(newSetPolicyMatcher("path1", inputPolicy)).
|
||||||
|
Return("", nil)
|
||||||
|
return runner
|
||||||
|
},
|
||||||
|
iptablesPathsToTry: []string{"path1", "path2"},
|
||||||
|
iptablesPath: "path1",
|
||||||
|
},
|
||||||
|
"all permission denied": {
|
||||||
|
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
||||||
|
runner := NewMockRunner(ctrl)
|
||||||
|
runner.EXPECT().Run(newAppendTestRuleMatcher("path1")).
|
||||||
|
Return("Permission denied (you must be root) more context", errDummy)
|
||||||
|
runner.EXPECT().Run(newAppendTestRuleMatcher("path2")).
|
||||||
|
Return("context: Permission denied (you must be root)", errDummy)
|
||||||
|
return runner
|
||||||
|
},
|
||||||
|
iptablesPathsToTry: []string{"path1", "path2"},
|
||||||
|
errSentinel: ErrNetAdminMissing,
|
||||||
|
errMessage: "NET_ADMIN capability is missing: " +
|
||||||
|
"path1: Permission denied (you must be root) more context (exit code 4); " +
|
||||||
|
"path2: context: Permission denied (you must be root) (exit code 4)",
|
||||||
|
},
|
||||||
|
"no valid path": {
|
||||||
|
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
||||||
|
runner := NewMockRunner(ctrl)
|
||||||
|
runner.EXPECT().Run(newAppendTestRuleMatcher("path1")).
|
||||||
|
Return("output 1", errDummy)
|
||||||
|
runner.EXPECT().Run(newAppendTestRuleMatcher("path2")).
|
||||||
|
Return("output 2", errDummy)
|
||||||
|
return runner
|
||||||
|
},
|
||||||
|
iptablesPathsToTry: []string{"path1", "path2"},
|
||||||
|
errSentinel: ErrIPTablesNotSupported,
|
||||||
|
errMessage: "no iptables supported found: " +
|
||||||
|
"errors encountered are: " +
|
||||||
|
"path1: output 1 (exit code 4); " +
|
||||||
|
"path2: output 2 (exit code 4)",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
|
||||||
|
runner := testCase.buildRunner(ctrl)
|
||||||
|
|
||||||
|
iptablesPath, err :=
|
||||||
|
checkIptablesSupport(ctx, runner, testCase.iptablesPathsToTry...)
|
||||||
|
|
||||||
|
require.ErrorIs(t, err, testCase.errSentinel)
|
||||||
|
if testCase.errSentinel != nil {
|
||||||
|
assert.EqualError(t, err, testCase.errMessage)
|
||||||
|
}
|
||||||
|
assert.Equal(t, testCase.iptablesPath, iptablesPath)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Test_testIptablesPath(t *testing.T) {
|
func Test_testIptablesPath(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -20,17 +140,6 @@ func Test_testIptablesPath(t *testing.T) {
|
|||||||
errDummy := errors.New("exit code 4")
|
errDummy := errors.New("exit code 4")
|
||||||
const inputPolicy = "ACCEPT"
|
const inputPolicy = "ACCEPT"
|
||||||
|
|
||||||
appendTestRuleMatcher := newCmdMatcher(path,
|
|
||||||
"^-A$", "^OUTPUT$", "^-o$", "^[a-z0-9]{15}$",
|
|
||||||
"^-j$", "^DROP$")
|
|
||||||
deleteTestRuleMatcher := newCmdMatcher(path,
|
|
||||||
"^-D$", "^OUTPUT$", "^-o$", "^[a-z0-9]{15}$",
|
|
||||||
"^-j$", "^DROP$")
|
|
||||||
listInputRulesMatcher := newCmdMatcher(path,
|
|
||||||
"^-L$", "^INPUT$")
|
|
||||||
setPolicyMatcher := newCmdMatcher(path,
|
|
||||||
"^--policy$", "^INPUT$", "^"+inputPolicy+"$")
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
buildRunner func(ctrl *gomock.Controller) command.Runner
|
buildRunner func(ctrl *gomock.Controller) command.Runner
|
||||||
ok bool
|
ok bool
|
||||||
@@ -41,18 +150,16 @@ func Test_testIptablesPath(t *testing.T) {
|
|||||||
"append test rule permission denied": {
|
"append test rule permission denied": {
|
||||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockRunner(ctrl)
|
||||||
runner.EXPECT().Run(appendTestRuleMatcher).
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).
|
||||||
Return("Permission denied (you must be root)", errDummy)
|
Return("Permission denied (you must be root)", errDummy)
|
||||||
return runner
|
return runner
|
||||||
},
|
},
|
||||||
criticalErrWrapped: ErrNetAdminMissing,
|
unsupportedMessage: "Permission denied (you must be root) (exit code 4)",
|
||||||
criticalErrMessage: "NET_ADMIN capability is missing: " +
|
|
||||||
"Permission denied (you must be root)",
|
|
||||||
},
|
},
|
||||||
"append test rule unsupported": {
|
"append test rule unsupported": {
|
||||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockRunner(ctrl)
|
||||||
runner.EXPECT().Run(appendTestRuleMatcher).
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).
|
||||||
Return("some output", errDummy)
|
Return("some output", errDummy)
|
||||||
return runner
|
return runner
|
||||||
},
|
},
|
||||||
@@ -61,8 +168,8 @@ func Test_testIptablesPath(t *testing.T) {
|
|||||||
"remove test rule error": {
|
"remove test rule error": {
|
||||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockRunner(ctrl)
|
||||||
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(deleteTestRuleMatcher).
|
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).
|
||||||
Return("some output", errDummy)
|
Return("some output", errDummy)
|
||||||
return runner
|
return runner
|
||||||
},
|
},
|
||||||
@@ -72,22 +179,20 @@ func Test_testIptablesPath(t *testing.T) {
|
|||||||
"list input rules permission denied": {
|
"list input rules permission denied": {
|
||||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockRunner(ctrl)
|
||||||
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
|
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(listInputRulesMatcher).
|
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
||||||
Return("Permission denied (you must be root)", errDummy)
|
Return("Permission denied (you must be root)", errDummy)
|
||||||
return runner
|
return runner
|
||||||
},
|
},
|
||||||
criticalErrWrapped: ErrNetAdminMissing,
|
unsupportedMessage: "Permission denied (you must be root) (exit code 4)",
|
||||||
criticalErrMessage: "NET_ADMIN capability is missing: " +
|
|
||||||
"Permission denied (you must be root)",
|
|
||||||
},
|
},
|
||||||
"list input rules unsupported": {
|
"list input rules unsupported": {
|
||||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockRunner(ctrl)
|
||||||
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
|
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(listInputRulesMatcher).
|
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
||||||
Return("some output", errDummy)
|
Return("some output", errDummy)
|
||||||
return runner
|
return runner
|
||||||
},
|
},
|
||||||
@@ -96,9 +201,9 @@ func Test_testIptablesPath(t *testing.T) {
|
|||||||
"list input rules no policy": {
|
"list input rules no policy": {
|
||||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockRunner(ctrl)
|
||||||
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
|
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(listInputRulesMatcher).
|
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
||||||
Return("some\noutput", nil)
|
Return("some\noutput", nil)
|
||||||
return runner
|
return runner
|
||||||
},
|
},
|
||||||
@@ -108,26 +213,24 @@ func Test_testIptablesPath(t *testing.T) {
|
|||||||
"set policy permission denied": {
|
"set policy permission denied": {
|
||||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockRunner(ctrl)
|
||||||
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
|
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(listInputRulesMatcher).
|
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
||||||
Return("\nChain INPUT (policy "+inputPolicy+")\nxx\n", nil)
|
Return("\nChain INPUT (policy "+inputPolicy+")\nxx\n", nil)
|
||||||
runner.EXPECT().Run(setPolicyMatcher).
|
runner.EXPECT().Run(newSetPolicyMatcher(path, inputPolicy)).
|
||||||
Return("Permission denied (you must be root)", errDummy)
|
Return("Permission denied (you must be root)", errDummy)
|
||||||
return runner
|
return runner
|
||||||
},
|
},
|
||||||
criticalErrWrapped: ErrNetAdminMissing,
|
unsupportedMessage: "Permission denied (you must be root) (exit code 4)",
|
||||||
criticalErrMessage: "NET_ADMIN capability is missing: " +
|
|
||||||
"Permission denied (you must be root)",
|
|
||||||
},
|
},
|
||||||
"set policy unsupported": {
|
"set policy unsupported": {
|
||||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockRunner(ctrl)
|
||||||
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
|
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(listInputRulesMatcher).
|
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
||||||
Return("\nChain INPUT (policy "+inputPolicy+")\nxx\n", nil)
|
Return("\nChain INPUT (policy "+inputPolicy+")\nxx\n", nil)
|
||||||
runner.EXPECT().Run(setPolicyMatcher).
|
runner.EXPECT().Run(newSetPolicyMatcher(path, inputPolicy)).
|
||||||
Return("some output", errDummy)
|
Return("some output", errDummy)
|
||||||
return runner
|
return runner
|
||||||
},
|
},
|
||||||
@@ -136,11 +239,12 @@ func Test_testIptablesPath(t *testing.T) {
|
|||||||
"success": {
|
"success": {
|
||||||
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
buildRunner: func(ctrl *gomock.Controller) command.Runner {
|
||||||
runner := NewMockRunner(ctrl)
|
runner := NewMockRunner(ctrl)
|
||||||
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
|
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
|
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
|
||||||
runner.EXPECT().Run(listInputRulesMatcher).
|
runner.EXPECT().Run(newListInputRulesMatcher(path)).
|
||||||
Return("\nChain INPUT (policy "+inputPolicy+")\nxx\n", nil)
|
Return("\nChain INPUT (policy "+inputPolicy+")\nxx\n", nil)
|
||||||
runner.EXPECT().Run(setPolicyMatcher).Return("some output", nil)
|
runner.EXPECT().Run(newSetPolicyMatcher(path, inputPolicy)).
|
||||||
|
Return("some output", nil)
|
||||||
return runner
|
return runner
|
||||||
},
|
},
|
||||||
ok: true,
|
ok: true,
|
||||||
|
|||||||
@@ -45,13 +45,13 @@ func (c *Config) SetVPNConnection(ctx context.Context,
|
|||||||
|
|
||||||
for _, defaultRoute := range c.defaultRoutes {
|
for _, defaultRoute := range c.defaultRoutes {
|
||||||
if err := c.acceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, connection, remove); err != nil {
|
if err := c.acceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, connection, remove); err != nil {
|
||||||
return fmt.Errorf("cannot allow output traffic through VPN connection: %w", err)
|
return fmt.Errorf("allowing output traffic through VPN connection: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.vpnConnection = connection
|
c.vpnConnection = connection
|
||||||
|
|
||||||
if err = c.acceptOutputThroughInterface(ctx, vpnIntf, remove); err != nil {
|
if err = c.acceptOutputThroughInterface(ctx, vpnIntf, remove); err != nil {
|
||||||
return fmt.Errorf("cannot accept output traffic through interface %s: %w", vpnIntf, err)
|
return fmt.Errorf("accepting output traffic through interface %s: %w", vpnIntf, err)
|
||||||
}
|
}
|
||||||
c.vpnIntf = vpnIntf
|
c.vpnIntf = vpnIntf
|
||||||
|
|
||||||
|
|||||||
@@ -74,12 +74,12 @@ func (s *Server) healthCheck(ctx context.Context) (err error) {
|
|||||||
const dialNetwork = "tcp4"
|
const dialNetwork = "tcp4"
|
||||||
connection, err := s.dialer.DialContext(ctx, dialNetwork, address)
|
connection, err := s.dialer.DialContext(ctx, dialNetwork, address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot dial: %w", err)
|
return fmt.Errorf("dialing: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = connection.Close()
|
err = connection.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot close connection: %w", err)
|
return fmt.Errorf("closing connection: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -91,7 +91,7 @@ func makeAddressToDial(address string) (addressToDial string, err error) {
|
|||||||
addrErr := new(net.AddrError)
|
addrErr := new(net.AddrError)
|
||||||
ok := errors.As(err, &addrErr)
|
ok := errors.As(err, &addrErr)
|
||||||
if !ok || addrErr.Err != "missing port in address" {
|
if !ok || addrErr.Err != "missing port in address" {
|
||||||
return "", fmt.Errorf("cannot split host and port from address: %w", err)
|
return "", fmt.Errorf("splitting host and port from address: %w", err)
|
||||||
}
|
}
|
||||||
host = address
|
host = address
|
||||||
const defaultPort = "443"
|
const defaultPort = "443"
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ func Test_makeAddressToDial(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"bad address": {
|
"bad address": {
|
||||||
address: "test.com::",
|
address: "test.com::",
|
||||||
err: fmt.Errorf("cannot split host and port from address: address test.com::: too many colons in address"), //nolint:lll
|
err: fmt.Errorf("splitting host and port from address: address test.com::: too many colons in address"), //nolint:lll
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ type vpnHealth struct {
|
|||||||
|
|
||||||
func (s *Server) onUnhealthyVPN(ctx context.Context) {
|
func (s *Server) onUnhealthyVPN(ctx context.Context) {
|
||||||
s.logger.Info("program has been unhealthy for " +
|
s.logger.Info("program has been unhealthy for " +
|
||||||
s.vpn.healthyWait.String() + ": restarting VPN")
|
s.vpn.healthyWait.String() + ": restarting VPN " +
|
||||||
|
"(see https://github.com/qdm12/gluetun/wiki/Healthcheck)")
|
||||||
_, _ = s.vpn.loop.ApplyStatus(ctx, constants.Stopped)
|
_, _ = s.vpn.loop.ApplyStatus(ctx, constants.Stopped)
|
||||||
_, _ = s.vpn.loop.ApplyStatus(ctx, constants.Running)
|
_, _ = s.vpn.loop.ApplyStatus(ctx, constants.Running)
|
||||||
s.vpn.healthyWait += *s.config.VPN.Addition
|
s.vpn.healthyWait += *s.config.VPN.Addition
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func (s *Server) Run(ctx context.Context, errorCh chan<- error) {
|
|||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
const shutdownGraceDuration = 2 * time.Second
|
const shutdownGraceDuration = 100 * time.Millisecond
|
||||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownGraceDuration)
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownGraceDuration)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if err := server.Shutdown(shutdownCtx); err != nil {
|
if err := server.Shutdown(shutdownCtx); err != nil {
|
||||||
|
|||||||
@@ -27,13 +27,13 @@ func (a *AllServers) MarshalJSON() (data []byte, err error) {
|
|||||||
|
|
||||||
_, err = buffer.WriteString("{")
|
_, err = buffer.WriteString("{")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot write opening bracket: %w", err)
|
return nil, fmt.Errorf("writing opening bracket: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
versionString := fmt.Sprintf(`"version":%d`, a.Version)
|
versionString := fmt.Sprintf(`"version":%d`, a.Version)
|
||||||
_, err = buffer.WriteString(versionString)
|
_, err = buffer.WriteString(versionString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot write schema version string: %w", err)
|
return nil, fmt.Errorf("writing schema version string: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sortedProviders := make(sort.StringSlice, 0, len(a.ProviderToServers))
|
sortedProviders := make(sort.StringSlice, 0, len(a.ProviderToServers))
|
||||||
@@ -46,26 +46,26 @@ func (a *AllServers) MarshalJSON() (data []byte, err error) {
|
|||||||
providerKey := fmt.Sprintf(`,"%s":`, provider)
|
providerKey := fmt.Sprintf(`,"%s":`, provider)
|
||||||
_, err = buffer.WriteString(providerKey)
|
_, err = buffer.WriteString(providerKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot write provider key %s: %w",
|
return nil, fmt.Errorf("writing provider key %s: %w",
|
||||||
providerKey, err)
|
providerKey, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
servers := a.ProviderToServers[provider]
|
servers := a.ProviderToServers[provider]
|
||||||
serversJSON, err := json.Marshal(servers)
|
serversJSON, err := json.Marshal(servers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed encoding servers for provider %s: %w",
|
return nil, fmt.Errorf("encoding servers for provider %s: %w",
|
||||||
provider, err)
|
provider, err)
|
||||||
}
|
}
|
||||||
_, err = buffer.Write(serversJSON)
|
_, err = buffer.Write(serversJSON)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot write JSON servers data for provider %s: %w",
|
return nil, fmt.Errorf("writing JSON servers data for provider %s: %w",
|
||||||
provider, err)
|
provider, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = buffer.WriteString("}")
|
_, err = buffer.WriteString("}")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot write closing bracket: %w", err)
|
return nil, fmt.Errorf("writing closing bracket: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer.Bytes(), nil
|
return buffer.Bytes(), nil
|
||||||
@@ -127,14 +127,14 @@ func (a *AllServers) UnmarshalJSON(data []byte) (err error) {
|
|||||||
|
|
||||||
jsonValue, err := json.Marshal(value)
|
jsonValue, err := json.Marshal(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot marshal %s servers: %w",
|
return fmt.Errorf("encoding %s servers: %w",
|
||||||
key, err)
|
key, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var servers Servers
|
var servers Servers
|
||||||
err = json.Unmarshal(jsonValue, &servers)
|
err = json.Unmarshal(jsonValue, &servers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot unmarshal %s servers: %w",
|
return fmt.Errorf("decoding %s servers: %w",
|
||||||
key, err)
|
key, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const (
|
|||||||
func (n *NetLink) IsWireguardSupported() (ok bool, err error) {
|
func (n *NetLink) IsWireguardSupported() (ok bool, err error) {
|
||||||
families, err := netlink.GenlFamilyList()
|
families, err := netlink.GenlFamilyList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("cannot list gen 1 families: %w", err)
|
return false, fmt.Errorf("listing gen 1 families: %w", err)
|
||||||
}
|
}
|
||||||
for _, family := range families {
|
for _, family := range families {
|
||||||
if family.Name == "wireguard" {
|
if family.Name == "wireguard" {
|
||||||
|
|||||||
8
internal/netlink/interfaces.go
Normal file
8
internal/netlink/interfaces.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package netlink
|
||||||
|
|
||||||
|
import "github.com/qdm12/log"
|
||||||
|
|
||||||
|
type DebugLogger interface {
|
||||||
|
Debugf(format string, args ...any)
|
||||||
|
Patch(options ...log.Option)
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ func (n *NetLink) IsIPv6Supported() (supported bool, err error) {
|
|||||||
return false, fmt.Errorf("listing links: %w", err)
|
return false, fmt.Errorf("listing links: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var totalRoutes uint
|
||||||
for _, link := range links {
|
for _, link := range links {
|
||||||
routes, err := n.RouteList(link, netlink.FAMILY_V6)
|
routes, err := n.RouteList(link, netlink.FAMILY_V6)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -19,12 +20,21 @@ func (n *NetLink) IsIPv6Supported() (supported bool, err error) {
|
|||||||
link.Attrs().Name, err)
|
link.Attrs().Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(routes) == 0 {
|
// Check each route for IPv6 due to Podman bug listing IPv4 routes
|
||||||
continue
|
// as IPv6 routes at container start, see:
|
||||||
}
|
// https://github.com/qdm12/gluetun/issues/1241#issuecomment-1333405949
|
||||||
|
for _, route := range routes {
|
||||||
|
sourceIsIPv6 := route.Src != nil && route.Src.To4() == nil
|
||||||
|
destinationIsIPv6 := route.Dst != nil && route.Dst.IP.To4() == nil
|
||||||
|
if sourceIsIPv6 || destinationIsIPv6 {
|
||||||
|
n.debugLogger.Debugf("IPv6 is supported by link %s", link.Attrs().Name)
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
totalRoutes++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n.debugLogger.Debugf("IPv6 is not supported after searching %d links and %d routes",
|
||||||
|
len(links), totalRoutes)
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
package netlink
|
package netlink
|
||||||
|
|
||||||
type NetLink struct{}
|
import "github.com/qdm12/log"
|
||||||
|
|
||||||
func New() *NetLink {
|
type NetLink struct {
|
||||||
return &NetLink{}
|
debugLogger DebugLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(debugLogger DebugLogger) *NetLink {
|
||||||
|
return &NetLink{
|
||||||
|
debugLogger: debugLogger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetLink) PatchLoggerLevel(level log.Level) {
|
||||||
|
n.debugLogger.Patch(log.SetLevel(level))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ func (e *Extractor) Data(filepath string) (lines []string,
|
|||||||
connection models.Connection, err error) {
|
connection models.Connection, err error) {
|
||||||
lines, err = readCustomConfigLines(filepath)
|
lines, err = readCustomConfigLines(filepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, connection, fmt.Errorf("cannot read configuration file: %w", err)
|
return nil, connection, fmt.Errorf("reading configuration file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
connection, err = extractDataFromLines(lines)
|
connection, err = extractDataFromLines(lines)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, connection, fmt.Errorf("cannot extract connection from file: %w", err)
|
return nil, connection, fmt.Errorf("extracting connection from file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines, connection, nil
|
return lines, connection, nil
|
||||||
|
|||||||
@@ -54,14 +54,14 @@ func extractDataFromLine(line string) (
|
|||||||
case strings.HasPrefix(line, "proto "):
|
case strings.HasPrefix(line, "proto "):
|
||||||
protocol, err = extractProto(line)
|
protocol, err = extractProto(line)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, "", fmt.Errorf("failed extracting protocol from proto line: %w", err)
|
return nil, 0, "", fmt.Errorf("extracting protocol from proto line: %w", err)
|
||||||
}
|
}
|
||||||
return nil, 0, protocol, nil
|
return nil, 0, protocol, nil
|
||||||
|
|
||||||
case strings.HasPrefix(line, "remote "):
|
case strings.HasPrefix(line, "remote "):
|
||||||
ip, port, protocol, err = extractRemote(line)
|
ip, port, protocol, err = extractRemote(line)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, "", fmt.Errorf("failed extracting from remote line: %w", err)
|
return nil, 0, "", fmt.Errorf("extracting from remote line: %w", err)
|
||||||
}
|
}
|
||||||
return ip, port, protocol, nil
|
return ip, port, protocol, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func Test_extractDataFromLines(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"extraction error": {
|
"extraction error": {
|
||||||
lines: []string{"bla bla", "proto bad", "remote 1.2.3.4 1194 tcp"},
|
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"),
|
err: errors.New("on line 2: extracting protocol from proto line: network protocol not supported: bad"),
|
||||||
},
|
},
|
||||||
"only use first values found": {
|
"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"},
|
lines: []string{"proto udp", "proto tcp", "remote 1.2.3.4 443 tcp", "remote 5.2.3.4 1194 udp"},
|
||||||
|
|||||||
@@ -1,33 +1,23 @@
|
|||||||
package extract
|
package extract
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"regexp"
|
"fmt"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errPEMDecode = errors.New("cannot decode PEM encoded block")
|
errPEMDecode = errors.New("cannot decode PEM encoded block")
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
regexPEMBegin = regexp.MustCompile(`-----BEGIN [A-Za-z ]+-----`)
|
|
||||||
regexPEMEnd = regexp.MustCompile(`-----END [A-Za-z ]+-----`)
|
|
||||||
)
|
|
||||||
|
|
||||||
func PEM(b []byte) (encodedData string, err error) {
|
func PEM(b []byte) (encodedData string, err error) {
|
||||||
pemBlock, _ := pem.Decode(b)
|
pemBlock, _ := pem.Decode(b)
|
||||||
if pemBlock == nil {
|
if pemBlock == nil {
|
||||||
return "", errPEMDecode
|
return "", fmt.Errorf("%w", errPEMDecode)
|
||||||
}
|
}
|
||||||
|
|
||||||
encodedBytes := pem.EncodeToMemory(pemBlock)
|
der := pemBlock.Bytes
|
||||||
encodedData = string(encodedBytes)
|
encodedData = base64.StdEncoding.EncodeToString(der)
|
||||||
encodedData = strings.ReplaceAll(encodedData, "\n", "")
|
|
||||||
beginPrefix := regexPEMBegin.FindString(encodedData)
|
|
||||||
encodedData = strings.TrimPrefix(encodedData, beginPrefix)
|
|
||||||
endPrefix := regexPEMEnd.FindString(encodedData)
|
|
||||||
encodedData = strings.TrimSuffix(encodedData, endPrefix)
|
|
||||||
return encodedData, nil
|
return encodedData, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package extract
|
package extract
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -24,6 +25,14 @@ func Test_PEM(t *testing.T) {
|
|||||||
errWrapped: errPEMDecode,
|
errWrapped: errPEMDecode,
|
||||||
errMessage: "cannot decode PEM encoded block",
|
errMessage: "cannot decode PEM encoded block",
|
||||||
},
|
},
|
||||||
|
"valid data with extras": {
|
||||||
|
b: bytes.Join([][]byte{
|
||||||
|
{1, 2, 3},
|
||||||
|
[]byte(validCertPEM),
|
||||||
|
{4, 5, 6},
|
||||||
|
}, []byte("\n")),
|
||||||
|
encodedData: validCertData,
|
||||||
|
},
|
||||||
"valid data": {
|
"valid data": {
|
||||||
b: []byte(validCertPEM),
|
b: []byte(validCertPEM),
|
||||||
encodedData: validCertData,
|
encodedData: validCertData,
|
||||||
|
|||||||
@@ -65,6 +65,16 @@ That error usually happens because either:
|
|||||||
filtered = s
|
filtered = s
|
||||||
level = levelInfo
|
level = levelInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case filtered == "RTNETLINK answers: File exists":
|
||||||
|
filtered = "OpenVPN tried to add an IP route which already exists (" + filtered + ")"
|
||||||
|
level = levelWarn
|
||||||
|
case strings.HasPrefix(filtered, "Linux route add command failed: "):
|
||||||
|
filtered = "Previous error details: " + filtered
|
||||||
|
level = levelWarn
|
||||||
|
}
|
||||||
|
|
||||||
filtered = constants.ColorOpenvpn().Sprintf(filtered)
|
filtered = constants.ColorOpenvpn().Sprintf(filtered)
|
||||||
return filtered, level
|
return filtered, level
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,36 @@ func Test_processLogLine(t *testing.T) {
|
|||||||
"AUTH: Received control message: AUTH_FAILED",
|
"AUTH: Received control message: AUTH_FAILED",
|
||||||
"AUTH: Received control message: AUTH_FAILED\n\nYour credentials might be wrong 🤨\n\n",
|
"AUTH: Received control message: AUTH_FAILED\n\nYour credentials might be wrong 🤨\n\n",
|
||||||
levelError},
|
levelError},
|
||||||
|
"TLS key negotiation error": {
|
||||||
|
s: "TLS Error: TLS key negotiation failed to occur within " +
|
||||||
|
"60 seconds (check your network connectivity)",
|
||||||
|
filtered: "TLS Error: TLS key negotiation failed to occur within " +
|
||||||
|
"60 seconds (check your network connectivity)" + `
|
||||||
|
🚒🚒🚒🚒🚒🚨🚨🚨🚨🚨🚨🚒🚒🚒🚒🚒
|
||||||
|
That error usually happens because either:
|
||||||
|
|
||||||
|
1. The VPN server IP address you are trying to connect to is no longer valid 🔌
|
||||||
|
Update your server information using https://github.com/qdm12/gluetun/wiki/Updating-Servers
|
||||||
|
|
||||||
|
2. The VPN server crashed 💥, try changing your VPN servers filtering options such as SERVER_REGIONS
|
||||||
|
|
||||||
|
3. Your Internet connection is not working 🤯, ensure it works
|
||||||
|
|
||||||
|
4. Something else ➡️ https://github.com/qdm12/gluetun/issues/new/choose
|
||||||
|
`,
|
||||||
|
level: levelWarn,
|
||||||
|
},
|
||||||
|
"RTNETLINK answers: File exists": {
|
||||||
|
s: "ERROR: RTNETLINK answers: File exists",
|
||||||
|
filtered: "OpenVPN tried to add an IP route which already exists " +
|
||||||
|
"(RTNETLINK answers: File exists)",
|
||||||
|
level: levelWarn,
|
||||||
|
},
|
||||||
|
"Linux route add command failed": {
|
||||||
|
s: "ERROR: Linux route add command failed: some error",
|
||||||
|
filtered: "Previous error details: Linux route add command failed: some error",
|
||||||
|
level: levelWarn,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for name, tc := range tests {
|
for name, tc := range tests {
|
||||||
tc := tc
|
tc := tc
|
||||||
|
|||||||
53
internal/openvpn/pkcs8/algorithms.go
Normal file
53
internal/openvpn/pkcs8/algorithms.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package pkcs8
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/asn1"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Algorithm identifiers are listed at
|
||||||
|
// https://www.ibm.com/docs/en/zos/2.3.0?topic=programming-object-identifiers
|
||||||
|
oidDESCBC = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 7} //nolint:gochecknoglobals
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrEncryptionAlgorithmNotPBES2 = errors.New("encryption algorithm is not PBES2")
|
||||||
|
)
|
||||||
|
|
||||||
|
type encryptedPrivateKey struct {
|
||||||
|
EncryptionAlgorithm pkix.AlgorithmIdentifier
|
||||||
|
EncryptedData []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type encryptedAlgorithmParams struct {
|
||||||
|
KeyDerivationFunc pkix.AlgorithmIdentifier
|
||||||
|
EncryptionScheme pkix.AlgorithmIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEncryptionAlgorithmOid(der []byte) (
|
||||||
|
encryptionSchemeAlgorithm asn1.ObjectIdentifier, err error) {
|
||||||
|
var encryptedPrivateKeyData encryptedPrivateKey
|
||||||
|
_, err = asn1.Unmarshal(der, &encryptedPrivateKeyData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decoding asn1 encrypted private key data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
oidPBES2 := asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 13}
|
||||||
|
oidAlgorithm := encryptedPrivateKeyData.EncryptionAlgorithm.Algorithm
|
||||||
|
if !oidAlgorithm.Equal(oidPBES2) {
|
||||||
|
return nil, fmt.Errorf("%w: %s instead of PBES2 %s",
|
||||||
|
ErrEncryptionAlgorithmNotPBES2, oidAlgorithm, oidPBES2)
|
||||||
|
}
|
||||||
|
|
||||||
|
var encryptionAlgorithmParams encryptedAlgorithmParams
|
||||||
|
paramBytes := encryptedPrivateKeyData.EncryptionAlgorithm.Parameters.FullBytes
|
||||||
|
_, err = asn1.Unmarshal(paramBytes, &encryptionAlgorithmParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decoding asn1 encryption algorithm parameters: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return encryptionAlgorithmParams.EncryptionScheme.Algorithm, nil
|
||||||
|
}
|
||||||
106
internal/openvpn/pkcs8/algorithms_test.go
Normal file
106
internal/openvpn/pkcs8/algorithms_test.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package pkcs8
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/asn1"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
pkcs8lib "github.com/youmark/pkcs8"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_getEncryptionAlgorithmOid(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
makeDER func() (der []byte, err error)
|
||||||
|
encryptionSchemeAlgorithm asn1.ObjectIdentifier
|
||||||
|
errMessage string
|
||||||
|
}{
|
||||||
|
"empty data": {
|
||||||
|
makeDER: func() (der []byte, err error) { return nil, nil },
|
||||||
|
errMessage: "decoding asn1 encrypted private key data: " +
|
||||||
|
"asn1: syntax error: sequence truncated",
|
||||||
|
},
|
||||||
|
"algorithm not pbes2": {
|
||||||
|
makeDER: func() (der []byte, err error) {
|
||||||
|
data := encryptedPrivateKey{
|
||||||
|
EncryptionAlgorithm: pkix.AlgorithmIdentifier{
|
||||||
|
Algorithm: asn1.ObjectIdentifier{1, 2, 3, 4},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return asn1.Marshal(data)
|
||||||
|
},
|
||||||
|
errMessage: "encryption algorithm is not PBES2: " +
|
||||||
|
"1.2.3.4 instead of PBES2 1.2.840.113549.1.5.13",
|
||||||
|
},
|
||||||
|
"empty params full bytes": {
|
||||||
|
makeDER: func() (der []byte, err error) {
|
||||||
|
data := encryptedPrivateKey{
|
||||||
|
EncryptionAlgorithm: pkix.AlgorithmIdentifier{
|
||||||
|
Algorithm: asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 13},
|
||||||
|
Parameters: asn1.RawValue{
|
||||||
|
FullBytes: []byte{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return asn1.Marshal(data)
|
||||||
|
},
|
||||||
|
errMessage: "decoding asn1 encryption algorithm parameters: " +
|
||||||
|
"asn1: structure error: tags don't match " +
|
||||||
|
"(16 vs {class:0 tag:0 length:0 isCompound:false}) {optional:false explicit:false application:false private:false defaultValue:<nil> tag:<nil> stringType:0 timeType:0 set:false omitEmpty:false} encryptedAlgorithmParams @2", //nolint:lll
|
||||||
|
},
|
||||||
|
"DES-CBC DER": {
|
||||||
|
makeDER: func() (der []byte, err error) {
|
||||||
|
DESCBCEncryptedPEM, err := os.ReadFile("testdata/rsa_pkcs8_descbc_encrypted.pem")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("reading file: %w", err)
|
||||||
|
}
|
||||||
|
pemBlock, _ := pem.Decode(DESCBCEncryptedPEM)
|
||||||
|
if pemBlock == nil {
|
||||||
|
return nil, errors.New("failed to decode PEM")
|
||||||
|
}
|
||||||
|
return pemBlock.Bytes, nil
|
||||||
|
},
|
||||||
|
encryptionSchemeAlgorithm: oidDESCBC,
|
||||||
|
},
|
||||||
|
"AES-128-CBC DER": {
|
||||||
|
makeDER: func() (der []byte, err error) {
|
||||||
|
AES128CBCEncryptedPEM, err := os.ReadFile("testdata/rsa_pkcs8_aes128cbc_encrypted.pem")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("reading file: %w", err)
|
||||||
|
}
|
||||||
|
pemBlock, _ := pem.Decode(AES128CBCEncryptedPEM)
|
||||||
|
if pemBlock == nil {
|
||||||
|
return nil, errors.New("failed to decode PEM")
|
||||||
|
}
|
||||||
|
return pemBlock.Bytes, nil
|
||||||
|
},
|
||||||
|
encryptionSchemeAlgorithm: pkcs8lib.AES128CBC.OID(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
der, err := testCase.makeDER()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
encryptionSchemeAlgorithm, err := getEncryptionAlgorithmOid(der)
|
||||||
|
|
||||||
|
if testCase.errMessage != "" {
|
||||||
|
assert.EqualError(t, err, testCase.errMessage)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, testCase.encryptionSchemeAlgorithm, encryptionSchemeAlgorithm)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
59
internal/openvpn/pkcs8/descbc.go
Normal file
59
internal/openvpn/pkcs8/descbc.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package pkcs8
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/des" //nolint:gosec
|
||||||
|
"encoding/asn1"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
pkcs8lib "github.com/youmark/pkcs8"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() { //nolint:gochecknoinits
|
||||||
|
pkcs8lib.RegisterCipher(oidDESCBC, newCipherDESCBCBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCipherDESCBCBlock() pkcs8lib.Cipher { //nolint:ireturn
|
||||||
|
return cipherDESCBC{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type cipherDESCBC struct{}
|
||||||
|
|
||||||
|
func (c cipherDESCBC) IVSize() int {
|
||||||
|
return des.BlockSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c cipherDESCBC) KeySize() int {
|
||||||
|
return 8 //nolint:gomnd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c cipherDESCBC) OID() asn1.ObjectIdentifier {
|
||||||
|
return oidDESCBC
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c cipherDESCBC) Encrypt(key, iv, plaintext []byte) ([]byte, error) {
|
||||||
|
block, err := des.NewCipher(key) //nolint:gosec
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("creating DES cipher: %w", err)
|
||||||
|
}
|
||||||
|
blockEncrypter := cipher.NewCBCEncrypter(block, iv)
|
||||||
|
paddingLen := block.BlockSize() - (len(plaintext) % block.BlockSize())
|
||||||
|
ciphertext := make([]byte, len(plaintext)+paddingLen)
|
||||||
|
copy(ciphertext, plaintext)
|
||||||
|
copy(ciphertext[len(plaintext):],
|
||||||
|
bytes.Repeat([]byte{byte(paddingLen)}, paddingLen))
|
||||||
|
blockEncrypter.CryptBlocks(ciphertext, ciphertext)
|
||||||
|
return ciphertext, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c cipherDESCBC) Decrypt(key, iv, ciphertext []byte) ([]byte, error) {
|
||||||
|
block, err := des.NewCipher(key) //nolint:gosec
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("creating DES cipher: %w", err)
|
||||||
|
}
|
||||||
|
blockDecrypter := cipher.NewCBCDecrypter(block, iv)
|
||||||
|
plaintext := make([]byte, len(ciphertext))
|
||||||
|
blockDecrypter.CryptBlocks(plaintext, ciphertext)
|
||||||
|
return plaintext, nil
|
||||||
|
}
|
||||||
12
internal/openvpn/pkcs8/testdata/readme.txt
vendored
Normal file
12
internal/openvpn/pkcs8/testdata/readme.txt
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
The key files in this directory are generated using OpenSSL.
|
||||||
|
Re-generating them is fine and should work with existing tests.
|
||||||
|
|
||||||
|
For DES encrypted RSA key files, openssl version 1.x.x is required, and the following commands in order generate the files:
|
||||||
|
|
||||||
|
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:512 -des -pass pass:password -out rsa_pkcs8_aes128cbc_encrypted.pem
|
||||||
|
openssl pkcs8 -topk8 -in rsa_pkcs8_aes128cbc_encrypted.pem -passin pass:password -nocrypt -out rsa_pkcs8_aes128cbc_decrypted.pem
|
||||||
|
|
||||||
|
For AES encrypted RSA key files, the following commands in order generate the files:
|
||||||
|
|
||||||
|
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:512 -aes-128-cbc -pass pass:password -out rsa_pkcs8_descbc_encrypted.pem
|
||||||
|
openssl pkcs8 -topk8 -in rsa_pkcs8_descbc_encrypted.pem -passin pass:password -nocrypt -out rsa_pkcs8_descbc_decrypted.pem
|
||||||
10
internal/openvpn/pkcs8/testdata/rsa_pkcs8_aes128cbc_decrypted.pem
vendored
Normal file
10
internal/openvpn/pkcs8/testdata/rsa_pkcs8_aes128cbc_decrypted.pem
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIBVgIBADANBgkqhkiG9w0BAQEFAASCAUAwggE8AgEAAkEAsont6TMS9RVqjXoi
|
||||||
|
wF/oKZCwbWM4HmCJvp5Z2dOfKabt+7FOTJiD7APLJKva6791HDTuyBu7+HFQCzW3
|
||||||
|
ghLuiwIDAQABAkB09FuwHq/1cmEJao+nO2xHBiw8i/lwFMdG4k5znegujL4g16i7
|
||||||
|
+afWrMd54jYNPGiKuSNObB2BZR1j8tz/jvbxAiEA3d7bVwtWdaZVIV5t9uqrq5fG
|
||||||
|
j3eXfNemTu1HQDmVqNMCIQDOALECY98KURR4NJueTKNuvawkuWFhizfKKTfS5B6Q
|
||||||
|
aQIhANsF/RFYp+lMYg2m4nc2AnJKSkGmlW0wlYSkyAmmzw7xAiEAqSz+MSVNnU5a
|
||||||
|
ziD+D/GGYkKYJYysgYvwZDCXbLT0uMkCIQDZghteTq2MMwIWWUJti3nc6nCICaJu
|
||||||
|
d5O9Sm7BcOSuoA==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
12
internal/openvpn/pkcs8/testdata/rsa_pkcs8_aes128cbc_encrypted.pem
vendored
Normal file
12
internal/openvpn/pkcs8/testdata/rsa_pkcs8_aes128cbc_encrypted.pem
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||||
|
MIIBvTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIX7rAZ9pfZ4ACAggA
|
||||||
|
MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBBVQ5G606jCKrKADBAiKwcPBIIB
|
||||||
|
YBbudvVfqdLKm9LBFOAcUQk+sdFrq6e2r/xnuqM7VY6Ru4pMOmVMhHMMCFkqHLjx
|
||||||
|
f7hN+xjk3XpYyoptnozPBOhypZrjd6IeEJSkBtU5BZR8fP0Bhny5NYHGcyPR6MZA
|
||||||
|
5iX/0fnyMlrncG67UNwoZQjfg7jEO3mAjuCW/F74xtPQ90ZHtw8mYC26fa09uQR4
|
||||||
|
ptL9XqZuw4+U//3CuOheKqI17wulKAb4NwJckYbKyOik+J4yAi0ScgO73pD1FFvl
|
||||||
|
qBxcpyvEqFQqkOlcbR9YwVBAXeW8cbpZJd+MilSs7Ru/phHrP9wz5chYDrocbeG/
|
||||||
|
H8FhCCvZnJ3zC3P3FPRNPtoaduJ0MbYpaMv4hyP3tEbzbslPA1v14ES3U+w0gmdD
|
||||||
|
zpsy0oplQK9d9wL2TKBwyALcUx5BhtcqKsUXwBOWXMToc4lIXUVl0UVYwULibmEd
|
||||||
|
yK6ajugNxG95X+BJjGvWu/U=
|
||||||
|
-----END ENCRYPTED PRIVATE KEY-----
|
||||||
10
internal/openvpn/pkcs8/testdata/rsa_pkcs8_descbc_decrypted.pem
vendored
Normal file
10
internal/openvpn/pkcs8/testdata/rsa_pkcs8_descbc_decrypted.pem
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIBVgIBADANBgkqhkiG9w0BAQEFAASCAUAwggE8AgEAAkEAuU3FTtbPm8OjZ/d8
|
||||||
|
vVd+seQcrCGgwxigKpOszFfOOXKxfy2CgpjE1Ga2h0UneJ6pq0KZyY+ggYAX8PaS
|
||||||
|
U6R3HwIDAQABAkEAibQPkjzz3u8Nua8i1Zn1nsDDxe7fhtv/+mPvn5MIv4sFRS71
|
||||||
|
0o9+SPNIQn7aJcGIqyBzHYdQg3/wGla+LA+msQIhAOt+hy1dnaWTSXIrIuPt+sSP
|
||||||
|
Fjk80ijfxntXHNU6qExjAiEAyXBurrTdQs6D61ZzdlOFzgUs/FHa4dmWmxXuFsdv
|
||||||
|
8RUCIQCIZQJaLiyOp94UOBO/PCjQC6ftguKeNe25plzWy2CKzQIgXBpBMTZXGG2u
|
||||||
|
WZMcldSYkFtDd1bB2pQPTXeYdefYYgUCIQDVH3ysySFXIlHJulgcxvriXTfY4goY
|
||||||
|
TQ0PL0Ow7sIz6A==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
12
internal/openvpn/pkcs8/testdata/rsa_pkcs8_descbc_encrypted.pem
vendored
Normal file
12
internal/openvpn/pkcs8/testdata/rsa_pkcs8_descbc_encrypted.pem
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||||
|
MIIBsTBLBgkqhkiG9w0BBQ0wPjApBgkqhkiG9w0BBQwwHAQIZK8yPqcvVqoCAggA
|
||||||
|
MAwGCCqGSIb3DQIJBQAwEQYFKw4DAgcECI7C8b+gk6UJBIIBYGQQ4UcglyUqSFC7
|
||||||
|
JiA+Gh01K1odfdLJKLh30+iescrFenII4Vv4rX5609URhn2iHCXhlnZ0+9geRR9k
|
||||||
|
dQSKXaDVVGQw3bQUKgS+lZDAeLV4PS7c+KW0xLpXWJxBPs6NXQMxoJZ23UA391EH
|
||||||
|
p8gKzZqUKk/rEOP68wr3IpHqaD3xggzN+4eA4ZKj4OktmWfUjgC7RQIZSaMxfq+D
|
||||||
|
q+4D5onp+B4C2WRfjnN/N2g7UhzKWGvhjKyogvl82PuY9Vp1qPwQGdg5wdJ/2UVX
|
||||||
|
QNvbkT21Wrv1ffFuIDS1/lCPnd8RAl2Q7chfLyut4BjP0tlmYNxRwQU2mT3KZOrB
|
||||||
|
wwhWgXZtBwj4LjyasVkKe4hyVfRXN5NgONvqxof3VdZUHzOegOapNbEmfhNwVogj
|
||||||
|
1gwRWL7etAbYKjiMPFzZJAiU97+UkqveguldeoHmvWRDTLqxgZw5M4wkPPldb+u8
|
||||||
|
d1vCDDQ=
|
||||||
|
-----END ENCRYPTED PRIVATE KEY-----
|
||||||
52
internal/openvpn/pkcs8/upgrade.go
Normal file
52
internal/openvpn/pkcs8/upgrade.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package pkcs8
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
pkcs8lib "github.com/youmark/pkcs8"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUnsupportedKeyType = errors.New("unsupported key type")
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpgradeEncryptedKey eventually upgrades an encrypted key to a newer encryption
|
||||||
|
// if its encryption is too weak for Openvpn/Openssl.
|
||||||
|
// If the key is encrypted using DES-CBC, it is decrypted and re-encrypted using AES-256-CBC.
|
||||||
|
// Otherwise, the key is returned unmodified.
|
||||||
|
// Note this function only supports:
|
||||||
|
// - PKCS8 encrypted keys
|
||||||
|
// - RSA and ECDSA keys
|
||||||
|
// - DES-CBC, 3DES, AES-128-CBC, AES-192-CBC, AES-256-CBC, AES-128-GCM, AES-192-GCM
|
||||||
|
// and AES-256-GCM encryption algorithms.
|
||||||
|
func UpgradeEncryptedKey(encryptedPKCS8DERKey, passphrase string) (securelyEncryptedPKCS8DERKey string, err error) {
|
||||||
|
der, err := base64.StdEncoding.DecodeString(encryptedPKCS8DERKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("decoding base64 encoded DER: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
oidEncryptionAlgorithm, err := getEncryptionAlgorithmOid(der)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("finding encryption algorithm oid: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !oidEncryptionAlgorithm.Equal(oidDESCBC) {
|
||||||
|
return encryptedPKCS8DERKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert DES-CBC encrypted key to an AES256CBC encrypted key
|
||||||
|
privateKey, err := pkcs8lib.ParsePKCS8PrivateKey(der, []byte(passphrase))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("parsing pkcs8 encrypted private key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
der, err = pkcs8lib.MarshalPrivateKey(privateKey, []byte(passphrase), pkcs8lib.DefaultOpts)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("encrypting and encoding private key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
securelyEncryptedPKCS8DERKey = base64.StdEncoding.EncodeToString(der)
|
||||||
|
return securelyEncryptedPKCS8DERKey, nil
|
||||||
|
}
|
||||||
75
internal/openvpn/pkcs8/upgrade_test.go
Normal file
75
internal/openvpn/pkcs8/upgrade_test.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package pkcs8
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/youmark/pkcs8"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parsePEMFile(t *testing.T, pemFilepath string) (base64DER string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
bytes, err := os.ReadFile(pemFilepath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
pemBlock, _ := pem.Decode(bytes)
|
||||||
|
require.NotNil(t, pemBlock)
|
||||||
|
|
||||||
|
derBytes := pemBlock.Bytes
|
||||||
|
base64DER = base64.StdEncoding.EncodeToString(derBytes)
|
||||||
|
return base64DER
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_UpgradeEncryptedKey(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
encryptedPKCS8base64DERKey string
|
||||||
|
passphrase string
|
||||||
|
decryptedPKCS8Base64DERKey string
|
||||||
|
errMessage string
|
||||||
|
}{
|
||||||
|
"AES-128-CBC key": {
|
||||||
|
encryptedPKCS8base64DERKey: parsePEMFile(t, "testdata/rsa_pkcs8_aes128cbc_encrypted.pem"),
|
||||||
|
passphrase: "password",
|
||||||
|
decryptedPKCS8Base64DERKey: parsePEMFile(t, "testdata/rsa_pkcs8_aes128cbc_decrypted.pem"),
|
||||||
|
},
|
||||||
|
"DES-CBC key": {
|
||||||
|
encryptedPKCS8base64DERKey: parsePEMFile(t, "testdata/rsa_pkcs8_descbc_encrypted.pem"),
|
||||||
|
passphrase: "password",
|
||||||
|
decryptedPKCS8Base64DERKey: parsePEMFile(t, "testdata/rsa_pkcs8_descbc_decrypted.pem"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
securelyEncryptedPKCS8DERKey, err := UpgradeEncryptedKey(testCase.encryptedPKCS8base64DERKey, testCase.passphrase)
|
||||||
|
|
||||||
|
if testCase.errMessage != "" {
|
||||||
|
assert.EqualError(t, err, testCase.errMessage)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Decrypt possible re-encrypted key to verify it matches the expected
|
||||||
|
// corresponding decrypted key.
|
||||||
|
der, err := base64.StdEncoding.DecodeString(securelyEncryptedPKCS8DERKey)
|
||||||
|
require.NoError(t, err)
|
||||||
|
privateKey, err := pkcs8.ParsePKCS8PrivateKey(der, []byte(testCase.passphrase))
|
||||||
|
require.NoError(t, err)
|
||||||
|
der, err = x509.MarshalPKCS8PrivateKey(privateKey)
|
||||||
|
require.NoError(t, err)
|
||||||
|
base64DER := base64.StdEncoding.EncodeToString(der)
|
||||||
|
assert.Equal(t, testCase.decryptedPKCS8Base64DERKey, base64DER)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -54,7 +54,7 @@ func fetchAPI(ctx context.Context, client *http.Client) (
|
|||||||
decoder := json.NewDecoder(response.Body)
|
decoder := json.NewDecoder(response.Body)
|
||||||
if err := decoder.Decode(&data); err != nil {
|
if err := decoder.Decode(&data); err != nil {
|
||||||
_ = response.Body.Close()
|
_ = response.Body.Close()
|
||||||
return data, fmt.Errorf("unmarshaling response body: %w", err)
|
return data, fmt.Errorf("decoding response body: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := response.Body.Close(); err != nil {
|
if err := response.Body.Close(); err != nil {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
@@ -42,10 +43,12 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
city := strings.ReplaceAll(apiServer.Location, ", ", " ")
|
||||||
|
city = strings.ReplaceAll(city, ",", "")
|
||||||
baseServer := models.Server{
|
baseServer := models.Server{
|
||||||
ServerName: apiServer.PublicName,
|
ServerName: apiServer.PublicName,
|
||||||
Country: apiServer.CountryName,
|
Country: apiServer.CountryName,
|
||||||
City: apiServer.Location,
|
City: city,
|
||||||
Region: apiServer.Continent,
|
Region: apiServer.Continent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func getOpenVPNConnection(extractor Extractor,
|
|||||||
connection models.Connection, err error) {
|
connection models.Connection, err error) {
|
||||||
_, connection, err = extractor.Data(*selection.OpenVPN.ConfFile)
|
_, connection, err = extractor.Data(*selection.OpenVPN.ConfFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return connection, fmt.Errorf("cannot extract connection: %w", err)
|
return connection, fmt.Errorf("extracting connection: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
customPort := *selection.OpenVPN.CustomPort
|
customPort := *selection.OpenVPN.CustomPort
|
||||||
|
|||||||
@@ -50,11 +50,11 @@ func fetchAPI(ctx context.Context, client *http.Client) (
|
|||||||
decoder := json.NewDecoder(response.Body)
|
decoder := json.NewDecoder(response.Body)
|
||||||
if err := decoder.Decode(&data); err != nil {
|
if err := decoder.Decode(&data); err != nil {
|
||||||
_ = response.Body.Close()
|
_ = response.Body.Close()
|
||||||
return data, fmt.Errorf("failed unmarshaling response body: %w", err)
|
return data, fmt.Errorf("decoding response body: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := response.Body.Close(); err != nil {
|
if err := response.Body.Close(); err != nil {
|
||||||
return data, fmt.Errorf("cannot close response body: %w", err)
|
return data, fmt.Errorf("closing response body: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ func Test_Provider_GetConnection(t *testing.T) {
|
|||||||
"error": {
|
"error": {
|
||||||
storageErr: errTest,
|
storageErr: errTest,
|
||||||
errWrapped: errTest,
|
errWrapped: errTest,
|
||||||
errMessage: "cannot filter servers: test error",
|
errMessage: "filtering servers: test error",
|
||||||
},
|
},
|
||||||
"default OpenVPN TCP port": {
|
"default OpenVPN TCP port": {
|
||||||
filteredServers: []models.Server{
|
filteredServers: []models.Server{
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
||||||
servers []models.Server, err error) {
|
servers []models.Server, err error) {
|
||||||
const url = "https://support.fastestvpn.com/download/openvpn-tcp-udp-config-files"
|
const url = "https://support.fastestvpn.com/download/fastestvpn_ovpn"
|
||||||
contents, err := u.unzipper.FetchAndExtract(ctx, url)
|
contents, err := u.unzipper.FetchAndExtract(ctx, url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
|
|
||||||
func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
||||||
servers []models.Server, err error) {
|
servers []models.Server, err error) {
|
||||||
const url = "https://www.ipvanish.com/software/configs/configs.zip"
|
const url = "https://configs.ipvanish.com/configs/configs.zip"
|
||||||
contents, err := u.unzipper.FetchAndExtract(ctx, url)
|
contents, err := u.unzipper.FetchAndExtract(ctx, url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ func Test_Updater_GetServers(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
unzipper := common.NewMockUnzipper(ctrl)
|
unzipper := common.NewMockUnzipper(ctrl)
|
||||||
const zipURL = "https://www.ipvanish.com/software/configs/configs.zip"
|
const zipURL = "https://configs.ipvanish.com/configs/configs.zip"
|
||||||
unzipper.EXPECT().FetchAndExtract(ctx, zipURL).
|
unzipper.EXPECT().FetchAndExtract(ctx, zipURL).
|
||||||
Return(testCase.unzipContents, testCase.unzipErr)
|
Return(testCase.unzipContents, testCase.unzipErr)
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ func Test_Provider_GetConnection(t *testing.T) {
|
|||||||
"error": {
|
"error": {
|
||||||
storageErr: errTest,
|
storageErr: errTest,
|
||||||
errWrapped: errTest,
|
errWrapped: errTest,
|
||||||
errMessage: "cannot filter servers: test error",
|
errMessage: "filtering servers: test error",
|
||||||
},
|
},
|
||||||
"default OpenVPN TCP port": {
|
"default OpenVPN TCP port": {
|
||||||
filteredServers: []models.Server{
|
filteredServers: []models.Server{
|
||||||
|
|||||||
@@ -53,11 +53,11 @@ func fetchAPI(ctx context.Context, client *http.Client) (
|
|||||||
decoder := json.NewDecoder(response.Body)
|
decoder := json.NewDecoder(response.Body)
|
||||||
if err := decoder.Decode(&data); err != nil {
|
if err := decoder.Decode(&data); err != nil {
|
||||||
_ = response.Body.Close()
|
_ = response.Body.Close()
|
||||||
return data, fmt.Errorf("failed unmarshaling response body: %w", err)
|
return data, fmt.Errorf("decoding response body: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := response.Body.Close(); err != nil {
|
if err := response.Body.Close(); err != nil {
|
||||||
return data, fmt.Errorf("cannot close response body: %w", err)
|
return data, fmt.Errorf("closing response body: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func Test_fetchAPI(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"nil body": {
|
"nil body": {
|
||||||
responseStatus: http.StatusOK,
|
responseStatus: http.StatusOK,
|
||||||
err: errors.New("failed unmarshaling response body: EOF"),
|
err: errors.New("decoding response body: EOF"),
|
||||||
},
|
},
|
||||||
"no server": {
|
"no server": {
|
||||||
responseStatus: http.StatusOK,
|
responseStatus: http.StatusOK,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
|||||||
servers []models.Server, err error) {
|
servers []models.Server, err error) {
|
||||||
data, err := fetchAPI(ctx, u.client)
|
data, err := fetchAPI(ctx, u.client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed fetching API: %w", err)
|
return nil, fmt.Errorf("fetching API: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hosts := make(map[string]struct{}, len(data.Servers))
|
hosts := make(map[string]struct{}, len(data.Servers))
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ func Test_Updater_GetServers(t *testing.T) {
|
|||||||
"http response error": {
|
"http response error": {
|
||||||
warnerBuilder: func(ctrl *gomock.Controller) common.Warner { return nil },
|
warnerBuilder: func(ctrl *gomock.Controller) common.Warner { return nil },
|
||||||
responseStatus: http.StatusNoContent,
|
responseStatus: http.StatusNoContent,
|
||||||
err: errors.New("failed fetching API: HTTP status code not OK: 204 No Content"),
|
err: errors.New("fetching API: HTTP status code not OK: 204 No Content"),
|
||||||
},
|
},
|
||||||
"resolve error": {
|
"resolve error": {
|
||||||
warnerBuilder: func(ctrl *gomock.Controller) common.Warner {
|
warnerBuilder: func(ctrl *gomock.Controller) common.Warner {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ func Test_Provider_GetConnection(t *testing.T) {
|
|||||||
"error": {
|
"error": {
|
||||||
storageErr: errTest,
|
storageErr: errTest,
|
||||||
errWrapped: errTest,
|
errWrapped: errTest,
|
||||||
errMessage: "cannot filter servers: test error",
|
errMessage: "filtering servers: test error",
|
||||||
},
|
},
|
||||||
"default OpenVPN TCP port": {
|
"default OpenVPN TCP port": {
|
||||||
filteredServers: []models.Server{
|
filteredServers: []models.Server{
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
ErrHTTPStatusCodeNotOK = errors.New("HTTP status code not OK")
|
ErrHTTPStatusCodeNotOK = errors.New("HTTP status code not OK")
|
||||||
ErrUnmarshalResponseBody = errors.New("failed unmarshaling response body")
|
ErrDecodeResponseBody = errors.New("failed decoding response body")
|
||||||
)
|
)
|
||||||
|
|
||||||
type serverData struct {
|
type serverData struct {
|
||||||
@@ -47,7 +47,7 @@ func fetchAPI(ctx context.Context, client *http.Client) (data []serverData, err
|
|||||||
|
|
||||||
decoder := json.NewDecoder(response.Body)
|
decoder := json.NewDecoder(response.Body)
|
||||||
if err := decoder.Decode(&data); err != nil {
|
if err := decoder.Decode(&data); err != nil {
|
||||||
return nil, fmt.Errorf("%w: %s", ErrUnmarshalResponseBody, err)
|
return nil, fmt.Errorf("%w: %s", ErrDecodeResponseBody, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := response.Body.Close(); err != nil {
|
if err := response.Body.Close(); err != nil {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func fetchAPI(ctx context.Context, client *http.Client) (data []serverData, err
|
|||||||
|
|
||||||
decoder := json.NewDecoder(response.Body)
|
decoder := json.NewDecoder(response.Body)
|
||||||
if err := decoder.Decode(&data); err != nil {
|
if err := decoder.Decode(&data); err != nil {
|
||||||
return nil, fmt.Errorf("failed unmarshaling response body: %w", err)
|
return nil, fmt.Errorf("decoding response body: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := response.Body.Close(); err != nil {
|
if err := response.Body.Close(); err != nil {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func parseServerName(serverName string) (number uint16, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
idString := serverName[i+1:]
|
idString := serverName[i+1:]
|
||||||
idUint64, err := strconv.ParseUint(idString, 10, 16) //nolint:gomnd
|
idUint64, err := strconv.ParseUint(idString, 10, 16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("%w: %s", ErrInvalidIDInServerName, serverName)
|
return 0, fmt.Errorf("%w: %s", ErrInvalidIDInServerName, serverName)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
func newHTTPClient(serverName string) (client *http.Client, err error) {
|
func newHTTPClient(serverName string) (client *http.Client, err error) {
|
||||||
rootCAs, err := x509.SystemCertPool()
|
rootCAs, err := x509.SystemCertPool()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot load system certificates: %w", err)
|
return nil, fmt.Errorf("loading system certificates: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
const piaCertificate = "MIIHqzCCBZOgAwIBAgIJAJ0u+vODZJntMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzQwMzNaFw0zNDA0MTIxNzQwMzNaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALVkhjumaqBbL8aSgj6xbX1QPTfTd1qHsAZd2B97m8Vw31c/2yQgZNf5qZY0+jOIHULNDe4R9TIvyBEbvnAg/OkPw8n/+ScgYOeH876VUXzjLDBnDb8DLr/+w9oVsuDeFJ9KV2UFM1OYX0SnkHnrYAN2QLF98ESK4NCSU01h5zkcgmQ+qKSfA9Ny0/UpsKPBFqsQ25NvjDWFhCpeqCHKUJ4Be27CDbSl7lAkBuHMPHJs8f8xPgAbHRXZOxVCpayZ2SNDfCwsnGWpWFoMGvdMbygngCn6jA/W1VSFOlRlfLuuGe7QFfDwA0jaLCxuWt/BgZylp7tAzYKR8lnWmtUCPm4+BtjyVDYtDCiGBD9Z4P13RFWvJHw5aapx/5W/CuvVyI7pKwvc2IT+KPxCUhH1XI8ca5RN3C9NoPJJf6qpg4g0rJH3aaWkoMRrYvQ+5PXXYUzjtRHImghRGd/ydERYoAZXuGSbPkm9Y/p2X8unLcW+F0xpJD98+ZI+tzSsI99Zs5wijSUGYr9/j18KHFTMQ8n+1jauc5bCCegN27dPeKXNSZ5riXFL2XX6BkY68y58UaNzmeGMiUL9BOV1iV+PMb7B7PYs7oFLjAhh0EdyvfHkrh/ZV9BEhtFa7yXp8XR0J6vz1YV9R6DYJmLjOEbhU8N0gc3tZm4Qz39lIIG6w3FDAgMBAAGjggFUMIIBUDAdBgNVHQ4EFgQUrsRtyWJftjpdRM0+925Y6Cl08SUwggEfBgNVHSMEggEWMIIBEoAUrsRtyWJftjpdRM0+925Y6Cl08SWhge6kgeswgegxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRlaW50ZXJuZXRhY2Nlc3MuY29tggkAnS7684Nkme0wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOCAgEAJsfhsPk3r8kLXLxY+v+vHzbr4ufNtqnL9/1Uuf8NrsCtpXAoyZ0YqfbkWx3NHTZ7OE9ZRhdMP/RqHQE1p4N4Sa1nZKhTKasV6KhHDqSCt/dvEm89xWm2MVA7nyzQxVlHa9AkcBaemcXEiyT19XdpiXOP4Vhs+J1R5m8zQOxZlV1GtF9vsXmJqWZpOVPmZ8f35BCsYPvv4yMewnrtAC8PFEK/bOPeYcKN50bol22QYaZuLfpkHfNiFTnfMh8sl/ablPyNY7DUNiP5DRcMdIwmfGQxR5WEQoHL3yPJ42LkB5zs6jIm26DGNXfwura/mi105+ENH1CaROtRYwkiHb08U6qLXXJz80mWJkT90nr8Asj35xN2cUppg74nG3YVav/38P48T56hG1NHbYF5uOCske19F6wi9maUoto/3vEr0rnXJUp2KODmKdvBI7co245lHBABWikk8VfejQSlCtDBXn644ZMtAdoxKNfR2WTFVEwJiyd1Fzx0yujuiXDROLhISLQDRjVVAvawrAtLZWYK31bY7KlezPlQnl/D9Asxe85l8jO5+0LdJ6VyOs/Hd4w52alDW/MFySDZSfQHMTIc30hLBJ8OnCEIvluVQQ2UQvoW+no177N9L2Y+M9TcTA62ZyMXShHQGeh20rb4kK8f+iFX8NxtdHVSkxMEFSfDDyQ=" //nolint:lll
|
const piaCertificate = "MIIHqzCCBZOgAwIBAgIJAJ0u+vODZJntMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzQwMzNaFw0zNDA0MTIxNzQwMzNaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALVkhjumaqBbL8aSgj6xbX1QPTfTd1qHsAZd2B97m8Vw31c/2yQgZNf5qZY0+jOIHULNDe4R9TIvyBEbvnAg/OkPw8n/+ScgYOeH876VUXzjLDBnDb8DLr/+w9oVsuDeFJ9KV2UFM1OYX0SnkHnrYAN2QLF98ESK4NCSU01h5zkcgmQ+qKSfA9Ny0/UpsKPBFqsQ25NvjDWFhCpeqCHKUJ4Be27CDbSl7lAkBuHMPHJs8f8xPgAbHRXZOxVCpayZ2SNDfCwsnGWpWFoMGvdMbygngCn6jA/W1VSFOlRlfLuuGe7QFfDwA0jaLCxuWt/BgZylp7tAzYKR8lnWmtUCPm4+BtjyVDYtDCiGBD9Z4P13RFWvJHw5aapx/5W/CuvVyI7pKwvc2IT+KPxCUhH1XI8ca5RN3C9NoPJJf6qpg4g0rJH3aaWkoMRrYvQ+5PXXYUzjtRHImghRGd/ydERYoAZXuGSbPkm9Y/p2X8unLcW+F0xpJD98+ZI+tzSsI99Zs5wijSUGYr9/j18KHFTMQ8n+1jauc5bCCegN27dPeKXNSZ5riXFL2XX6BkY68y58UaNzmeGMiUL9BOV1iV+PMb7B7PYs7oFLjAhh0EdyvfHkrh/ZV9BEhtFa7yXp8XR0J6vz1YV9R6DYJmLjOEbhU8N0gc3tZm4Qz39lIIG6w3FDAgMBAAGjggFUMIIBUDAdBgNVHQ4EFgQUrsRtyWJftjpdRM0+925Y6Cl08SUwggEfBgNVHSMEggEWMIIBEoAUrsRtyWJftjpdRM0+925Y6Cl08SWhge6kgeswgegxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRlaW50ZXJuZXRhY2Nlc3MuY29tggkAnS7684Nkme0wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOCAgEAJsfhsPk3r8kLXLxY+v+vHzbr4ufNtqnL9/1Uuf8NrsCtpXAoyZ0YqfbkWx3NHTZ7OE9ZRhdMP/RqHQE1p4N4Sa1nZKhTKasV6KhHDqSCt/dvEm89xWm2MVA7nyzQxVlHa9AkcBaemcXEiyT19XdpiXOP4Vhs+J1R5m8zQOxZlV1GtF9vsXmJqWZpOVPmZ8f35BCsYPvv4yMewnrtAC8PFEK/bOPeYcKN50bol22QYaZuLfpkHfNiFTnfMh8sl/ablPyNY7DUNiP5DRcMdIwmfGQxR5WEQoHL3yPJ42LkB5zs6jIm26DGNXfwura/mi105+ENH1CaROtRYwkiHb08U6qLXXJz80mWJkT90nr8Asj35xN2cUppg74nG3YVav/38P48T56hG1NHbYF5uOCske19F6wi9maUoto/3vEr0rnXJUp2KODmKdvBI7co245lHBABWikk8VfejQSlCtDBXn644ZMtAdoxKNfR2WTFVEwJiyd1Fzx0yujuiXDROLhISLQDRjVVAvawrAtLZWYK31bY7KlezPlQnl/D9Asxe85l8jO5+0LdJ6VyOs/Hd4w52alDW/MFySDZSfQHMTIc30hLBJ8OnCEIvluVQQ2UQvoW+no177N9L2Y+M9TcTA62ZyMXShHQGeh20rb4kK8f+iFX8NxtdHVSkxMEFSfDDyQ=" //nolint:lll
|
||||||
|
|||||||
@@ -48,12 +48,12 @@ func (p *Provider) PortForward(ctx context.Context, client *http.Client,
|
|||||||
|
|
||||||
privateIPClient, err := newHTTPClient(serverName)
|
privateIPClient, err := newHTTPClient(serverName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("cannot create custom HTTP client: %w", err)
|
return 0, fmt.Errorf("creating custom HTTP client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := readPIAPortForwardData(p.portForwardPath)
|
data, err := readPIAPortForwardData(p.portForwardPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("cannot read saved port forwarded data: %w", err)
|
return 0, fmt.Errorf("reading saved port forwarded data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dataFound := data.Port > 0
|
dataFound := data.Port > 0
|
||||||
@@ -72,7 +72,7 @@ func (p *Provider) PortForward(ctx context.Context, client *http.Client,
|
|||||||
data, err = refreshPIAPortForwardData(ctx, client, privateIPClient, gateway,
|
data, err = refreshPIAPortForwardData(ctx, client, privateIPClient, gateway,
|
||||||
p.portForwardPath, p.authFilePath)
|
p.portForwardPath, p.authFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("cannot refresh port forward data: %w", err)
|
return 0, fmt.Errorf("refreshing port forward data: %w", err)
|
||||||
}
|
}
|
||||||
durationToExpiration = data.Expiration.Sub(p.timeNow())
|
durationToExpiration = data.Expiration.Sub(p.timeNow())
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ func (p *Provider) PortForward(ctx context.Context, client *http.Client,
|
|||||||
|
|
||||||
// First time binding
|
// First time binding
|
||||||
if err := bindPort(ctx, privateIPClient, gateway, data); err != nil {
|
if err := bindPort(ctx, privateIPClient, gateway, data); err != nil {
|
||||||
return 0, fmt.Errorf("cannot bind port: %w", err)
|
return 0, fmt.Errorf("binding port: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return data.Port, nil
|
return data.Port, nil
|
||||||
@@ -94,12 +94,12 @@ func (p *Provider) KeepPortForward(ctx context.Context, client *http.Client,
|
|||||||
port uint16, gateway net.IP, serverName string) (err error) {
|
port uint16, gateway net.IP, serverName string) (err error) {
|
||||||
privateIPClient, err := newHTTPClient(serverName)
|
privateIPClient, err := newHTTPClient(serverName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot create custom HTTP client: %w", err)
|
return fmt.Errorf("creating custom HTTP client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := readPIAPortForwardData(p.portForwardPath)
|
data, err := readPIAPortForwardData(p.portForwardPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot read saved port forwarded data: %w", err)
|
return fmt.Errorf("reading saved port forwarded data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
durationToExpiration := data.Expiration.Sub(p.timeNow())
|
durationToExpiration := data.Expiration.Sub(p.timeNow())
|
||||||
@@ -121,7 +121,7 @@ func (p *Provider) KeepPortForward(ctx context.Context, client *http.Client,
|
|||||||
case <-keepAliveTimer.C:
|
case <-keepAliveTimer.C:
|
||||||
err := bindPort(ctx, privateIPClient, gateway, data)
|
err := bindPort(ctx, privateIPClient, gateway, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot bind port: %w", err)
|
return fmt.Errorf("binding port: %w", err)
|
||||||
}
|
}
|
||||||
keepAliveTimer.Reset(keepAlivePeriod)
|
keepAliveTimer.Reset(keepAlivePeriod)
|
||||||
case <-expiryTimer.C:
|
case <-expiryTimer.C:
|
||||||
@@ -135,16 +135,16 @@ func refreshPIAPortForwardData(ctx context.Context, client, privateIPClient *htt
|
|||||||
gateway net.IP, portForwardPath, authFilePath string) (data piaPortForwardData, err error) {
|
gateway net.IP, portForwardPath, authFilePath string) (data piaPortForwardData, err error) {
|
||||||
data.Token, err = fetchToken(ctx, client, authFilePath)
|
data.Token, err = fetchToken(ctx, client, authFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data, fmt.Errorf("cannot fetch token: %w", err)
|
return data, fmt.Errorf("fetching token: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
data.Port, data.Signature, data.Expiration, err = fetchPortForwardData(ctx, privateIPClient, gateway, data.Token)
|
data.Port, data.Signature, data.Expiration, err = fetchPortForwardData(ctx, privateIPClient, gateway, data.Token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data, fmt.Errorf("cannot fetch port forwarding data: %w", err)
|
return data, fmt.Errorf("fetching port forwarding data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := writePIAPortForwardData(portForwardPath, data); err != nil {
|
if err := writePIAPortForwardData(portForwardPath, data); err != nil {
|
||||||
return data, fmt.Errorf("cannot persist port forwarding data: %w", err)
|
return data, fmt.Errorf("persisting port forwarding data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
@@ -236,7 +236,7 @@ func fetchToken(ctx context.Context, client *http.Client,
|
|||||||
authFilePath string) (token string, err error) {
|
authFilePath string) (token string, err error) {
|
||||||
username, password, err := getOpenvpnCredentials(authFilePath)
|
username, password, err := getOpenvpnCredentials(authFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("cannot get username and password: %w", err)
|
return "", fmt.Errorf("getting username and password: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
errSubstitutions := map[string]string{
|
errSubstitutions := map[string]string{
|
||||||
@@ -266,7 +266,7 @@ func fetchToken(ctx context.Context, client *http.Client,
|
|||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
|
|
||||||
if response.StatusCode != http.StatusOK {
|
if response.StatusCode != http.StatusOK {
|
||||||
return "", makeNOKStatusError(response, nil)
|
return "", makeNOKStatusError(response, errSubstitutions)
|
||||||
}
|
}
|
||||||
|
|
||||||
decoder := json.NewDecoder(response.Body)
|
decoder := json.NewDecoder(response.Body)
|
||||||
@@ -274,7 +274,7 @@ func fetchToken(ctx context.Context, client *http.Client,
|
|||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
}
|
}
|
||||||
if err := decoder.Decode(&result); err != nil {
|
if err := decoder.Decode(&result); err != nil {
|
||||||
return "", fmt.Errorf("cannot unmarshal response: %w", err)
|
return "", fmt.Errorf("decoding response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if result.Token == "" {
|
if result.Token == "" {
|
||||||
@@ -291,13 +291,13 @@ func getOpenvpnCredentials(authFilePath string) (
|
|||||||
username, password string, err error) {
|
username, password string, err error) {
|
||||||
file, err := os.Open(authFilePath)
|
file, err := os.Open(authFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("cannot read OpenVPN authentication file: %w", err)
|
return "", "", fmt.Errorf("reading OpenVPN authentication file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
authData, err := io.ReadAll(file)
|
authData, err := io.ReadAll(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
return "", "", fmt.Errorf("authentication file is malformed: %w", err)
|
return "", "", fmt.Errorf("reading authentication file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := file.Close(); err != nil {
|
if err := file.Close(); err != nil {
|
||||||
@@ -329,13 +329,13 @@ func fetchPortForwardData(ctx context.Context, client *http.Client, gateway net.
|
|||||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url.String(), nil)
|
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url.String(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = replaceInErr(err, errSubstitutions)
|
err = replaceInErr(err, errSubstitutions)
|
||||||
return 0, "", expiration, fmt.Errorf("cannot obtain signature payload: %w", err)
|
return 0, "", expiration, fmt.Errorf("obtaining signature payload: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := client.Do(request)
|
response, err := client.Do(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = replaceInErr(err, errSubstitutions)
|
err = replaceInErr(err, errSubstitutions)
|
||||||
return 0, "", expiration, fmt.Errorf("cannot obtain signature payload: %w", err)
|
return 0, "", expiration, fmt.Errorf("obtaining signature payload: %w", err)
|
||||||
}
|
}
|
||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
|
|
||||||
@@ -350,7 +350,7 @@ func fetchPortForwardData(ctx context.Context, client *http.Client, gateway net.
|
|||||||
Signature string `json:"signature"`
|
Signature string `json:"signature"`
|
||||||
}
|
}
|
||||||
if err := decoder.Decode(&data); err != nil {
|
if err := decoder.Decode(&data); err != nil {
|
||||||
return 0, "", expiration, fmt.Errorf("cannot unmarshal response: %w", err)
|
return 0, "", expiration, fmt.Errorf("decoding response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.Status != "OK" {
|
if data.Status != "OK" {
|
||||||
@@ -359,7 +359,7 @@ func fetchPortForwardData(ctx context.Context, client *http.Client, gateway net.
|
|||||||
|
|
||||||
port, _, expiration, err = unpackPayload(data.Payload)
|
port, _, expiration, err = unpackPayload(data.Payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, "", expiration, fmt.Errorf("cannot unpack payload data: %w", err)
|
return 0, "", expiration, fmt.Errorf("unpacking payload data: %w", err)
|
||||||
}
|
}
|
||||||
return port, data.Signature, expiration, err
|
return port, data.Signature, expiration, err
|
||||||
}
|
}
|
||||||
@@ -371,7 +371,7 @@ var (
|
|||||||
func bindPort(ctx context.Context, client *http.Client, gateway net.IP, data piaPortForwardData) (err error) {
|
func bindPort(ctx context.Context, client *http.Client, gateway net.IP, data piaPortForwardData) (err error) {
|
||||||
payload, err := packPayload(data.Port, data.Token, data.Expiration)
|
payload, err := packPayload(data.Port, data.Token, data.Expiration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot serialize payload: %w", err)
|
return fmt.Errorf("serializing payload: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
queryParams := make(url.Values)
|
queryParams := make(url.Values)
|
||||||
@@ -410,7 +410,7 @@ func bindPort(ctx context.Context, client *http.Client, gateway net.IP, data pia
|
|||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
if err := decoder.Decode(&responseData); err != nil {
|
if err := decoder.Decode(&responseData); err != nil {
|
||||||
return fmt.Errorf("cannot unmarshal response: from %s: %w", bindPortURL.String(), err)
|
return fmt.Errorf("decoding response: from %s: %w", bindPortURL.String(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if responseData.Status != "OK" {
|
if responseData.Status != "OK" {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func fetchAPI(ctx context.Context, client *http.Client) (
|
|||||||
|
|
||||||
decoder := json.NewDecoder(response.Body)
|
decoder := json.NewDecoder(response.Body)
|
||||||
if err := decoder.Decode(&data); err != nil {
|
if err := decoder.Decode(&data); err != nil {
|
||||||
return data, fmt.Errorf("failed unmarshaling response body: %w", err)
|
return data, fmt.Errorf("decoding response body: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := response.Body.Close(); err != nil {
|
if err := response.Body.Close(); err != nil {
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ import (
|
|||||||
|
|
||||||
func (p *Provider) GetConnection(selection settings.ServerSelection, ipv6Supported bool) (
|
func (p *Provider) GetConnection(selection settings.ServerSelection, ipv6Supported bool) (
|
||||||
connection models.Connection, err error) {
|
connection models.Connection, err error) {
|
||||||
defaults := utils.NewConnectionDefaults(0, 443, 0) //nolint:gomnd
|
defaults := utils.NewConnectionDefaults(443, 443, 0) //nolint:gomnd
|
||||||
return utils.GetConnection(p.Name(), p.storage, selection, defaults, ipv6Supported, p.randSource)
|
return utils.GetConnection(p.Name(), p.storage, selection, defaults, ipv6Supported, p.randSource)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ func (p *Provider) OpenVPNConfig(connection models.Connection,
|
|||||||
RemoteCertTLS: true,
|
RemoteCertTLS: true,
|
||||||
AuthUserPass: true,
|
AuthUserPass: true,
|
||||||
Ciphers: []string{
|
Ciphers: []string{
|
||||||
|
openvpn.AES256gcm,
|
||||||
openvpn.AES256cbc,
|
openvpn.AES256cbc,
|
||||||
},
|
},
|
||||||
Ping: pingSeconds,
|
Ping: pingSeconds,
|
||||||
@@ -26,5 +27,14 @@ func (p *Provider) OpenVPNConfig(connection models.Connection,
|
|||||||
"redirect-gateway",
|
"redirect-gateway",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if settings.Version == openvpn.Openvpn25 {
|
||||||
|
// SlickVPN's certificate is sha1WithRSAEncryption and sha1 is now
|
||||||
|
// rejected by openssl 3.x.x which is used by OpenVPN >= 2.5.
|
||||||
|
// We lower the security level to 3 to allow this algorithm,
|
||||||
|
// see https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_security_level.html
|
||||||
|
providerSettings.TLSCipher = "DEFAULT:@SECLEVEL=0"
|
||||||
|
}
|
||||||
|
|
||||||
return utils.OpenVPNConfig(providerSettings, connection, settings, ipv6Supported)
|
return utils.OpenVPNConfig(providerSettings, connection, settings, ipv6Supported)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,18 +29,18 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const failEarly = false // some URLs from the website are not valid
|
const failEarly = false // some URLs from the website are not valid
|
||||||
udpHostToURL, errors := openvpn.FetchMultiFiles(ctx, u.client, openvpnURLs, failEarly)
|
hostToURL, errors := openvpn.FetchMultiFiles(ctx, u.client, openvpnURLs, failEarly)
|
||||||
for _, err := range errors {
|
for _, err := range errors {
|
||||||
u.warner.Warn(fmt.Sprintf("fetching OpenVPN files: %s", err))
|
u.warner.Warn(fmt.Sprintf("fetching OpenVPN files: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(udpHostToURL) < minServers {
|
if len(hostToURL) < minServers {
|
||||||
return nil, fmt.Errorf("%w: %d and expected at least %d",
|
return nil, fmt.Errorf("%w: %d and expected at least %d",
|
||||||
common.ErrNotEnoughServers, len(udpHostToURL), minServers)
|
common.ErrNotEnoughServers, len(hostToURL), minServers)
|
||||||
}
|
}
|
||||||
|
|
||||||
hosts := make([]string, 0, len(udpHostToURL))
|
hosts := make([]string, 0, len(hostToURL))
|
||||||
for host := range udpHostToURL {
|
for host := range hostToURL {
|
||||||
hosts = append(hosts, host)
|
hosts = append(hosts, host)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,8 +60,6 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
|||||||
|
|
||||||
servers = make([]models.Server, 0, len(hostToIPs))
|
servers = make([]models.Server, 0, len(hostToIPs))
|
||||||
for host, IPs := range hostToIPs {
|
for host, IPs := range hostToIPs {
|
||||||
_, udp := udpHostToURL[host]
|
|
||||||
|
|
||||||
serverData := hostToData[host]
|
serverData := hostToData[host]
|
||||||
|
|
||||||
server := models.Server{
|
server := models.Server{
|
||||||
@@ -70,7 +68,8 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
|||||||
Country: serverData.country,
|
Country: serverData.country,
|
||||||
City: serverData.city,
|
City: serverData.city,
|
||||||
Hostname: host,
|
Hostname: host,
|
||||||
UDP: udp,
|
UDP: true,
|
||||||
|
TCP: true,
|
||||||
IPs: IPs,
|
IPs: IPs,
|
||||||
}
|
}
|
||||||
servers = append(servers, server)
|
servers = append(servers, server)
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ func LocationData() (data []ServerLocation) {
|
|||||||
{Region: "Asia Pacific", Country: "Australia", City: "Sydney", RetroLoc: "Australia Sydney", Hostname: "au-syd.prod.surfshark.com", MultiHop: false},
|
{Region: "Asia Pacific", Country: "Australia", City: "Sydney", RetroLoc: "Australia Sydney", Hostname: "au-syd.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Asia Pacific", Country: "Azerbaijan", City: "Baku", RetroLoc: "Azerbaijan", Hostname: "az-bak.prod.surfshark.com", MultiHop: false},
|
{Region: "Asia Pacific", Country: "Azerbaijan", City: "Baku", RetroLoc: "Azerbaijan", Hostname: "az-bak.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Asia Pacific", Country: "Hong Kong", City: "Hong Kong", RetroLoc: "Hong Kong", Hostname: "hk-hkg.prod.surfshark.com", MultiHop: false},
|
{Region: "Asia Pacific", Country: "Hong Kong", City: "Hong Kong", RetroLoc: "Hong Kong", Hostname: "hk-hkg.prod.surfshark.com", MultiHop: false},
|
||||||
|
{Region: "Asia Pacific", Country: "Hong Kong", City: "Hong Kong", RetroLoc: "Hong Kong", Hostname: "lk-cmb.prod.surfshark.com", MultiHop: false},
|
||||||
|
{Region: "Asia Pacific", Country: "Hong Kong", City: "Hong Kong", RetroLoc: "Hong Kong", Hostname: "mn-uln.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Asia Pacific", Country: "Indonesia", City: "Jakarta", RetroLoc: "Indonesia", Hostname: "id-jak.prod.surfshark.com", MultiHop: false},
|
{Region: "Asia Pacific", Country: "Indonesia", City: "Jakarta", RetroLoc: "Indonesia", Hostname: "id-jak.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", RetroLoc: "Japan Tokyo st004", Hostname: "jp-tok-st004.prod.surfshark.com", MultiHop: false},
|
|
||||||
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", RetroLoc: "Japan Tokyo st007", Hostname: "jp-tok-st007.prod.surfshark.com", MultiHop: false},
|
|
||||||
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", Hostname: "jp-tok-st014.prod.surfshark.com"},
|
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", Hostname: "jp-tok-st014.prod.surfshark.com"},
|
||||||
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", Hostname: "jp-tok-st015.prod.surfshark.com"},
|
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", Hostname: "jp-tok-st015.prod.surfshark.com"},
|
||||||
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", Hostname: "jp-tok-st016.prod.surfshark.com"},
|
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", Hostname: "jp-tok-st016.prod.surfshark.com"},
|
||||||
@@ -54,7 +54,6 @@ func LocationData() (data []ServerLocation) {
|
|||||||
{Region: "Asia Pacific", Country: "South Korea", City: "Seoul", RetroLoc: "Korea", Hostname: "kr-seo.prod.surfshark.com", MultiHop: false},
|
{Region: "Asia Pacific", Country: "South Korea", City: "Seoul", RetroLoc: "Korea", Hostname: "kr-seo.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Asia Pacific", Country: "Taiwan", City: "Taichung City", RetroLoc: "Taiwan", Hostname: "tw-tai.prod.surfshark.com", MultiHop: false},
|
{Region: "Asia Pacific", Country: "Taiwan", City: "Taichung City", RetroLoc: "Taiwan", Hostname: "tw-tai.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Asia Pacific", Country: "Thailand", City: "Bangkok", RetroLoc: "Thailand", Hostname: "th-bkk.prod.surfshark.com", MultiHop: false},
|
{Region: "Asia Pacific", Country: "Thailand", City: "Bangkok", RetroLoc: "Thailand", Hostname: "th-bkk.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Asia Pacific", Country: "Vietnam", City: "Ho Chi Minh City", RetroLoc: "Vietnam", Hostname: "vn-hcm.prod.surfshark.com", MultiHop: false},
|
|
||||||
{Region: "Europe", Country: "Albania", City: "Tirana", RetroLoc: "Albania", Hostname: "al-tia.prod.surfshark.com", MultiHop: false},
|
{Region: "Europe", Country: "Albania", City: "Tirana", RetroLoc: "Albania", Hostname: "al-tia.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Europe", Country: "Austria", City: "Vienna", RetroLoc: "Austria", Hostname: "at-vie.prod.surfshark.com", MultiHop: false},
|
{Region: "Europe", Country: "Austria", City: "Vienna", RetroLoc: "Austria", Hostname: "at-vie.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Europe", Country: "Belgium", City: "Brussels", RetroLoc: "Belgium", Hostname: "be-bru.prod.surfshark.com", MultiHop: false},
|
{Region: "Europe", Country: "Belgium", City: "Brussels", RetroLoc: "Belgium", Hostname: "be-bru.prod.surfshark.com", MultiHop: false},
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ func fetchAPI(ctx context.Context, client *http.Client) (
|
|||||||
|
|
||||||
decoder := json.NewDecoder(response.Body)
|
decoder := json.NewDecoder(response.Body)
|
||||||
if err := decoder.Decode(&servers); err != nil {
|
if err := decoder.Decode(&servers); err != nil {
|
||||||
return nil, fmt.Errorf("failed unmarshaling response body: %w", err)
|
return nil, fmt.Errorf("decoding response body: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := response.Body.Close(); err != nil {
|
if err := response.Body.Close(); err != nil {
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ func Test_fetchAPI(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"nil body": {
|
"nil body": {
|
||||||
responseStatus: http.StatusOK,
|
responseStatus: http.StatusOK,
|
||||||
err: errors.New("failed unmarshaling response body: EOF"),
|
err: errors.New("decoding response body: EOF"),
|
||||||
},
|
},
|
||||||
"no server": {
|
"no server": {
|
||||||
responseStatus: http.StatusOK,
|
responseStatus: http.StatusOK,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
|||||||
|
|
||||||
err = addServersFromAPI(ctx, u.client, hts)
|
err = addServersFromAPI(ctx, u.client, hts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot fetch server information from API: %w", err)
|
return nil, fmt.Errorf("fetching server information from API: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
warnings, err := addOpenVPNServersFromZip(ctx, u.unzipper, hts)
|
warnings, err := addOpenVPNServersFromZip(ctx, u.unzipper, hts)
|
||||||
@@ -23,7 +23,7 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
|||||||
u.warner.Warn(warning)
|
u.warner.Warn(warning)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot get OpenVPN ZIP file: %w", err)
|
return nil, fmt.Errorf("getting OpenVPN ZIP file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
getRemainingServers(hts)
|
getRemainingServers(hts)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func GetConnection(provider string,
|
|||||||
connection models.Connection, err error) {
|
connection models.Connection, err error) {
|
||||||
servers, err := storage.FilterServers(provider, selection)
|
servers, err := storage.FilterServers(provider, selection)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return connection, fmt.Errorf("cannot filter servers: %w", err)
|
return connection, fmt.Errorf("filtering servers: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol := getProtocol(selection)
|
protocol := getProtocol(selection)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ func Test_GetConnection(t *testing.T) {
|
|||||||
"storage filter error": {
|
"storage filter error": {
|
||||||
filterError: errTest,
|
filterError: errTest,
|
||||||
errWrapped: errTest,
|
errWrapped: errTest,
|
||||||
errMessage: "cannot filter servers: test error",
|
errMessage: "filtering servers: test error",
|
||||||
},
|
},
|
||||||
"server without IPs": {
|
"server without IPs": {
|
||||||
filteredServers: []models.Server{
|
filteredServers: []models.Server{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/constants/openvpn"
|
"github.com/qdm12/gluetun/internal/constants/openvpn"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/openvpn/pkcs8"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OpenVPNProviderSettings struct {
|
type OpenVPNProviderSettings struct {
|
||||||
@@ -196,8 +197,20 @@ func OpenVPNConfig(provider OpenVPNProviderSettings,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if *settings.EncryptedKey != "" {
|
if *settings.EncryptedKey != "" {
|
||||||
|
encryptedBase64DERKey := *settings.EncryptedKey
|
||||||
|
if settings.Version != openvpn.Openvpn24 {
|
||||||
|
// OpenVPN above 2.4 does not support old encryption schemes such as
|
||||||
|
// DES-CBC, so decrypt and reencrypt the key.
|
||||||
|
// This is a workaround for VPN secure.
|
||||||
|
var err error
|
||||||
|
encryptedBase64DERKey, err = pkcs8.UpgradeEncryptedKey(encryptedBase64DERKey, *settings.KeyPassphrase)
|
||||||
|
if err != nil {
|
||||||
|
// TODO return an error instead.
|
||||||
|
panic(fmt.Sprintf("upgrading encrypted key: %s", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
lines.add("askpass", openvpn.AskPassPath)
|
lines.add("askpass", openvpn.AskPassPath)
|
||||||
lines.addLines(WrapOpenvpnEncryptedKey(*settings.EncryptedKey))
|
lines.addLines(WrapOpenvpnEncryptedKey(encryptedBase64DERKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
if *settings.Cert != "" {
|
if *settings.Cert != "" {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ func BuildWireguardSettings(connection models.Connection,
|
|||||||
settings.PublicKey = connection.PubKey
|
settings.PublicKey = connection.PubKey
|
||||||
settings.PreSharedKey = *userSettings.PreSharedKey
|
settings.PreSharedKey = *userSettings.PreSharedKey
|
||||||
settings.InterfaceName = userSettings.Interface
|
settings.InterfaceName = userSettings.Interface
|
||||||
|
settings.Implementation = userSettings.Implementation
|
||||||
settings.IPv6 = &ipv6Supported
|
settings.IPv6 = &ipv6Supported
|
||||||
|
|
||||||
const rulePriority = 101 // 100 is to receive external connections
|
const rulePriority = 101 // 100 is to receive external connections
|
||||||
@@ -24,7 +25,12 @@ func BuildWireguardSettings(connection models.Connection,
|
|||||||
copy(settings.Endpoint.IP, connection.IP)
|
copy(settings.Endpoint.IP, connection.IP)
|
||||||
settings.Endpoint.Port = int(connection.Port)
|
settings.Endpoint.Port = int(connection.Port)
|
||||||
|
|
||||||
|
settings.Addresses = make([]*net.IPNet, 0, len(userSettings.Addresses))
|
||||||
for _, address := range userSettings.Addresses {
|
for _, address := range userSettings.Addresses {
|
||||||
|
ipv6Address := address.IP.To4() == nil
|
||||||
|
if !ipv6Supported && ipv6Address {
|
||||||
|
continue
|
||||||
|
}
|
||||||
addressCopy := new(net.IPNet)
|
addressCopy := new(net.IPNet)
|
||||||
addressCopy.IP = make(net.IP, len(address.IP))
|
addressCopy.IP = make(net.IP, len(address.IP))
|
||||||
copy(addressCopy.IP, address.IP)
|
copy(addressCopy.IP, address.IP)
|
||||||
|
|||||||
@@ -32,11 +32,11 @@ func Test_BuildWireguardSettings(t *testing.T) {
|
|||||||
PreSharedKey: stringPtr("pre-shared"),
|
PreSharedKey: stringPtr("pre-shared"),
|
||||||
Addresses: []net.IPNet{
|
Addresses: []net.IPNet{
|
||||||
{IP: net.IPv4(1, 1, 1, 1), Mask: net.IPv4Mask(255, 255, 255, 255)},
|
{IP: net.IPv4(1, 1, 1, 1), Mask: net.IPv4Mask(255, 255, 255, 255)},
|
||||||
{IP: net.IPv4(2, 2, 2, 2), Mask: net.IPv4Mask(255, 255, 255, 255)},
|
{IP: net.IPv6zero, Mask: net.IPv4Mask(255, 255, 255, 255)},
|
||||||
},
|
},
|
||||||
Interface: "wg1",
|
Interface: "wg1",
|
||||||
},
|
},
|
||||||
ipv6Supported: true,
|
ipv6Supported: false,
|
||||||
settings: wireguard.Settings{
|
settings: wireguard.Settings{
|
||||||
InterfaceName: "wg1",
|
InterfaceName: "wg1",
|
||||||
PrivateKey: "private",
|
PrivateKey: "private",
|
||||||
@@ -48,10 +48,9 @@ func Test_BuildWireguardSettings(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Addresses: []*net.IPNet{
|
Addresses: []*net.IPNet{
|
||||||
{IP: net.IPv4(1, 1, 1, 1), Mask: net.IPv4Mask(255, 255, 255, 255)},
|
{IP: net.IPv4(1, 1, 1, 1), Mask: net.IPv4Mask(255, 255, 255, 255)},
|
||||||
{IP: net.IPv4(2, 2, 2, 2), Mask: net.IPv4Mask(255, 255, 255, 255)},
|
|
||||||
},
|
},
|
||||||
RulePriority: 101,
|
RulePriority: 101,
|
||||||
IPv6: boolPtr(true),
|
IPv6: boolPtr(false),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
|||||||
servers []models.Server, err error) {
|
servers []models.Server, err error) {
|
||||||
servers, err = fetchServers(ctx, u.client, u.warner)
|
servers, err = fetchServers(ctx, u.client, u.warner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot fetch servers: %w", err)
|
return nil, fmt.Errorf("fetching servers: %w", err)
|
||||||
} else if len(servers) < minServers {
|
} else if len(servers) < minServers {
|
||||||
return nil, fmt.Errorf("%w: %d and expected at least %d",
|
return nil, fmt.Errorf("%w: %d and expected at least %d",
|
||||||
common.ErrNotEnoughServers, len(servers), minServers)
|
common.ErrNotEnoughServers, len(servers), minServers)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package vpnunlimited
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants/openvpn"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
)
|
)
|
||||||
@@ -18,5 +19,14 @@ func (p *Provider) OpenVPNConfig(connection models.Connection,
|
|||||||
"route-metric 1",
|
"route-metric 1",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if settings.Version != openvpn.Openvpn24 {
|
||||||
|
// VPN Unlimited's certificate is sha1WithRSAEncryption and sha1 is now
|
||||||
|
// rejected by openssl 3.x.x which is used by OpenVPN >= 2.5.
|
||||||
|
// We lower the security level to 0 to allow this algorithm,
|
||||||
|
// see https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_security_level.html
|
||||||
|
providerSettings.TLSCipher = `"DEFAULT:@SECLEVEL=0"`
|
||||||
|
}
|
||||||
|
|
||||||
return utils.OpenVPNConfig(providerSettings, connection, settings, ipv6Supported)
|
return utils.OpenVPNConfig(providerSettings, connection, settings, ipv6Supported)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ func Test_Provider_GetConnection(t *testing.T) {
|
|||||||
"error": {
|
"error": {
|
||||||
storageErr: errTest,
|
storageErr: errTest,
|
||||||
errWrapped: errTest,
|
errWrapped: errTest,
|
||||||
errMessage: "cannot filter servers: test error",
|
errMessage: "filtering servers: test error",
|
||||||
},
|
},
|
||||||
"default OpenVPN TCP port": {
|
"default OpenVPN TCP port": {
|
||||||
filteredServers: []models.Server{
|
filteredServers: []models.Server{
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func Test_Provider_GetConnection(t *testing.T) {
|
|||||||
"error": {
|
"error": {
|
||||||
storageErr: errTest,
|
storageErr: errTest,
|
||||||
errWrapped: errTest,
|
errWrapped: errTest,
|
||||||
errMessage: "cannot filter servers: test error",
|
errMessage: "filtering servers: test error",
|
||||||
},
|
},
|
||||||
"default OpenVPN TCP port": {
|
"default OpenVPN TCP port": {
|
||||||
filteredServers: []models.Server{
|
filteredServers: []models.Server{
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func fetchAPI(ctx context.Context, client *http.Client) (
|
|||||||
|
|
||||||
decoder := json.NewDecoder(response.Body)
|
decoder := json.NewDecoder(response.Body)
|
||||||
if err := decoder.Decode(&data); err != nil {
|
if err := decoder.Decode(&data); err != nil {
|
||||||
return data, fmt.Errorf("failed unmarshaling response body: %w", err)
|
return data, fmt.Errorf("decoding response body: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ func (f *Fetch) FetchInfo(ctx context.Context, ip net.IP) (
|
|||||||
|
|
||||||
decoder := json.NewDecoder(response.Body)
|
decoder := json.NewDecoder(response.Body)
|
||||||
if err := decoder.Decode(&result); err != nil {
|
if err := decoder.Decode(&result); err != nil {
|
||||||
return result, fmt.Errorf("cannot decode response: %w", err)
|
return result, fmt.Errorf("decoding response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
countryCode := strings.ToLower(result.Country)
|
countryCode := strings.ToLower(result.Country)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func (d DefaultRoute) String() string {
|
|||||||
func (r *Routing) DefaultRoutes() (defaultRoutes []DefaultRoute, err error) {
|
func (r *Routing) DefaultRoutes() (defaultRoutes []DefaultRoute, err error) {
|
||||||
routes, err := r.netLinker.RouteList(nil, netlink.FAMILY_ALL)
|
routes, err := r.netLinker.RouteList(nil, netlink.FAMILY_ALL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot list routes: %w", err)
|
return nil, fmt.Errorf("listing routes: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, route := range routes {
|
for _, route := range routes {
|
||||||
@@ -39,7 +39,7 @@ func (r *Routing) DefaultRoutes() (defaultRoutes []DefaultRoute, err error) {
|
|||||||
linkIndex := route.LinkIndex
|
linkIndex := route.LinkIndex
|
||||||
link, err := r.netLinker.LinkByIndex(linkIndex)
|
link, err := r.netLinker.LinkByIndex(linkIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot obtain link by index: for default route at index %d: %w", linkIndex, err)
|
return nil, fmt.Errorf("obtaining link by index: for default route at index %d: %w", linkIndex, err)
|
||||||
}
|
}
|
||||||
attributes := link.Attrs()
|
attributes := link.Attrs()
|
||||||
defaultRoute.NetInterface = attributes.Name
|
defaultRoute.NetInterface = attributes.Name
|
||||||
@@ -49,7 +49,7 @@ func (r *Routing) DefaultRoutes() (defaultRoutes []DefaultRoute, err error) {
|
|||||||
}
|
}
|
||||||
defaultRoute.AssignedIP, err = r.assignedIP(defaultRoute.NetInterface, family)
|
defaultRoute.AssignedIP, err = r.assignedIP(defaultRoute.NetInterface, family)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot get assigned IP of %s: %w", defaultRoute.NetInterface, err)
|
return nil, fmt.Errorf("getting assigned IP of %s: %w", defaultRoute.NetInterface, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.logger.Info("default route found: " + defaultRoute.String())
|
r.logger.Info("default route found: " + defaultRoute.String())
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user