Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(netlink): detect ipv6 support level #2523

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
org.opencontainers.image.source="https://github.com/qdm12/gluetun" \
org.opencontainers.image.title="VPN swiss-knife like client for multiple VPN providers" \
org.opencontainers.image.description="VPN swiss-knife like client to tunnel to multiple VPN servers using OpenVPN, IPtables, DNS over TLS, Shadowsocks, an HTTP proxy and Alpine Linux"
ENV VPN_SERVICE_PROVIDER=pia \

Check warning on line 76 in Dockerfile

View workflow job for this annotation

GitHub Actions / publish

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "OPENVPN_KEY_PASSPHRASE_SECRETFILE") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 76 in Dockerfile

View workflow job for this annotation

GitHub Actions / publish

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "HTTPPROXY_PASSWORD") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 76 in Dockerfile

View workflow job for this annotation

GitHub Actions / publish

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "OPENVPN_PASSWORD") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 76 in Dockerfile

View workflow job for this annotation

GitHub Actions / publish

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "OPENVPN_PASSWORD_SECRETFILE") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 76 in Dockerfile

View workflow job for this annotation

GitHub Actions / publish

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "OPENVPN_AUTH") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 76 in Dockerfile

View workflow job for this annotation

GitHub Actions / publish

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "WIREGUARD_PRESHARED_KEY") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 76 in Dockerfile

View workflow job for this annotation

GitHub Actions / publish

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "OPENVPN_ENCRYPTED_KEY") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 76 in Dockerfile

View workflow job for this annotation

GitHub Actions / publish

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "OPENVPN_ENCRYPTED_KEY_SECRETFILE") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 76 in Dockerfile

View workflow job for this annotation

GitHub Actions / publish

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "HTTPPROXY_PASSWORD_SECRETFILE") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 76 in Dockerfile

View workflow job for this annotation

GitHub Actions / publish

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "SHADOWSOCKS_PASSWORD") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
VPN_TYPE=openvpn \
# Common VPN options
VPN_INTERFACE=tun0 \
Expand Down Expand Up @@ -159,6 +159,8 @@
FIREWALL_INPUT_PORTS= \
FIREWALL_OUTBOUND_SUBNETS= \
FIREWALL_DEBUG=off \
# IPv6
IPV6_CHECK_ADDRESS=[2606:4700::6810:84e5]:443 \
# Logging
LOG_LEVEL=info \
# Health
Expand Down
14 changes: 10 additions & 4 deletions cmd/gluetun/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io/fs"
"net/http"
"net/netip"
"os"
"os/exec"
"os/signal"
Expand Down Expand Up @@ -242,10 +243,13 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
return err
}

ipv6Supported, err := netLinker.IsIPv6Supported()
ipv6SupportLevel, err := netLinker.FindIPv6SupportLevel(ctx,
allSettings.IPv6.CheckAddress, firewallConf)
if err != nil {
return fmt.Errorf("checking for IPv6 support: %w", err)
}
ipv6Supported := ipv6SupportLevel == netlink.IPv6Supported ||
ipv6SupportLevel == netlink.IPv6Internet

err = allSettings.Validate(storage, ipv6Supported, logger)
if err != nil {
Expand Down Expand Up @@ -423,7 +427,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
httpClient, unzipper, parallelResolver, publicIPLooper.Fetcher(), openvpnFileExtractor)

vpnLogger := logger.New(log.SetComponent("vpn"))
vpnLooper := vpn.NewLoop(allSettings.VPN, ipv6Supported, allSettings.Firewall.VPNInputPorts,
vpnLooper := vpn.NewLoop(allSettings.VPN, ipv6SupportLevel, allSettings.Firewall.VPNInputPorts,
providers, storage, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
cmder, publicIPLooper, dnsLooper, vpnLogger, httpClient,
buildInfo, *allSettings.Version.Enabled)
Expand Down Expand Up @@ -467,7 +471,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
logger.New(log.SetComponent("http server")),
allSettings.ControlServer.AuthFilePath,
buildInfo, vpnLooper, portForwardLooper, dnsLooper, updaterLooper, publicIPLooper,
storage, ipv6Supported)
storage, ipv6SupportLevel.IsSupported())
if err != nil {
return fmt.Errorf("setting up control server: %w", err)
}
Expand Down Expand Up @@ -549,7 +553,9 @@ type netLinker interface {
Ruler
Linker
IsWireguardSupported() (ok bool, err error)
IsIPv6Supported() (ok bool, err error)
FindIPv6SupportLevel(ctx context.Context,
checkAddress netip.AddrPort, firewall netlink.Firewall,
) (level netlink.IPv6SupportLevel, err error)
PatchLoggerLevel(level log.Level)
}

