Skip to content

Commit 5b01324

Browse files
committed
hotfix(pmtud): detect IPv6 usage in VPN connection
1 parent 445f99d commit 5b01324

7 files changed

Lines changed: 69 additions & 24 deletions

File tree

internal/pmtud/ip/source.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"errors"
55
"fmt"
66
"net/netip"
7-
"strings"
87

98
"github.com/jsimonetti/rtnetlink"
109
"github.com/qdm12/gluetun/internal/pmtud/constants"
@@ -28,10 +27,7 @@ func SrcAddr(dst netip.AddrPort, proto int) (src netip.AddrPort, cleanup func(),
2827
return netip.AddrPortFrom(srcAddr, srcPort), cleanup, nil
2928
}
3029

31-
var (
32-
errNoRoute = errors.New("no route to destination")
33-
ErrNetworkUnreachable = errors.New("network unreachable")
34-
)
30+
var errNoRoute = errors.New("no route to destination")
3531

3632
func srcIP(dst netip.Addr) (netip.Addr, error) {
3733
conn, err := rtnetlink.Dial(nil)
@@ -54,9 +50,6 @@ func srcIP(dst netip.Addr) (netip.Addr, error) {
5450
}
5551
messages, err := conn.Route.Get(requestMessage)
5652
if err != nil {
57-
if strings.Contains(err.Error(), "network is unreachable") {
58-
err = ErrNetworkUnreachable
59-
}
6053
return netip.Addr{}, fmt.Errorf("getting routes to %s: %w", dst, err)
6154
}
6255

internal/pmtud/tcp/mss.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,6 @@ func findHighestMSSDestination(ctx context.Context, familyToFD map[int]fileDescr
4343
case err != nil: // error already occurred for another findMSS goroutine
4444
case errors.Is(result.err, iptables.ErrMarkMatchModuleMissing):
4545
err = fmt.Errorf("finding MSS for %s: %w", result.dst, result.err)
46-
case dst.Addr().Is6() && errors.Is(result.err, ip.ErrNetworkUnreachable):
47-
// silently discard IPv6 network unreachable errors since they are common
48-
// and expected when the host doesn't have IPv6 connectivity
4946
default: // another error not due to the match module missing
5047
logger.Debugf("finding MSS for %s failed: %s", result.dst, result.err)
5148
}

internal/pmtud/vpn.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
package pmtud
22

33
import (
4-
"net/netip"
5-
64
"github.com/qdm12/gluetun/internal/constants"
75
"github.com/qdm12/gluetun/internal/constants/vpn"
86
pconstants "github.com/qdm12/gluetun/internal/pmtud/constants"
97
)
108

119
// MaxTheoreticalVPNMTU returns the theoretical maximum MTU for a VPN tunnel
12-
// given the VPN type, network protocol, and VPN gateway IP address.
10+
// given the VPN type, network protocol, and whether IPv6 is used.
1311
// This is notably useful to skip testing MTU values higher than this value.
1412
// The function panics if the network or VPN type is unknown.
15-
func MaxTheoreticalVPNMTU(vpnType, network string, vpnGateway netip.Addr) uint32 {
13+
func MaxTheoreticalVPNMTU(vpnType, network string, ipv6 bool) uint32 {
1614
const physicalLinkMTU = pconstants.MaxEthernetFrameSize
1715
vpnLinkMTU := physicalLinkMTU
18-
if vpnGateway.Is4() {
16+
if !ipv6 {
1917
vpnLinkMTU -= pconstants.IPv4HeaderLength
2018
} else {
2119
vpnLinkMTU -= pconstants.IPv6HeaderLength

internal/vpn/interfaces.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ type Storage interface {
6161
}
6262

6363
type NetLinker interface {
64+
AddrList(linkIndex uint32, family uint8) (addresses []netip.Prefix, err error)
6465
AddrReplace(linkIndex uint32, addr netip.Prefix) error
6566
Router
6667
Ruler

internal/vpn/ipv6.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package vpn
2+
3+
import (
4+
"github.com/qdm12/gluetun/internal/configuration/settings"
5+
"github.com/qdm12/gluetun/internal/constants/vpn"
6+
"github.com/qdm12/gluetun/internal/netlink"
7+
)
8+
9+
func (l *Loop) isIPv6Used(settings settings.VPN) bool {
10+
if !l.ipv6SupportLevel.IsSupported() {
11+
return false
12+
}
13+
switch settings.Type {
14+
case vpn.AmneziaWg:
15+
for _, prefix := range settings.AmneziaWg.Wireguard.Addresses {
16+
if prefix.Addr().Is6() {
17+
return true
18+
}
19+
}
20+
return false
21+
case vpn.OpenVPN:
22+
link, err := l.netLinker.LinkByName(settings.OpenVPN.Interface)
23+
if err != nil {
24+
l.logger.Warnf("assuming IPv6 is not supported, cannot get OpenVPN link by name: %v", err)
25+
return false
26+
}
27+
ipv6Prefixes, err := l.netLinker.AddrList(link.Index, netlink.FamilyV6)
28+
if err != nil {
29+
l.logger.Warnf("assuming IPv6 is not supported, cannot list OpenVPN link addresses: %v", err)
30+
return false
31+
}
32+
for _, prefix := range ipv6Prefixes {
33+
if prefix.Addr().IsGlobalUnicast() && !prefix.Addr().IsPrivate() {
34+
return true
35+
}
36+
}
37+
return false
38+
case vpn.Wireguard:
39+
for _, prefix := range settings.Wireguard.Addresses {
40+
if prefix.Addr().Is6() {
41+
return true
42+
}
43+
}
44+
return false
45+
default:
46+
panic("vpn type not implemented: " + settings.Type)
47+
}
48+
}

internal/vpn/run.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
5959
enabled: settings.Type != vpn.Wireguard || *settings.Wireguard.MTU == 0,
6060
vpnType: settings.Type,
6161
network: connection.Protocol,
62+
ipv6: l.isIPv6Used(settings),
6263
icmpAddrs: settings.PMTUD.ICMPAddresses,
6364
tcpAddrs: settings.PMTUD.TCPAddresses,
6465
},

internal/vpn/tunnelup.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"net/netip"
7+
"slices"
78
"strings"
89
"time"
910

@@ -40,6 +41,8 @@ type tunnelUpPMTUDData struct {
4041
// network is used to find the network level header overhead.
4142
// It can be [constants.UDP] or [constants.TCP].
4243
network string
44+
// ipv6 is true if the VPN connection supports IPv6.
45+
ipv6 bool
4346
// icmpAddrs is the list of addresses to use for ICMP path MTU discovery.
4447
// Each address should handle ICMP packets for PMTUD to work.
4548
icmpAddrs []netip.Addr
@@ -69,7 +72,7 @@ func (l *Loop) onTunnelUp(ctx, loopCtx context.Context, data tunnelUpData) {
6972
if data.pmtud.enabled {
7073
mtuLogger := l.logger.New(log.SetComponent("MTU discovery"))
7174
err := updateToMaxMTU(ctx, data.vpnIntf, data.pmtud.vpnType,
72-
data.pmtud.network, data.pmtud.icmpAddrs, data.pmtud.tcpAddrs,
75+
data.pmtud.network, data.pmtud.ipv6, data.pmtud.icmpAddrs, data.pmtud.tcpAddrs,
7376
l.netLinker, l.routing, l.fw, mtuLogger)
7477
if err != nil {
7578
mtuLogger.Error(err.Error())
@@ -173,16 +176,11 @@ func (l *Loop) restartVPN(ctx context.Context, healthErr error) {
173176
}
174177

175178
func updateToMaxMTU(ctx context.Context, vpnInterface string,
176-
vpnType, network string, icmpAddrs []netip.Addr, tcpAddrs []netip.AddrPort,
179+
vpnType, network string, ipv6 bool, icmpAddrs []netip.Addr, tcpAddrs []netip.AddrPort,
177180
netlinker NetLinker, routing Routing, firewall tcp.Firewall, logger *log.Logger,
178181
) error {
179182
logger.Info("finding maximum MTU, this can take up to 6 seconds")
180183

181-
vpnGatewayIP, err := routing.VPNLocalGatewayIP(vpnInterface)
182-
if err != nil {
183-
return fmt.Errorf("getting VPN gateway IP address: %w", err)
184-
}
185-
186184
vpnRoutes, err := routing.VPNRoutes(vpnInterface)
187185
if err != nil {
188186
return fmt.Errorf("getting VPN routes: %w", err)
@@ -195,7 +193,7 @@ func updateToMaxMTU(ctx context.Context, vpnInterface string,
195193

196194
originalMTU := link.MTU
197195

198-
vpnLinkMTU := pmtud.MaxTheoreticalVPNMTU(vpnType, network, vpnGatewayIP)
196+
vpnLinkMTU := pmtud.MaxTheoreticalVPNMTU(vpnType, network, ipv6)
199197

200198
// Setting the VPN link MTU to 1500 might interrupt the connection until
201199
// the new MTU is set again, but this is necessary to find the highest valid MTU.
@@ -206,6 +204,15 @@ func updateToMaxMTU(ctx context.Context, vpnInterface string,
206204
return fmt.Errorf("setting VPN interface %s MTU to %d: %w", vpnInterface, vpnLinkMTU, err)
207205
}
208206

207+
if !ipv6 {
208+
icmpAddrs = slices.DeleteFunc(icmpAddrs, func(addr netip.Addr) bool {
209+
return addr.Is6()
210+
})
211+
tcpAddrs = slices.DeleteFunc(tcpAddrs, func(addr netip.AddrPort) bool {
212+
return addr.Addr().Is6()
213+
})
214+
}
215+
209216
const pingTimeout = time.Second
210217
vpnLinkMTU, err = pmtud.PathMTUDiscover(ctx, icmpAddrs, tcpAddrs,
211218
vpnLinkMTU, pingTimeout, firewall, logger)

0 commit comments

Comments
 (0)