Skip to content

Commit

Permalink
feat(netlink): detect ipv6 support level
Browse files Browse the repository at this point in the history
- 'supported' if one ipv6 route is found that is not loopback and not a default route
- 'internet' if one default ipv6 route is found
  • Loading branch information
qdm12 committed Dec 10, 2024
1 parent f9bdb21 commit 83a5b73
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 60 deletions.
10 changes: 6 additions & 4 deletions cmd/gluetun/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,10 +242,12 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
return err
}

ipv6Supported, err := netLinker.IsIPv6Supported()
ipv6SupportLevel, err := netLinker.FindIPv6SupportLevel()
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 +425,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 +469,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 +551,7 @@ type netLinker interface {
Ruler
Linker
IsWireguardSupported() (ok bool, err error)
IsIPv6Supported() (ok bool, err error)
FindIPv6SupportLevel() (level netlink.IPv6SupportLevel, err error)
PatchLoggerLevel(level log.Level)
}

Expand Down
12 changes: 7 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,7 @@ type IPFetcher interface {
}

type IPv6Checker interface {
IsIPv6Supported() (supported bool, err error)
FindIPv6SupportLevel() (level netlink.IPv6SupportLevel, err error)
}

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

ipv6Supported, err := ipv6Checker.IsIPv6Supported()
ipv6SupportLevel, err := ipv6Checker.FindIPv6SupportLevel()
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 +80,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
46 changes: 34 additions & 12 deletions internal/netlink/ipv6.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,56 @@ import (
"fmt"
)

