Skip to content

Commit 96a4a64

Browse files
committed
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
1 parent 7e58b4b commit 96a4a64

File tree

7 files changed

+94
-60
lines changed

7 files changed

+94
-60
lines changed

cmd/gluetun/main.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -242,10 +242,12 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
242242
return err
243243
}
244244

245-
ipv6Supported, err := netLinker.IsIPv6Supported()
245+
ipv6SupportLevel, err := netLinker.FindIPv6SupportLevel()
246246
if err != nil {
247247
return fmt.Errorf("checking for IPv6 support: %w", err)
248248
}
249+
ipv6Supported := ipv6SupportLevel == netlink.IPv6Supported ||
250+
ipv6SupportLevel == netlink.IPv6Internet
249251

250252
err = allSettings.Validate(storage, ipv6Supported, logger)
251253
if err != nil {
@@ -423,7 +425,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
423425
httpClient, unzipper, parallelResolver, publicIPLooper.Fetcher(), openvpnFileExtractor)
424426

425427
vpnLogger := logger.New(log.SetComponent("vpn"))
426-
vpnLooper := vpn.NewLoop(allSettings.VPN, ipv6Supported, allSettings.Firewall.VPNInputPorts,
428+
vpnLooper := vpn.NewLoop(allSettings.VPN, ipv6SupportLevel, allSettings.Firewall.VPNInputPorts,
427429
providers, storage, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
428430
cmder, publicIPLooper, dnsLooper, vpnLogger, httpClient,
429431
buildInfo, *allSettings.Version.Enabled)
@@ -467,7 +469,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
467469
logger.New(log.SetComponent("http server")),
468470
allSettings.ControlServer.AuthFilePath,
469471
buildInfo, vpnLooper, portForwardLooper, dnsLooper, updaterLooper, publicIPLooper,
470-
storage, ipv6Supported)
472+
storage, ipv6SupportLevel.IsSupported())
471473
if err != nil {
472474
return fmt.Errorf("setting up control server: %w", err)
473475
}
@@ -549,7 +551,7 @@ type netLinker interface {
549551
Ruler
550552
Linker
551553
IsWireguardSupported() (ok bool, err error)
552-
IsIPv6Supported() (ok bool, err error)
554+
FindIPv6SupportLevel() (level netlink.IPv6SupportLevel, err error)
553555
PatchLoggerLevel(level log.Level)
554556
}
555557

internal/cli/openvpnconfig.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/qdm12/gluetun/internal/configuration/settings"
1212
"github.com/qdm12/gluetun/internal/constants"
1313
"github.com/qdm12/gluetun/internal/models"
14+
"github.com/qdm12/gluetun/internal/netlink"
1415
"github.com/qdm12/gluetun/internal/openvpn/extract"
1516
"github.com/qdm12/gluetun/internal/provider"
1617
"github.com/qdm12/gluetun/internal/storage"
@@ -40,7 +41,7 @@ type IPFetcher interface {
4041
}
4142

4243
type IPv6Checker interface {
43-
IsIPv6Supported() (supported bool, err error)
44+
FindIPv6SupportLevel() (level netlink.IPv6SupportLevel, err error)
4445
}
4546

4647
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader,
@@ -57,12 +58,13 @@ func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader,
5758
return err
5859
}
5960

60-
ipv6Supported, err := ipv6Checker.IsIPv6Supported()
61+
ipv6SupportLevel, err := ipv6Checker.FindIPv6SupportLevel()
6162
if err != nil {
6263
return fmt.Errorf("checking for IPv6 support: %w", err)
6364
}
6465

65-
if err = allSettings.Validate(storage, ipv6Supported, logger); err != nil {
66+
err = allSettings.Validate(storage, ipv6SupportLevel.IsSupported(), logger)
67+
if err != nil {
6668
return fmt.Errorf("validating settings: %w", err)
6769
}
6870

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

8688
lines := providerConf.OpenVPNConfig(connection,
87-
allSettings.VPN.OpenVPN, ipv6Supported)
89+
allSettings.VPN.OpenVPN, ipv6SupportLevel.IsSupported())
8890

8991
fmt.Println(strings.Join(lines, "\n"))
9092
return nil

