feat(netlink): detect ipv6 support level
- 'supported' if one ipv6 route is found that is not loopback and not a default route - 'internet' if one default ipv6 route is found
This commit is contained in:
@@ -242,10 +242,12 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ipv6Supported, err := netLinker.IsIPv6Supported()
|
ipv6SupportLevel, err := netLinker.FindIPv6SupportLevel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("checking for IPv6 support: %w", err)
|
return fmt.Errorf("checking for IPv6 support: %w", err)
|
||||||
}
|
}
|
||||||
|
ipv6Supported := ipv6SupportLevel == netlink.IPv6Supported ||
|
||||||
|
ipv6SupportLevel == netlink.IPv6Internet
|
||||||
|
|
||||||
err = allSettings.Validate(storage, ipv6Supported, logger)
|
err = allSettings.Validate(storage, ipv6Supported, logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -430,7 +432,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
httpClient, unzipper, parallelResolver, publicIPLooper.Fetcher(), openvpnFileExtractor)
|
httpClient, unzipper, parallelResolver, publicIPLooper.Fetcher(), openvpnFileExtractor)
|
||||||
|
|
||||||
vpnLogger := logger.New(log.SetComponent("vpn"))
|
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, allSettings.Health, healthChecker, healthcheckServer, ovpnConf, netLinker, firewallConf,
|
providers, storage, allSettings.Health, healthChecker, healthcheckServer, ovpnConf, netLinker, firewallConf,
|
||||||
routingConf, portForwardLooper, cmder, publicIPLooper, dnsLooper, vpnLogger, httpClient,
|
routingConf, portForwardLooper, cmder, publicIPLooper, dnsLooper, vpnLogger, httpClient,
|
||||||
buildInfo, *allSettings.Version.Enabled)
|
buildInfo, *allSettings.Version.Enabled)
|
||||||
@@ -474,7 +476,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
logger.New(log.SetComponent("http server")),
|
logger.New(log.SetComponent("http server")),
|
||||||
allSettings.ControlServer.AuthFilePath,
|
allSettings.ControlServer.AuthFilePath,
|
||||||
buildInfo, vpnLooper, portForwardLooper, dnsLooper, updaterLooper, publicIPLooper,
|
buildInfo, vpnLooper, portForwardLooper, dnsLooper, updaterLooper, publicIPLooper,
|
||||||
storage, ipv6Supported)
|
storage, ipv6SupportLevel.IsSupported())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("setting up control server: %w", err)
|
return fmt.Errorf("setting up control server: %w", err)
|
||||||
}
|
}
|
||||||
@@ -550,7 +552,7 @@ type netLinker interface {
|
|||||||
Ruler
|
Ruler
|
||||||
Linker
|
Linker
|
||||||
IsWireguardSupported() (ok bool, err error)
|
IsWireguardSupported() (ok bool, err error)
|
||||||
IsIPv6Supported() (ok bool, err error)
|
FindIPv6SupportLevel() (level netlink.IPv6SupportLevel, err error)
|
||||||
PatchLoggerLevel(level log.Level)
|
PatchLoggerLevel(level log.Level)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/netlink"
|
||||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
||||||
"github.com/qdm12/gluetun/internal/provider"
|
"github.com/qdm12/gluetun/internal/provider"
|
||||||
"github.com/qdm12/gluetun/internal/storage"
|
"github.com/qdm12/gluetun/internal/storage"
|
||||||
@@ -40,7 +41,7 @@ type IPFetcher interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type IPv6Checker interface {
|
type IPv6Checker interface {
|
||||||
IsIPv6Supported() (supported bool, err error)
|
FindIPv6SupportLevel() (level netlink.IPv6SupportLevel, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader,
|
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader,
|
||||||
@@ -58,12 +59,13 @@ func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader,
|
|||||||
}
|
}
|
||||||
allSettings.SetDefaults()
|
allSettings.SetDefaults()
|
||||||
|
|
||||||
ipv6Supported, err := ipv6Checker.IsIPv6Supported()
|
ipv6SupportLevel, err := ipv6Checker.FindIPv6SupportLevel()
|
||||||
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, logger); err != nil {
|
err = allSettings.Validate(storage, ipv6SupportLevel.IsSupported(), logger)
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("validating settings: %w", err)
|
return fmt.Errorf("validating settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,13 +81,13 @@ func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader,
|
|||||||
unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
|
unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
|
||||||
providerConf := providers.Get(allSettings.VPN.Provider.Name)
|
providerConf := providers.Get(allSettings.VPN.Provider.Name)
|
||||||
connection, err := providerConf.GetConnection(
|
connection, err := providerConf.GetConnection(
|
||||||
allSettings.VPN.Provider.ServerSelection, ipv6Supported)
|
allSettings.VPN.Provider.ServerSelection, ipv6SupportLevel == netlink.IPv6Internet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
lines := providerConf.OpenVPNConfig(connection,
|
lines := providerConf.OpenVPNConfig(connection,
|
||||||
allSettings.VPN.OpenVPN, ipv6Supported)
|
allSettings.VPN.OpenVPN, ipv6SupportLevel.IsSupported())
|
||||||
|
|
||||||
fmt.Println(strings.Join(lines, "\n"))
|
fmt.Println(strings.Join(lines, "\n"))
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -4,34 +4,56 @@ import (
|
|||||||
"fmt"
|
"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)
|
routes, err := n.RouteList(FamilyV6)
|
||||||
if err != nil {
|
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
|
// Check each route for IPv6 due to Podman bug listing IPv4 routes
|
||||||
// as IPv6 routes at container start, see:
|
// as IPv6 routes at container start, see:
|
||||||
// https://github.com/qdm12/gluetun/issues/1241#issuecomment-1333405949
|
// https://github.com/qdm12/gluetun/issues/1241#issuecomment-1333405949
|
||||||
|
level = IPv6Unsupported
|
||||||
for _, route := range routes {
|
for _, route := range routes {
|
||||||
link, err := n.LinkByIndex(route.LinkIndex)
|
link, err := n.LinkByIndex(route.LinkIndex)
|
||||||
if err != nil {
|
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()
|
destinationIsIPv6 := route.Dst.IsValid() && route.Dst.Addr().Is6()
|
||||||
switch {
|
switch {
|
||||||
case !sourceIsIPv6 && !destinationIsIPv6,
|
case sourceIsIPv4 && destinationIsIPv4,
|
||||||
destinationIsIPv6 && route.Dst.Addr().IsLoopback():
|
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",
|
if level == IPv6Unsupported {
|
||||||
len(routes))
|
n.debugLogger.Debugf("no IPv6 route found in %d routes", len(routes))
|
||||||
return false, nil
|
}
|
||||||
|
return level, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/loopstate"
|
"github.com/qdm12/gluetun/internal/loopstate"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/netlink"
|
||||||
"github.com/qdm12/gluetun/internal/vpn/state"
|
"github.com/qdm12/gluetun/internal/vpn/state"
|
||||||
"github.com/qdm12/log"
|
"github.com/qdm12/log"
|
||||||
)
|
)
|
||||||
@@ -21,10 +22,10 @@ type Loop struct {
|
|||||||
healthChecker HealthChecker
|
healthChecker HealthChecker
|
||||||
healthServer HealthServer
|
healthServer HealthServer
|
||||||
// Fixed parameters
|
// Fixed parameters
|
||||||
buildInfo models.BuildInformation
|
buildInfo models.BuildInformation
|
||||||
versionInfo bool
|
versionInfo bool
|
||||||
ipv6Supported bool
|
ipv6SupportLevel netlink.IPv6SupportLevel
|
||||||
vpnInputPorts []uint16 // TODO make changeable through stateful firewall
|
vpnInputPorts []uint16 // TODO make changeable through stateful firewall
|
||||||
// Configurators
|
// Configurators
|
||||||
openvpnConf OpenVPN
|
openvpnConf OpenVPN
|
||||||
netLinker NetLinker
|
netLinker NetLinker
|
||||||
@@ -51,7 +52,7 @@ const (
|
|||||||
defaultBackoffTime = 15 * time.Second
|
defaultBackoffTime = 15 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewLoop(vpnSettings settings.VPN, ipv6Supported bool, vpnInputPorts []uint16,
|
func NewLoop(vpnSettings settings.VPN, ipv6SupportLevel netlink.IPv6SupportLevel, vpnInputPorts []uint16,
|
||||||
providers Providers, storage Storage, healthSettings settings.Health,
|
providers Providers, storage Storage, healthSettings settings.Health,
|
||||||
healthChecker HealthChecker, healthServer HealthServer, openvpnConf OpenVPN,
|
healthChecker HealthChecker, healthServer HealthServer, openvpnConf OpenVPN,
|
||||||
netLinker NetLinker, fw Firewall, routing Routing,
|
netLinker NetLinker, fw Firewall, routing Routing,
|
||||||
@@ -69,32 +70,32 @@ func NewLoop(vpnSettings settings.VPN, ipv6Supported bool, vpnInputPorts []uint1
|
|||||||
state := state.New(statusManager, vpnSettings)
|
state := state.New(statusManager, vpnSettings)
|
||||||
|
|
||||||
return &Loop{
|
return &Loop{
|
||||||
statusManager: statusManager,
|
statusManager: statusManager,
|
||||||
state: state,
|
state: state,
|
||||||
providers: providers,
|
providers: providers,
|
||||||
storage: storage,
|
storage: storage,
|
||||||
healthSettings: healthSettings,
|
healthSettings: healthSettings,
|
||||||
healthChecker: healthChecker,
|
healthChecker: healthChecker,
|
||||||
healthServer: healthServer,
|
healthServer: healthServer,
|
||||||
buildInfo: buildInfo,
|
buildInfo: buildInfo,
|
||||||
versionInfo: versionInfo,
|
versionInfo: versionInfo,
|
||||||
ipv6Supported: ipv6Supported,
|
ipv6SupportLevel: ipv6SupportLevel,
|
||||||
vpnInputPorts: vpnInputPorts,
|
vpnInputPorts: vpnInputPorts,
|
||||||
openvpnConf: openvpnConf,
|
openvpnConf: openvpnConf,
|
||||||
netLinker: netLinker,
|
netLinker: netLinker,
|
||||||
fw: fw,
|
fw: fw,
|
||||||
routing: routing,
|
routing: routing,
|
||||||
portForward: portForward,
|
portForward: portForward,
|
||||||
publicip: publicip,
|
publicip: publicip,
|
||||||
dnsLooper: dnsLooper,
|
dnsLooper: dnsLooper,
|
||||||
starter: starter,
|
starter: starter,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
client: client,
|
client: client,
|
||||||
start: start,
|
start: start,
|
||||||
running: running,
|
running: running,
|
||||||
stop: stop,
|
stop: stop,
|
||||||
stopped: stopped,
|
stopped: stopped,
|
||||||
userTrigger: true,
|
userTrigger: true,
|
||||||
backoffTime: defaultBackoffTime,
|
backoffTime: defaultBackoffTime,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/netlink"
|
||||||
"github.com/qdm12/gluetun/internal/openvpn"
|
"github.com/qdm12/gluetun/internal/openvpn"
|
||||||
"github.com/qdm12/gluetun/internal/provider"
|
"github.com/qdm12/gluetun/internal/provider"
|
||||||
)
|
)
|
||||||
@@ -14,15 +15,16 @@ import (
|
|||||||
// It returns a serverName for port forwarding (PIA) and an error if it fails.
|
// It returns a serverName for port forwarding (PIA) and an error if it fails.
|
||||||
func setupOpenVPN(ctx context.Context, fw Firewall,
|
func setupOpenVPN(ctx context.Context, fw Firewall,
|
||||||
openvpnConf OpenVPN, providerConf provider.Provider,
|
openvpnConf OpenVPN, providerConf provider.Provider,
|
||||||
settings settings.VPN, ipv6Supported bool, starter CmdStarter,
|
settings settings.VPN, ipv6SupportLevel netlink.IPv6SupportLevel, starter CmdStarter,
|
||||||
logger openvpn.Logger) (runner *openvpn.Runner, connection models.Connection, err error,
|
logger openvpn.Logger) (runner *openvpn.Runner, connection models.Connection, 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 {
|
if err != nil {
|
||||||
return nil, models.Connection{}, fmt.Errorf("finding a valid server connection: %w", err)
|
return nil, models.Connection{}, 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 {
|
if err := openvpnConf.WriteConfig(lines); err != nil {
|
||||||
return nil, models.Connection{}, fmt.Errorf("writing configuration to file: %w", err)
|
return nil, models.Connection{}, fmt.Errorf("writing configuration to file: %w", err)
|
||||||
|
|||||||
@@ -36,11 +36,11 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
|||||||
if settings.Type == vpn.OpenVPN {
|
if settings.Type == vpn.OpenVPN {
|
||||||
vpnInterface = settings.OpenVPN.Interface
|
vpnInterface = settings.OpenVPN.Interface
|
||||||
vpnRunner, connection, err = setupOpenVPN(ctx, l.fw,
|
vpnRunner, connection, 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
|
} else { // Wireguard
|
||||||
vpnInterface = settings.Wireguard.Interface
|
vpnInterface = settings.Wireguard.Interface
|
||||||
vpnRunner, connection, err = setupWireguard(ctx, l.netLinker, l.fw,
|
vpnRunner, connection, err = setupWireguard(ctx, l.netLinker, l.fw,
|
||||||
providerConf, settings, l.ipv6Supported, subLogger)
|
providerConf, settings, l.ipv6SupportLevel, subLogger)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.crashed(ctx, err)
|
l.crashed(ctx, err)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
|
"github.com/qdm12/gluetun/internal/netlink"
|
||||||
"github.com/qdm12/gluetun/internal/provider"
|
"github.com/qdm12/gluetun/internal/provider"
|
||||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
"github.com/qdm12/gluetun/internal/wireguard"
|
"github.com/qdm12/gluetun/internal/wireguard"
|
||||||
@@ -16,15 +17,16 @@ import (
|
|||||||
// It returns a serverName for port forwarding (PIA) and an error if it fails.
|
// It returns a serverName for port forwarding (PIA) and an error if it fails.
|
||||||
func setupWireguard(ctx context.Context, netlinker NetLinker,
|
func setupWireguard(ctx context.Context, netlinker NetLinker,
|
||||||
fw Firewall, providerConf provider.Provider,
|
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, connection models.Connection, err error,
|
wireguarder *wireguard.Wireguard, connection models.Connection, 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 {
|
if err != nil {
|
||||||
return nil, models.Connection{}, fmt.Errorf("finding a VPN server: %w", err)
|
return nil, models.Connection{}, 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 server public key: " + wireguardSettings.PublicKey)
|
||||||
logger.Debug("Wireguard client private key: " + gosettings.ObfuscateKey(wireguardSettings.PrivateKey))
|
logger.Debug("Wireguard client private key: " + gosettings.ObfuscateKey(wireguardSettings.PrivateKey))
|
||||||
|
|||||||
Reference in New Issue
Block a user