func (n *NetLink) IsIPv6Supported() (supported bool, err error) {
type IPv6SupportLevel uint8

const (
IPv6Unsupported = iota
// IPv6Supported indicates the host supports IPv6 but has no access to the
// Internet via IPv6. It is true if one IPv6 route is found and no default
// IPv6 route is found.
IPv6Supported
// IPv6Internet indicates the host has access to the Internet via IPv6,
// which is detected when a default IPv6 route is found.
IPv6Internet
)

func (i IPv6SupportLevel) IsSupported() bool {
return i == IPv6Supported || i == IPv6Internet
}

func (n *NetLink) FindIPv6SupportLevel() (level IPv6SupportLevel, err error) {
routes, err := n.RouteList(FamilyV6)
if err != nil {
return false, fmt.Errorf("listing IPv6 routes: %w", err)
return IPv6Unsupported, fmt.Errorf("listing IPv6 routes: %w", err)
}

// Check each route for IPv6 due to Podman bug listing IPv4 routes
// as IPv6 routes at container start, see:
// https://github.com/qdm12/gluetun/issues/1241#issuecomment-1333405949
level = IPv6Unsupported
for _, route := range routes {
link, err := n.LinkByIndex(route.LinkIndex)
if err != nil {
return false, fmt.Errorf("finding link corresponding to route: %w", err)
return IPv6Unsupported, fmt.Errorf("finding link corresponding to route: %w", err)
}

sourceIsIPv6 := route.Src.IsValid() && route.Src.Is6()
sourceIsIPv4 := route.Src.IsValid() && route.Src.Is4()
destinationIsIPv4 := route.Dst.IsValid() && route.Dst.Addr().Is4()
destinationIsIPv6 := route.Dst.IsValid() && route.Dst.Addr().Is6()
switch {
case !sourceIsIPv6 && !destinationIsIPv6,
case sourceIsIPv4 && destinationIsIPv4,
destinationIsIPv6 && route.Dst.Addr().IsLoopback():
continue
case route.Dst.Addr().IsUnspecified(): // default ipv6 route
n.debugLogger.Debugf("IPv6 internet access is enabled on link %s", link.Name)
return IPv6Internet, nil
default: // non-default ipv6 route found
n.debugLogger.Debugf("IPv6 is supported by link %s", link.Name)
level = IPv6Supported
}

n.debugLogger.Debugf("IPv6 is supported by link %s", link.Name)
return true, nil
}

n.debugLogger.Debugf("IPv6 is not supported after searching %d routes",
len(routes))
return false, nil
if level == IPv6Unsupported {
n.debugLogger.Debugf("no IPv6 route found in %d routes", len(routes))
}
return level, nil
}
63 changes: 33 additions & 30 deletions internal/vpn/loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/loopstate"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/netlink"
"github.com/qdm12/gluetun/internal/vpn/state"
"github.com/qdm12/log"
)
Expand All @@ -18,10 +19,10 @@ type Loop struct {
providers Providers
storage Storage
// Fixed parameters
buildInfo models.BuildInformation
versionInfo bool
ipv6Supported bool
vpnInputPorts []uint16 // TODO make changeable through stateful firewall
buildInfo models.BuildInformation
versionInfo bool
ipv6SupportLevel netlink.IPv6SupportLevel
vpnInputPorts []uint16 // TODO make changeable through stateful firewall
// Configurators
openvpnConf OpenVPN
netLinker NetLinker
Expand All @@ -48,8 +49,10 @@ const (
defaultBackoffTime = 15 * time.Second
)

func NewLoop(vpnSettings settings.VPN, ipv6Supported bool, vpnInputPorts []uint16,
providers Providers, storage Storage, openvpnConf OpenVPN,
func NewLoop(vpnSettings settings.VPN,
ipv6SupportLevel netlink.IPv6SupportLevel,
vpnInputPorts []uint16, providers Providers,
storage Storage, openvpnConf OpenVPN,
netLinker NetLinker, fw Firewall, routing Routing,
portForward PortForward, starter CmdStarter,
publicip PublicIPLoop, dnsLooper DNSLoop,
Expand All @@ -65,29 +68,29 @@ func NewLoop(vpnSettings settings.VPN, ipv6Supported bool, vpnInputPorts []uint1
state := state.New(statusManager, vpnSettings)

return &Loop{
statusManager: statusManager,
state: state,
providers: providers,
storage: storage,
buildInfo: buildInfo,
versionInfo: versionInfo,
ipv6Supported: ipv6Supported,
vpnInputPorts: vpnInputPorts,
openvpnConf: openvpnConf,
netLinker: netLinker,
fw: fw,
routing: routing,
portForward: portForward,
publicip: publicip,
dnsLooper: dnsLooper,
starter: starter,
logger: logger,
client: client,
start: start,
running: running,
stop: stop,
stopped: stopped,
userTrigger: true,
backoffTime: defaultBackoffTime,
statusManager: statusManager,
state: state,
providers: providers,
storage: storage,
buildInfo: buildInfo,
versionInfo: versionInfo,
ipv6SupportLevel: ipv6SupportLevel,
vpnInputPorts: vpnInputPorts,
openvpnConf: openvpnConf,
netLinker: netLinker,
fw: fw,
routing: routing,
portForward: portForward,
publicip: publicip,
dnsLooper: dnsLooper,
starter: starter,
logger: logger,
client: client,
start: start,
running: running,
stop: stop,
stopped: stopped,
userTrigger: true,
backoffTime: defaultBackoffTime,
}
}
11 changes: 7 additions & 4 deletions internal/vpn/openvpn.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"

"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/netlink"
"github.com/qdm12/gluetun/internal/openvpn"
"github.com/qdm12/gluetun/internal/provider"
)
Expand All @@ -13,16 +14,18 @@ import (
// It returns a serverName for port forwarding (PIA) and an error if it fails.
func setupOpenVPN(ctx context.Context, fw Firewall,
openvpnConf OpenVPN, providerConf provider.Provider,
settings settings.VPN, ipv6Supported bool, starter CmdStarter,
logger openvpn.Logger) (runner *openvpn.Runner, serverName string,
settings settings.VPN, ipv6SupportLevel netlink.IPv6SupportLevel,
starter CmdStarter, logger openvpn.Logger) (
runner *openvpn.Runner, serverName string,
canPortForward bool, err error,
) {
connection, err := providerConf.GetConnection(settings.Provider.ServerSelection, ipv6Supported)
ipv6Internet := ipv6SupportLevel == netlink.IPv6Internet
connection, err := providerConf.GetConnection(settings.Provider.ServerSelection, ipv6Internet)
if err != nil {
return nil, "", false, fmt.Errorf("finding a valid server connection: %w", err)
}

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

if err := openvpnConf.WriteConfig(lines); err != nil {
return nil, "", false, fmt.Errorf("writing configuration to file: %w", err)
Expand Down
4 changes: 2 additions & 2 deletions internal/vpn/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
if settings.Type == vpn.OpenVPN {
vpnInterface = settings.OpenVPN.Interface
vpnRunner, serverName, canPortForward, err = setupOpenVPN(ctx, l.fw,
l.openvpnConf, providerConf, settings, l.ipv6Supported, l.starter, subLogger)
l.openvpnConf, providerConf, settings, l.ipv6SupportLevel, l.starter, subLogger)
} else { // Wireguard
vpnInterface = settings.Wireguard.Interface
vpnRunner, serverName, canPortForward, err = setupWireguard(ctx, l.netLinker, l.fw,
providerConf, settings, l.ipv6Supported, subLogger)
providerConf, settings, l.ipv6SupportLevel, subLogger)
}
if err != nil {
l.crashed(ctx, err)
Expand Down
8 changes: 5 additions & 3 deletions internal/vpn/wireguard.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"

"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/netlink"
"github.com/qdm12/gluetun/internal/provider"
"github.com/qdm12/gluetun/internal/provider/utils"
"github.com/qdm12/gluetun/internal/wireguard"
Expand All @@ -15,15 +16,16 @@ import (
// It returns a serverName for port forwarding (PIA) and an error if it fails.
func setupWireguard(ctx context.Context, netlinker NetLinker,
fw Firewall, providerConf provider.Provider,
settings settings.VPN, ipv6Supported bool, logger wireguard.Logger) (
settings settings.VPN, ipv6SupportLevel netlink.IPv6SupportLevel, logger wireguard.Logger) (
wireguarder *wireguard.Wireguard, serverName string, canPortForward bool, err error,
) {
connection, err := providerConf.GetConnection(settings.Provider.ServerSelection, ipv6Supported)
ipv6Internet := ipv6SupportLevel == netlink.IPv6Internet
connection, err := providerConf.GetConnection(settings.Provider.ServerSelection, ipv6Internet)
if err != nil {
return nil, "", false, fmt.Errorf("finding a VPN server: %w", err)
}

wireguardSettings := utils.BuildWireguardSettings(connection, settings.Wireguard, ipv6Supported)
wireguardSettings := utils.BuildWireguardSettings(connection, settings.Wireguard, ipv6SupportLevel.IsSupported())

logger.Debug("Wireguard server public key: " + wireguardSettings.PublicKey)
logger.Debug("Wireguard client private key: " + gosettings.ObfuscateKey(wireguardSettings.PrivateKey))
Expand Down

0 comments on commit 83a5b73

Please sign in to comment.