mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 19:13:34 +00:00
network: add auto-detection of optimal MTU for WireGuard interface, --wg-mtu flag, set max_mtu for Corrosion to 1232
This commit is contained in:
@@ -18,9 +18,11 @@ type Config struct {
|
||||
ManagementIP netip.Addr
|
||||
// WireGuardPort is the UDP port WireGuard listens on. Zero means the default port (51820).
|
||||
WireGuardPort int `json:",omitempty"`
|
||||
PrivateKey secret.Secret
|
||||
PublicKey secret.Secret
|
||||
Peers []PeerConfig `json:",omitempty"`
|
||||
// MTU of the WireGuard interface. Use EffectiveMTU to get the default if not set (zero).
|
||||
MTU int `json:",omitempty"`
|
||||
PrivateKey secret.Secret
|
||||
PublicKey secret.Secret
|
||||
Peers []PeerConfig `json:",omitempty"`
|
||||
}
|
||||
|
||||
type PeerConfig struct {
|
||||
@@ -48,6 +50,14 @@ func (c Config) EffectiveWireGuardPort() int {
|
||||
return DefaultWireGuardPort
|
||||
}
|
||||
|
||||
// EffectiveMTU returns the MTU for the WireGuard interface. Falls back to MaxWireGuardMTU if not set (zero).
|
||||
func (c Config) EffectiveMTU() int {
|
||||
if c.MTU != 0 {
|
||||
return c.MTU
|
||||
}
|
||||
return MaxWireGuardMTU
|
||||
}
|
||||
|
||||
// toDeviceConfig converts the configuration to a WireGuard device configuration. It updates the existing peers
|
||||
// without replacing them to not disrupt existing connections and to not lose track of the last handshake time.
|
||||
func (c Config) toDeviceConfig(currentPeers []wgtypes.Peer) (wgtypes.Config, error) {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package network
|
||||
|
||||
import "log/slog"
|
||||
|
||||
// DetectMTU returns the optimal MTU for the WireGuard interface based on the machine's egress network.
|
||||
// The egress MTU is capped at MaxWireGuardMTU to not overestimate the path MTU between machines which can go over
|
||||
// the public internet. If the egress MTU cannot be detected, it falls back to MaxWireGuardMTU.
|
||||
func DetectMTU() int {
|
||||
egressMTU, err := detectEgressMTU()
|
||||
if err != nil {
|
||||
slog.Warn("Failed to detect egress network MTU, falling back to the default WireGuard MTU.",
|
||||
"mtu", MaxWireGuardMTU, "err", err)
|
||||
return MaxWireGuardMTU
|
||||
}
|
||||
|
||||
mtu := egressMTU - wireGuardEncapOverhead
|
||||
// Clamp the computed MTU to the range [MinWireGuardMTU, MaxWireGuardMTU].
|
||||
mtu = min(max(mtu, MinWireGuardMTU), MaxWireGuardMTU)
|
||||
slog.Info("Detected optimal WireGuard MTU from the egress network.", "mtu", mtu, "egress_mtu", egressMTU)
|
||||
|
||||
return mtu
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
//go:build darwin
|
||||
|
||||
package network
|
||||
|
||||
import "errors"
|
||||
|
||||
// detectEgressMTU is a stub for Darwin. The machine daemon that performs detection only runs on Linux.
|
||||
func detectEgressMTU() (int, error) {
|
||||
return 0, errors.New("not implemented on darwin")
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//go:build linux
|
||||
|
||||
package network
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
// detectEgressMTU returns the MTU of the egress network interface.
|
||||
// It resolves the route to a public address to find the egress interface.
|
||||
func detectEgressMTU() (int, error) {
|
||||
// Resolve the route to a public address to determine the egress interface.
|
||||
routes, err := netlink.RouteGet(net.IPv4(1, 1, 1, 1))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("get route to public address: %w", err)
|
||||
}
|
||||
if len(routes) == 0 {
|
||||
return 0, fmt.Errorf("no route to public address")
|
||||
}
|
||||
route := routes[0]
|
||||
|
||||
link, err := netlink.LinkByIndex(route.LinkIndex)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("get egress interface for route: %w", err)
|
||||
}
|
||||
// Don't detect the MTU from the WireGuard interface itself if the default route happens to go through it.
|
||||
if link.Attrs().Name == WireGuardInterfaceName {
|
||||
return 0, fmt.Errorf("egress interface is the WireGuard interface '%s'", WireGuardInterfaceName)
|
||||
}
|
||||
|
||||
// Prefer the route-level MTU (e.g. set by PMTU discovery) over the interface MTU if present.
|
||||
if route.MTU > 0 {
|
||||
return route.MTU, nil
|
||||
}
|
||||
return link.Attrs().MTU, nil
|
||||
}
|
||||
@@ -12,6 +12,16 @@ import (
|
||||
const (
|
||||
WireGuardInterfaceName = "uncloud"
|
||||
DefaultWireGuardPort = 51820
|
||||
// MinWireGuardMTU is the minimum MTU for the WireGuard interface. The management traffic inside the tunnel uses
|
||||
// IPv6 whose minimum link MTU is 1280, so this is a safe floor that also keeps Corrosion's max_mtu (>= 1200) valid.
|
||||
MinWireGuardMTU = 1280
|
||||
// MaxWireGuardMTU is the conservative maximum MTU set by auto-detection and the fallback when detection fails.
|
||||
// It's the standard WireGuard MTU for a 1500-byte underlay (1500 - 80) that matches the kernel's default
|
||||
// for WireGuard links.
|
||||
MaxWireGuardMTU = 1500 - wireGuardEncapOverhead
|
||||
// wireGuardEncapOverhead is WireGuard's worst-case (IPv6 endpoint) encapsulation overhead: outer IPv6 (40) +
|
||||
// UDP (8) + WireGuard message header and auth tag (32).
|
||||
wireGuardEncapOverhead = 80
|
||||
// WireGuardKeepaliveInterval is sensible interval that works with a wide variety of firewalls.
|
||||
WireGuardKeepaliveInterval = 25 * time.Second
|
||||
)
|
||||
|
||||
@@ -53,7 +53,6 @@ func createOrGetLink(name string) (netlink.Link, error) {
|
||||
return nil, fmt.Errorf("find WireGuard link %q: %v", name, err)
|
||||
}
|
||||
link = &netlink.GenericLink{
|
||||
// TODO: figure out how to set the most appropriate MTU.
|
||||
LinkAttrs: netlink.LinkAttrs{Name: name},
|
||||
LinkType: "wireguard",
|
||||
}
|
||||
@@ -91,6 +90,14 @@ func (n *WireGuardNetwork) Configure(config Config) error {
|
||||
}
|
||||
slog.Info("Updated addresses of the WireGuard interface.", "name", n.link.Attrs().Name, "addrs", addrs)
|
||||
|
||||
// Set the MTU on the WireGuard interface if it differs from the configured value.
|
||||
if mtu := config.EffectiveMTU(); n.link.Attrs().MTU != mtu {
|
||||
if err = netlink.LinkSetMTU(n.link, mtu); err != nil {
|
||||
return fmt.Errorf("set MTU %d on WireGuard link %q: %w", mtu, n.link.Attrs().Name, err)
|
||||
}
|
||||
slog.Info("Set MTU on the WireGuard interface.", "name", n.link.Attrs().Name, "mtu", mtu)
|
||||
}
|
||||
|
||||
// Bring the WireGuard interface up if it's not already up.
|
||||
if n.link.Attrs().Flags&unix.IFF_UP != unix.IFF_UP {
|
||||
if err = netlink.LinkSetUp(n.link); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user