internal/netlink/ipv6.go

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,56 @@ import (
44
"fmt"
55
)
66

7-
func (n *NetLink) IsIPv6Supported() (supported bool, err error) {
7+
type IPv6SupportLevel uint8
8+
9+
const (
10+
IPv6Unsupported = iota
11+
// IPv6Supported indicates the host supports IPv6 but has no access to the
12+
// Internet via IPv6. It is true if one IPv6 route is found and no default
13+
// IPv6 route is found.
14+
IPv6Supported
15+
// IPv6Internet indicates the host has access to the Internet via IPv6,
16+
// which is detected when a default IPv6 route is found.
17+
IPv6Internet
18+
)
19+
20+
func (i IPv6SupportLevel) IsSupported() bool {
21+
return i == IPv6Supported || i == IPv6Internet
22+
}
23+
24+
func (n *NetLink) FindIPv6SupportLevel() (level IPv6SupportLevel, err error) {
825
routes, err := n.RouteList(FamilyV6)
926
if err != nil {
10-
return false, fmt.Errorf("listing IPv6 routes: %w", err)
27+
return IPv6Unsupported, fmt.Errorf("listing IPv6 routes: %w", err)
1128
}
1229

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

22-
sourceIsIPv6 := route.Src.IsValid() && route.Src.Is6()
40+
sourceIsIPv4 := route.Src.IsValid() && route.Src.Is4()
41+
destinationIsIPv4 := route.Dst.IsValid() && route.Dst.Addr().Is4()
2342
destinationIsIPv6 := route.Dst.IsValid() && route.Dst.Addr().Is6()
2443
switch {
25-
case !sourceIsIPv6 && !destinationIsIPv6,
44+
case sourceIsIPv4 && destinationIsIPv4,
2645
destinationIsIPv6 && route.Dst.Addr().IsLoopback():
27-
continue
46+
case route.Dst.Addr().IsUnspecified(): // default ipv6 route
47+
n.debugLogger.Debugf("IPv6 internet access is enabled on link %s", link.Name)
48+
return IPv6Internet, nil
49+
default: // non-default ipv6 route found
50+
n.debugLogger.Debugf("IPv6 is supported by link %s", link.Name)
51+
level = IPv6Supported
2852
}
29-
30-
n.debugLogger.Debugf("IPv6 is supported by link %s", link.Name)
31-
return true, nil
3253
}
3354

34-
n.debugLogger.Debugf("IPv6 is not supported after searching %d routes",
35-
len(routes))
36-
return false, nil
55+
if level == IPv6Unsupported {
56+
n.debugLogger.Debugf("no IPv6 route found in %d routes", len(routes))
57+
}
58+
return level, nil
3759
}

internal/vpn/loop.go

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/qdm12/gluetun/internal/constants"
99
"github.com/qdm12/gluetun/internal/loopstate"
1010
"github.com/qdm12/gluetun/internal/models"
11+
"github.com/qdm12/gluetun/internal/netlink"
1112
"github.com/qdm12/gluetun/internal/vpn/state"
1213
"github.com/qdm12/log"
1314
)
@@ -18,10 +19,10 @@ type Loop struct {
1819
providers Providers
1920
storage Storage
2021
// Fixed parameters
21-
buildInfo models.BuildInformation
22-
versionInfo bool
23-
ipv6Supported bool
24-
vpnInputPorts []uint16 // TODO make changeable through stateful firewall
22+
buildInfo models.BuildInformation
23+
versionInfo bool
24+
ipv6SupportLevel netlink.IPv6SupportLevel
25+
vpnInputPorts []uint16 // TODO make changeable through stateful firewall
2526
// Configurators
2627
openvpnConf OpenVPN
2728
netLinker NetLinker
@@ -48,8 +49,10 @@ const (
4849
defaultBackoffTime = 15 * time.Second
4950
)
5051

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

6770
return &Loop{
68-
statusManager: statusManager,
69-
state: state,
70-
providers: providers,
71-
storage: storage,
72-
buildInfo: buildInfo,
73-
versionInfo: versionInfo,
74-
ipv6Supported: ipv6Supported,
75-
vpnInputPorts: vpnInputPorts,
76-
openvpnConf: openvpnConf,
77-
netLinker: netLinker,
78-
fw: fw,
79-
routing: routing,
80-
portForward: portForward,
81-
publicip: publicip,
82-
dnsLooper: dnsLooper,
83-
starter: starter,
84-
logger: logger,
85-
client: client,
86-
start: start,
87-
running: running,
88-
stop: stop,
89-
stopped: stopped,
90-
userTrigger: true,
91-
backoffTime: defaultBackoffTime,
71+
statusManager: statusManager,
72+
state: state,
73+
providers: providers,
74+
storage: storage,
75+
buildInfo: buildInfo,
76+
versionInfo: versionInfo,
77+
ipv6SupportLevel: ipv6SupportLevel,
78+
vpnInputPorts: vpnInputPorts,
79+
openvpnConf: openvpnConf,
80+
netLinker: netLinker,
81+
fw: fw,
82+
routing: routing,
83+
portForward: portForward,
84+
publicip: publicip,
85+
dnsLooper: dnsLooper,
86+
starter: starter,
87+
logger: logger,
88+
client: client,
89+
start: start,
90+
running: running,
91+
stop: stop,
92+
stopped: stopped,
93+
userTrigger: true,
94+
backoffTime: defaultBackoffTime,
9295
}
9396
}