Expand Down
14 changes: 14 additions & 0 deletions internal/cli/noopfirewall.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package cli

import (
"context"
"net/netip"
)

type noopFirewall struct{}

func (f *noopFirewall) AcceptOutput(_ context.Context, _, _ string, _ netip.Addr,
_ uint16, _ bool,
) (err error) {
return nil
}
15 changes: 10 additions & 5 deletions internal/cli/openvpnconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/netlink"
"github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/gluetun/internal/provider"
"github.com/qdm12/gluetun/internal/storage"
Expand Down Expand Up @@ -40,7 +41,9 @@ type IPFetcher interface {
}

type IPv6Checker interface {
IsIPv6Supported() (supported bool, err error)
FindIPv6SupportLevel(ctx context.Context,
checkAddress netip.AddrPort, firewall netlink.Firewall,
) (level netlink.IPv6SupportLevel, err error)
}

func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader,
Expand All @@ -57,12 +60,14 @@ func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader,
return err
}

ipv6Supported, err := ipv6Checker.IsIPv6Supported()
ipv6SupportLevel, err := ipv6Checker.FindIPv6SupportLevel(context.Background(),
allSettings.IPv6.CheckAddress, &noopFirewall{})
if err != nil {
return fmt.Errorf("checking for IPv6 support: %w", err)
}

if err = allSettings.Validate(storage, ipv6Supported, logger); err != nil {
err = allSettings.Validate(storage, ipv6SupportLevel.IsSupported(), logger)
if err != nil {
return fmt.Errorf("validating settings: %w", err)
}

Expand All @@ -78,13 +83,13 @@ func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader,
unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
providerConf := providers.Get(allSettings.VPN.Provider.Name)
connection, err := providerConf.GetConnection(
allSettings.VPN.Provider.ServerSelection, ipv6Supported)
allSettings.VPN.Provider.ServerSelection, ipv6SupportLevel == netlink.IPv6Internet)
if err != nil {
return err
}

lines := providerConf.OpenVPNConfig(connection,
allSettings.VPN.OpenVPN, ipv6Supported)
allSettings.VPN.OpenVPN, ipv6SupportLevel.IsSupported())

fmt.Println(strings.Join(lines, "\n"))
return nil
Expand Down
51 changes: 51 additions & 0 deletions internal/configuration/settings/ipv6.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package settings

import (
"net/netip"

"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree"
)

// IPv6 contains settings regarding IPv6 configuration.
type IPv6 struct {
// CheckAddress is the TCP ip:port address to dial to check
// IPv6 is supported, in case a default IPv6 route is found.
// It defaults to cloudflare.com address [2606:4700::6810:84e5]:443
CheckAddress netip.AddrPort
}

func (i IPv6) validate() (err error) {
return nil
}

func (i *IPv6) copy() (copied IPv6) {
return IPv6{
CheckAddress: i.CheckAddress,
}
}

func (i *IPv6) overrideWith(other IPv6) {
i.CheckAddress = gosettings.OverrideWithValidator(i.CheckAddress, other.CheckAddress)
}

func (i *IPv6) setDefaults() {
defaultCheckAddress := netip.MustParseAddrPort("[2606:4700::6810:84e5]:443")
i.CheckAddress = gosettings.DefaultComparable(i.CheckAddress, defaultCheckAddress)
}

func (i IPv6) String() string {
return i.toLinesNode().String()
}

func (i IPv6) toLinesNode() (node *gotree.Node) {
node = gotree.New("IPv6 settings:")
node.Appendf("Check address: %s", i.CheckAddress)
return node
}

func (i *IPv6) read(r *reader.Reader) (err error) {
i.CheckAddress, err = r.NetipAddrPort("IPV6_CHECK_ADDRESS")
return err
}
7 changes: 7 additions & 0 deletions internal/configuration/settings/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Settings struct {
Updater Updater
Version Version
VPN VPN
IPv6 IPv6
Pprof pprof.Settings
}

