From 67cc3e68ca694d7b057b9fbfc9268782cf3bb0ef Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 15 Oct 2026 01:28:34 +0200 Subject: [PATCH 1/2] ipvs: only close namespace handles when opened Only defer closing the network namespace handle when GetFromPath succeeds. Avoid calling Close on the invalid handle returned by netns.None when using the current network namespace. Signed-off-by: Sebastiaan van Stijn --- ipvs_linux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipvs_linux.go b/ipvs_linux.go index e110f52..37bc2a5 100644 --- a/ipvs_linux.go +++ b/ipvs_linux.go @@ -93,8 +93,8 @@ func New(path string) (*Handle, error) { if err != nil { return nil, err } + defer func() { _ = n.Close() }() } - defer n.Close() sock, err := nl.GetNetlinkSocketAt(n, netns.None(), unix.NETLINK_GENERIC) if err != nil { From 8a0ca3c19fa3b19955c6a9d3596f6781da115a26 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 15 Oct 2026 01:27:15 +0200 Subject: [PATCH 2/2] ipvs: propagate errors during netlink initialization Replace the global IPVS family ID and sync.Once with sync.OnceValues to cache both the family ID and initialization error. Try discovering the IPVS generic-netlink family before attempting modprobe. Linux 5.17 and newer support automatic module loading, but older kernels may still require explicit module loading. Keep modprobe as a best-effort fallback and retry discovery afterward. Only report module-loading errors if discovery also fails, preserving both errors to help diagnose initialization failures. Return initialization errors instead of logging them, removing the dependency on logrus. Signed-off-by: Sebastiaan van Stijn --- go.mod | 1 - go.sum | 6 ------ ipvs_linux.go | 4 +++- netlink_linux.go | 45 ++++++++++++++++++++++++++------------------- 4 files changed, 29 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index 6e33218..42095fc 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/moby/ipvs go 1.23 require ( - github.com/sirupsen/logrus v1.10.2 github.com/vishvananda/netlink v1.3.1 github.com/vishvananda/netns v0.0.5 golang.org/x/sys v0.13.0 diff --git a/go.sum b/go.sum index 4231fa6..532bf3d 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,7 @@ -github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo= -github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q= -github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= -github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0= github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= -go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= -go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= diff --git a/ipvs_linux.go b/ipvs_linux.go index 37bc2a5..d6835b0 100644 --- a/ipvs_linux.go +++ b/ipvs_linux.go @@ -84,7 +84,9 @@ type Handle struct { // passed path. It will return a valid handle or an error in case an // error occurred while creating the handle. func New(path string) (*Handle, error) { - setup() + if _, err := ipvsFamily(); err != nil { + return nil, err + } n := netns.None() if path != "" { diff --git a/netlink_linux.go b/netlink_linux.go index 86ffe00..c472b1a 100644 --- a/netlink_linux.go +++ b/netlink_linux.go @@ -8,24 +8,18 @@ import ( "math" "net" "os/exec" - "strings" "sync" "sync/atomic" "syscall" "time" - "github.com/sirupsen/logrus" "github.com/vishvananda/netlink/nl" "github.com/vishvananda/netns" "golang.org/x/sys/unix" ) // For Quick Reference IPVS related netlink message is described at the end of this file. -var ( - native = nl.NativeEndian() - ipvsFamily int - ipvsOnce sync.Once -) +var native = nl.NativeEndian() // genlMsgHdrLen is the serialized size of genlMsgHdr. Keep this explicit // rather than deriving it with unsafe.Sizeof so the wire format does not @@ -71,19 +65,31 @@ func (*ipvsFlags) Len() int { return ipvsFlagsLen } -func setup() { - ipvsOnce.Do(func() { - var err error - if out, err := exec.Command("modprobe", "-va", "ip_vs").CombinedOutput(); err != nil { - logrus.Warnf("Running modprobe ip_vs failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err) - } +// ipvsFamily lazily discovers and caches the IPVS generic-netlink family ID +// and any initialization error. +var ipvsFamily = sync.OnceValues(func() (int, error) { + family, err := getIPVSFamily() + if err == nil { + return family, nil + } - ipvsFamily, err = getIPVSFamily() - if err != nil { - logrus.Error("Could not get ipvs family information from the kernel. It is possible that ipvs is not enabled in your kernel. Native loadbalancing will not work until this is fixed.") + // Linux 5.17 added automatic loading of ip_vs through generic-netlink + // family discovery. Older kernels, including RHEL 8's 4.18 kernel, + // may still require explicit module loading. + // + // See https://lists.openwall.net/netdev/2021/11/01/229 + output, modprobeErr := exec.Command("modprobe", "-va", "ip_vs").CombinedOutput() + + family, err = getIPVSFamily() + if err != nil { + err = fmt.Errorf("get IPVS generic-netlink family: %w", err) + if modprobeErr != nil && !errors.Is(modprobeErr, exec.ErrNotFound) { + err = errors.Join(err, fmt.Errorf("modprobe ip_vs: %w: %s", modprobeErr, bytes.TrimSpace(output))) } - }) -} + return 0, err + } + return family, nil +}) func fillService(s *Service) nl.NetlinkRequestData { cmdAttr := nl.NewRtAttr(int(ipvsCmdAttrService), nil) @@ -218,7 +224,8 @@ func rawIPData(ip net.IP) []byte { } func newIPVSRequest(cmd ipvsCmd) *nl.NetlinkRequest { - return newGenlRequest(ipvsFamily, uint8(cmd)) + family, _ := ipvsFamily() + return newGenlRequest(family, uint8(cmd)) } func newGenlRequest(familyID int, cmd uint8) *nl.NetlinkRequest {