internal/vpn/openvpn.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66

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

25-
lines := providerConf.OpenVPNConfig(connection, settings.OpenVPN, ipv6Supported)
28+
lines := providerConf.OpenVPNConfig(connection, settings.OpenVPN, ipv6SupportLevel.IsSupported())
2629

2730
if err := openvpnConf.WriteConfig(lines); err != nil {
2831
return nil, "", false, fmt.Errorf("writing configuration to file: %w", err)

internal/vpn/run.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
3535
if settings.Type == vpn.OpenVPN {
3636
vpnInterface = settings.OpenVPN.Interface
3737
vpnRunner, serverName, canPortForward, err = setupOpenVPN(ctx, l.fw,
38-
l.openvpnConf, providerConf, settings, l.ipv6Supported, l.starter, subLogger)
38+
l.openvpnConf, providerConf, settings, l.ipv6SupportLevel, l.starter, subLogger)
3939
} else { // Wireguard
4040
vpnInterface = settings.Wireguard.Interface
4141
vpnRunner, serverName, canPortForward, err = setupWireguard(ctx, l.netLinker, l.fw,
42-
providerConf, settings, l.ipv6Supported, subLogger)
42+
providerConf, settings, l.ipv6SupportLevel, subLogger)
4343
}
4444
if err != nil {
4545
l.crashed(ctx, err)

internal/vpn/wireguard.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66

77
"github.com/qdm12/gluetun/internal/configuration/settings"
8+
"github.com/qdm12/gluetun/internal/netlink"
89
"github.com/qdm12/gluetun/internal/provider"
910
"github.com/qdm12/gluetun/internal/provider/utils"
1011
"github.com/qdm12/gluetun/internal/wireguard"
@@ -15,15 +16,16 @@ import (
1516
// It returns a serverName for port forwarding (PIA) and an error if it fails.
1617
func setupWireguard(ctx context.Context, netlinker NetLinker,
1718
fw Firewall, providerConf provider.Provider,
18-
settings settings.VPN, ipv6Supported bool, logger wireguard.Logger) (
19+
settings settings.VPN, ipv6SupportLevel netlink.IPv6SupportLevel, logger wireguard.Logger) (
1920
wireguarder *wireguard.Wireguard, serverName string, canPortForward bool, err error,
2021
) {
21-
connection, err := providerConf.GetConnection(settings.Provider.ServerSelection, ipv6Supported)
22+
ipv6Internet := ipv6SupportLevel == netlink.IPv6Internet
23+
connection, err := providerConf.GetConnection(settings.Provider.ServerSelection, ipv6Internet)
2224
if err != nil {
2325
return nil, "", false, fmt.Errorf("finding a VPN server: %w", err)
2426
}
2527

26-
wireguardSettings := utils.BuildWireguardSettings(connection, settings.Wireguard, ipv6Supported)
28+
wireguardSettings := utils.BuildWireguardSettings(connection, settings.Wireguard, ipv6SupportLevel.IsSupported())
2729

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

0 commit comments

Comments
 (0)