diff --git a/backend/wireguard/config.go b/backend/wireguard/config.go index 1b8665b9..a068ff70 100644 --- a/backend/wireguard/config.go +++ b/backend/wireguard/config.go @@ -17,6 +17,7 @@ type Config struct { PrivateKey string `json:"private_key"` PreSharedKey string `json:"pre_shared_key,omitempty"` ListenPort int `json:"listen_port"` + MTU *int `json:"mtu,omitempty"` Address []string `json:"address"` Latency *LatencyConfig `json:"latency,omitempty"` @@ -61,6 +62,9 @@ func NewConfig(config string) (*Config, error) { } // Validate configuration + if err := validateMTU(wgConfig.MTU); err != nil { + return nil, err + } if wgConfig.InterfaceName == "" { wgConfig.InterfaceName = "wg0" } @@ -81,6 +85,13 @@ func NewConfig(config string) (*Config, error) { return &wgConfig, nil } +func validateMTU(mtu *int) error { + if mtu != nil && (*mtu < 576 || *mtu > 9000) { + return errors.New("mtu must be an integer between 576 and 9000") + } + return nil +} + // InterfaceNetworks returns CIDR prefixes parsed from the node's core `address` list. // Used to restrict peer AllowedIPs to subnets this interface actually serves. func (c *Config) InterfaceNetworks() []*net.IPNet { diff --git a/backend/wireguard/manager.go b/backend/wireguard/manager.go index 10537438..19426f64 100644 --- a/backend/wireguard/manager.go +++ b/backend/wireguard/manager.go @@ -74,11 +74,14 @@ func wrapPermissionDeniedError(action string, err error) error { // Manager handles WireGuard interface management using wgctrl type Manager struct { - client wgClient - iFaceName string - nl netlinkOps - configure configureDeviceFunc - mu sync.RWMutex + client wgClient + iFaceName string + nl netlinkOps + configure configureDeviceFunc + setLinkMTU func(netlink.Link, int) error + defaultMTU int + mtuManaged bool + mu sync.RWMutex } // NewManager creates a new WireGuard manager @@ -126,9 +129,17 @@ func buildInitialWGConfig(privateKey wgtypes.Key, listenPort int, peers []wgtype // InitializeWithPeers sets up the WireGuard interface with initial configuration and optional full peer snapshot. func (m *Manager) InitializeWithPeers(privateKey wgtypes.Key, listenPort int, serverIPs []string, peers []wgtypes.PeerConfig) error { + return m.initializeWithPeers(privateKey, listenPort, serverIPs, peers, nil) +} + +func (m *Manager) initializeWithPeers(privateKey wgtypes.Key, listenPort int, serverIPs []string, peers []wgtypes.PeerConfig, mtu *int) error { m.mu.Lock() defer m.mu.Unlock() + if err := validateMTU(mtu); err != nil { + return err + } + if m.client == nil { return fmt.Errorf("wgctrl client is not initialized") } @@ -173,6 +184,11 @@ func (m *Manager) InitializeWithPeers(privateKey wgtypes.Key, listenPort int, se if err != nil { return fmt.Errorf("failed to get link: %w", err) } + m.defaultMTU = 0 + m.mtuManaged = false + if err := m.applyMTULocked(mtu); err != nil { + return err + } for _, addr := range parsedAddrs { if err := nl.AddrAdd(link2, addr); err != nil { @@ -226,6 +242,10 @@ func (m *Manager) ApplyPeersReplaceAll(peers []wgtypes.PeerConfig) error { // ApplyConfig safely configures the device with the given configuration under lock. func (m *Manager) ApplyConfig(config wgtypes.Config) error { + return m.applyConfig(config, nil) +} + +func (m *Manager) applyConfig(config wgtypes.Config, mtu *int) error { m.mu.Lock() defer m.mu.Unlock() @@ -233,10 +253,46 @@ func (m *Manager) ApplyConfig(config wgtypes.Config) error { return fmt.Errorf("wgctrl client is not initialized") } + if err := m.applyMTULocked(mtu); err != nil { + return err + } configure := m.getConfigureDevice() return configure(m.client, m.iFaceName, config) } +// applyMTULocked preserves the kernel default until an explicit MTU is configured. +// Clearing that setting restores the MTU captured before the first override. +func (m *Manager) applyMTULocked(mtu *int) error { + if err := validateMTU(mtu); err != nil { + return err + } + if mtu == nil && !m.mtuManaged { + return nil + } + target := m.defaultMTU + if mtu != nil { + target = *mtu + } + link, err := m.getNetlinkOps().LinkByName(m.iFaceName) + if err != nil { + return fmt.Errorf("failed to get link for MTU update: %w", err) + } + if m.defaultMTU == 0 { + m.defaultMTU = link.Attrs().MTU + } + if link.Attrs().MTU != target { + setMTU := m.setLinkMTU + if setMTU == nil { + setMTU = netlink.LinkSetMTU + } + if err := setMTU(link, target); err != nil { + return fmt.Errorf("failed to set interface MTU: %w", wrapPermissionDeniedError("setting wireguard interface MTU", err)) + } + } + m.mtuManaged = mtu != nil + return nil +} + // GetDevice returns the current WireGuard device statistics func (m *Manager) GetDevice() (*wgtypes.Device, error) { m.mu.RLock() diff --git a/backend/wireguard/wireguard.go b/backend/wireguard/wireguard.go index c41225ec..403cf9c8 100644 --- a/backend/wireguard/wireguard.go +++ b/backend/wireguard/wireguard.go @@ -147,6 +147,9 @@ func newWithManagerFactory(cfg *config.Config, wgConfig *Config, users []*common } wg.config = wgConfig + if err := validateMTU(wgConfig.MTU); err != nil { + return nil, err + } log.Println("config loaded in", time.Since(start).Seconds(), "second.") @@ -177,7 +180,7 @@ func newWithManagerFactory(cfg *config.Config, wgConfig *Config, users []*common } // Initialize the WireGuard interface with peers in the same kernel configure call. - if err = manager.InitializeWithPeers(privateKey, wgConfig.ListenPort, wgConfig.Address, startupPeerConfigs); err != nil { + if err = manager.initializeWithPeers(privateKey, wgConfig.ListenPort, wgConfig.Address, startupPeerConfigs, wgConfig.MTU); err != nil { manager.Close() return nil, fmt.Errorf("failed to initialize interface: %w", err) } @@ -278,7 +281,7 @@ func (wg *WireGuard) restartLocked() error { ReplacePeers: true, } - if err := manager.ApplyConfig(config); err != nil { + if err := manager.applyConfig(config, cfg.MTU); err != nil { return fmt.Errorf("failed to reconfigure interface during restart: %w", err) }