Expand All @@ -53,6 +54,7 @@ func (s *Settings) Validate(filterChoicesGetter FilterChoicesGetter, ipv6Support
"system": s.System.validate,
"updater": s.Updater.Validate,
"version": s.Version.validate,
"ipv6": s.IPv6.validate,
// Pprof validation done in pprof constructor
"VPN": func() error {
return s.VPN.Validate(filterChoicesGetter, ipv6Supported, warner)
Expand Down Expand Up @@ -85,6 +87,7 @@ func (s *Settings) copy() (copied Settings) {
Version: s.Version.copy(),
VPN: s.VPN.Copy(),
Pprof: s.Pprof.Copy(),
IPv6: s.IPv6.copy(),
}
}

Expand All @@ -106,6 +109,7 @@ func (s *Settings) OverrideWith(other Settings,
patchedSettings.Version.overrideWith(other.Version)
patchedSettings.VPN.OverrideWith(other.VPN)
patchedSettings.Pprof.OverrideWith(other.Pprof)
patchedSettings.IPv6.overrideWith(other.IPv6)
err = patchedSettings.Validate(filterChoicesGetter, ipv6Supported, warner)
if err != nil {
return err
Expand All @@ -121,6 +125,7 @@ func (s *Settings) SetDefaults() {
s.Health.SetDefaults()
s.HTTPProxy.setDefaults()
s.Log.setDefaults()
s.IPv6.setDefaults()
s.PublicIP.setDefaults()
s.Shadowsocks.setDefaults()
s.Storage.setDefaults()
Expand All @@ -142,6 +147,7 @@ func (s Settings) toLinesNode() (node *gotree.Node) {
node.AppendNode(s.DNS.toLinesNode())
node.AppendNode(s.Firewall.toLinesNode())
node.AppendNode(s.Log.toLinesNode())
node.AppendNode(s.IPv6.toLinesNode())
node.AppendNode(s.Health.toLinesNode())
node.AppendNode(s.Shadowsocks.toLinesNode())
node.AppendNode(s.HTTPProxy.toLinesNode())
Expand Down Expand Up @@ -208,6 +214,7 @@ func (s *Settings) Read(r *reader.Reader, warner Warner) (err error) {
"updater": s.Updater.read,
"version": s.Version.read,
"VPN": s.VPN.read,
"IPv6": s.IPv6.read,
"profiling": s.Pprof.Read,
}

Expand Down
2 changes: 2 additions & 0 deletions internal/configuration/settings/settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ func Test_Settings_String(t *testing.T) {
| └── Enabled: yes
├── Log settings:
| └── Log level: INFO
├── IPv6 settings:
| └── Check address: [2606:4700::6810:84e5]:443
├── Health settings:
| ├── Server listening address: 127.0.0.1:9999
| ├── Target address: cloudflare.com:443
Expand Down
18 changes: 18 additions & 0 deletions internal/firewall/iptables.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,24 @@ func (c *Config) acceptOutputTrafficToVPN(ctx context.Context,
return c.runIP6tablesInstruction(ctx, instruction)
}

func (c *Config) AcceptOutput(ctx context.Context,
protocol, intf string, ip netip.Addr, port uint16, remove bool,
) error {
interfaceFlag := "-o " + intf
if intf == "*" { // all interfaces
interfaceFlag = ""
}

instruction := fmt.Sprintf("%s OUTPUT -d %s %s -p %s -m %s --dport %d -j ACCEPT",
appendOrDelete(remove), ip, interfaceFlag, protocol, protocol, port)
if ip.Is4() {
return c.runIptablesInstruction(ctx, instruction)
} else if c.ip6Tables == "" {
return fmt.Errorf("accept output to VPN server: %w", ErrNeedIP6Tables)
}
return c.runIP6tablesInstruction(ctx, instruction)
}

// Thanks to @npawelek.
func (c *Config) acceptOutputFromIPToSubnet(ctx context.Context,
intf string, sourceIP netip.Addr, destinationSubnet netip.Prefix, remove bool,
Expand Down
12 changes: 11 additions & 1 deletion internal/netlink/interfaces.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
package netlink

import "github.com/qdm12/log"
import (
"context"
"net/netip"

"github.com/qdm12/log"
)

type DebugLogger interface {
Debug(message string)
Debugf(format string, args ...any)
Patch(options ...log.Option)
}

type Firewall interface {
AcceptOutput(ctx context.Context, protocol, intf string, ip netip.Addr,
port uint16, remove bool) (err error)
}
Loading
Loading