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 e110f52..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 != "" { @@ -93,8 +95,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 { 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 {