diff --git a/cmd/gluetun/main.go b/cmd/gluetun/main.go index 97d20ea9..56749e3c 100644 --- a/cmd/gluetun/main.go +++ b/cmd/gluetun/main.go @@ -232,7 +232,12 @@ func _main(ctx context.Context, buildInfo models.BuildInformation, return err } - err = allSettings.Validate(storage) + ipv6Supported, err := netLinker.IsIPv6Supported() + if err != nil { + return fmt.Errorf("checking for IPv6 support: %w", err) + } + + err = allSettings.Validate(storage, ipv6Supported) if err != nil { return err } @@ -296,11 +301,6 @@ func _main(ctx context.Context, buildInfo models.BuildInformation, return err } - ipv6Supported, err := netLinker.IsIPv6Supported() - if err != nil { - return fmt.Errorf("checking for IPv6 support: %w", err) - } - if err := routingConf.Setup(); err != nil { if strings.Contains(err.Error(), "operation not permitted") { logger.Warn("💡 Tip: Are you passing NET_ADMIN capability to gluetun?") @@ -451,7 +451,8 @@ func _main(ctx context.Context, buildInfo models.BuildInformation, "http server", goroutine.OptionTimeout(defaultShutdownTimeout)) httpServer, err := server.New(httpServerCtx, controlServerAddress, controlServerLogging, logger.New(log.SetComponent("http server")), - buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper, storage) + buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper, + storage, ipv6Supported) if err != nil { return fmt.Errorf("cannot setup control server: %w", err) } diff --git a/internal/cli/openvpnconfig.go b/internal/cli/openvpnconfig.go index 41bc6696..c4883853 100644 --- a/internal/cli/openvpnconfig.go +++ b/internal/cli/openvpnconfig.go @@ -51,15 +51,15 @@ func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source Source, return err } - if err = allSettings.Validate(storage); err != nil { - return err - } - ipv6Supported, err := ipv6Checker.IsIPv6Supported() if err != nil { return fmt.Errorf("checking for IPv6 support: %w", err) } + if err = allSettings.Validate(storage, ipv6Supported); err != nil { + return fmt.Errorf("validating settings: %w", err) + } + // Unused by this CLI command unzipper := (Unzipper)(nil) client := (*http.Client)(nil) diff --git a/internal/configuration/settings/errors.go b/internal/configuration/settings/errors.go index cc40c459..3803fe7e 100644 --- a/internal/configuration/settings/errors.go +++ b/internal/configuration/settings/errors.go @@ -39,6 +39,7 @@ var ( ErrWireguardEndpointPortNotSet = errors.New("endpoint port is not set") ErrWireguardEndpointPortSet = errors.New("endpoint port is set") ErrWireguardInterfaceAddressNotSet = errors.New("interface address is not set") + ErrWireguardInterfaceAddressIPv6 = errors.New("interface address is IPv6 but IPv6 is not supported") ErrWireguardInterfaceNotValid = errors.New("interface name is not valid") ErrWireguardPreSharedKeyNotSet = errors.New("pre-shared key is not set") ErrWireguardPrivateKeyNotSet = errors.New("private key is not set") diff --git a/internal/configuration/settings/settings.go b/internal/configuration/settings/settings.go index b9042553..6c3e6da0 100644 --- a/internal/configuration/settings/settings.go +++ b/internal/configuration/settings/settings.go @@ -31,7 +31,7 @@ type Storage interface { // Validate validates all the settings and returns an error // if one of them is not valid. // TODO v4 remove pointer for receiver (because of Surfshark). -func (s *Settings) Validate(storage Storage) (err error) { +func (s *Settings) Validate(storage Storage, ipv6Supported bool) (err error) { nameToValidation := map[string]func() error{ "control server": s.ControlServer.validate, "dns": s.DNS.validate, @@ -46,7 +46,7 @@ func (s *Settings) Validate(storage Storage) (err error) { "version": s.Version.validate, // Pprof validation done in pprof constructor "VPN": func() error { - return s.VPN.Validate(storage) + return s.VPN.Validate(storage, ipv6Supported) }, } @@ -95,7 +95,7 @@ func (s *Settings) MergeWith(other Settings) { } func (s *Settings) OverrideWith(other Settings, - storage Storage) (err error) { + storage Storage, ipv6Supported bool) (err error) { patchedSettings := s.copy() patchedSettings.ControlServer.overrideWith(other.ControlServer) patchedSettings.DNS.overrideWith(other.DNS) @@ -110,7 +110,7 @@ func (s *Settings) OverrideWith(other Settings, patchedSettings.Version.overrideWith(other.Version) patchedSettings.VPN.OverrideWith(other.VPN) patchedSettings.Pprof.OverrideWith(other.Pprof) - err = patchedSettings.Validate(storage) + err = patchedSettings.Validate(storage, ipv6Supported) if err != nil { return err } diff --git a/internal/configuration/settings/vpn.go b/internal/configuration/settings/vpn.go index 8977638e..d84db03c 100644 --- a/internal/configuration/settings/vpn.go +++ b/internal/configuration/settings/vpn.go @@ -20,7 +20,7 @@ type VPN struct { } // TODO v4 remove pointer for receiver (because of Surfshark). -func (v *VPN) Validate(storage Storage) (err error) { +func (v *VPN) Validate(storage Storage, ipv6Supported bool) (err error) { // Validate Type validVPNTypes := []string{vpn.OpenVPN, vpn.Wireguard} if !helpers.IsOneOf(v.Type, validVPNTypes...) { @@ -39,7 +39,7 @@ func (v *VPN) Validate(storage Storage) (err error) { return fmt.Errorf("OpenVPN settings: %w", err) } } else { - err := v.Wireguard.validate(*v.Provider.Name) + err := v.Wireguard.validate(*v.Provider.Name, ipv6Supported) if err != nil { return fmt.Errorf("Wireguard settings: %w", err) } diff --git a/internal/configuration/settings/wireguard.go b/internal/configuration/settings/wireguard.go index d6d3c06d..932e3d20 100644 --- a/internal/configuration/settings/wireguard.go +++ b/internal/configuration/settings/wireguard.go @@ -38,7 +38,7 @@ var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`) // Validate validates Wireguard settings. // It should only be ran if the VPN type chosen is Wireguard. -func (w Wireguard) validate(vpnProvider string) (err error) { +func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error) { if !helpers.IsOneOf(vpnProvider, providers.Custom, providers.Ivpn, @@ -82,6 +82,12 @@ func (w Wireguard) validate(vpnProvider string) (err error) { return fmt.Errorf("%w: for address at index %d: %s", ErrWireguardInterfaceAddressNotSet, i, ipNet.String()) } + + ipv6Net := ipNet.IP.To4() == nil + if ipv6Net && !ipv6Supported { + return fmt.Errorf("%w: address %s", + ErrWireguardInterfaceAddressIPv6, ipNet) + } } // Validate interface diff --git a/internal/server/handler.go b/internal/server/handler.go index 00173d5b..29ae2ca5 100644 --- a/internal/server/handler.go +++ b/internal/server/handler.go @@ -16,10 +16,11 @@ func newHandler(ctx context.Context, logger infoWarner, logging bool, updaterLooper UpdaterLooper, publicIPLooper PublicIPLoop, storage Storage, + ipv6Supported bool, ) http.Handler { handler := &handler{} - vpn := newVPNHandler(ctx, vpnLooper, storage, logger) + vpn := newVPNHandler(ctx, vpnLooper, storage, ipv6Supported, logger) openvpn := newOpenvpnHandler(ctx, vpnLooper, pfGetter, logger) dns := newDNSHandler(ctx, unboundLooper, logger) updater := newUpdaterHandler(ctx, updaterLooper, logger) diff --git a/internal/server/server.go b/internal/server/server.go index e7e431d3..8790c017 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -11,10 +11,12 @@ import ( func New(ctx context.Context, address string, logEnabled bool, logger Logger, buildInfo models.BuildInformation, openvpnLooper VPNLooper, pfGetter PortForwardedGetter, unboundLooper DNSLoop, - updaterLooper UpdaterLooper, publicIPLooper PublicIPLoop, storage Storage) ( + updaterLooper UpdaterLooper, publicIPLooper PublicIPLoop, storage Storage, + ipv6Supported bool) ( server *httpserver.Server, err error) { handler := newHandler(ctx, logger, logEnabled, buildInfo, - openvpnLooper, pfGetter, unboundLooper, updaterLooper, publicIPLooper, storage) + openvpnLooper, pfGetter, unboundLooper, updaterLooper, publicIPLooper, + storage, ipv6Supported) httpServerSettings := httpserver.Settings{ Address: address, diff --git a/internal/server/vpn.go b/internal/server/vpn.go index ff91a5ae..e5a35588 100644 --- a/internal/server/vpn.go +++ b/internal/server/vpn.go @@ -10,20 +10,22 @@ import ( ) func newVPNHandler(ctx context.Context, looper VPNLooper, - storage Storage, w warner) http.Handler { + storage Storage, ipv6Supported bool, w warner) http.Handler { return &vpnHandler{ - ctx: ctx, - looper: looper, - storage: storage, - warner: w, + ctx: ctx, + looper: looper, + storage: storage, + ipv6Supported: ipv6Supported, + warner: w, } } type vpnHandler struct { - ctx context.Context //nolint:containedctx - looper VPNLooper - storage Storage - warner warner + ctx context.Context //nolint:containedctx + looper VPNLooper + storage Storage + ipv6Supported bool + warner warner } func (h *vpnHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -114,7 +116,7 @@ func (h *vpnHandler) patchSettings(w http.ResponseWriter, r *http.Request) { updatedSettings := h.looper.GetSettings() // already copied updatedSettings.OverrideWith(overrideSettings) - err = updatedSettings.Validate(h.storage) + err = updatedSettings.Validate(h.storage, h.ipv6Supported) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return