diff --git a/pkg/ip/ipmasq_linux.go b/pkg/ip/ipmasq_linux.go index 0063e0a78..c1c54cc60 100644 --- a/pkg/ip/ipmasq_linux.go +++ b/pkg/ip/ipmasq_linux.go @@ -30,10 +30,18 @@ import ( // implementation will be used. func SetupIPMasqForNetworks(backend *string, ipns []*net.IPNet, network, ifname, containerID string) error { if backend == nil { - // Prefer iptables, unless only nftables is available + // Prefer iptables, unless only nftables is available. FIXME: flip this default at some point. defaultBackend := "iptables" - if !utils.SupportsIPTables() && utils.SupportsNFTables() { + supportsIPT := utils.SupportsIPTables() + supportsNFT := utils.SupportsNFTables() + if !supportsIPT && supportsNFT { defaultBackend = "nftables" + } else if !supportsIPT && !supportsNFT { + // Both full checks failed (likely missing CAP_NET_ADMIN at detection + // time). Fall back to binary presence as tiebreaker. + if !utils.IPTablesBinaryAvailable() && utils.NFTablesBinaryAvailable() { + defaultBackend = "nftables" + } } backend = &defaultBackend } diff --git a/pkg/utils/netfilter.go b/pkg/utils/netfilter.go index 1fa391404..7895da483 100644 --- a/pkg/utils/netfilter.go +++ b/pkg/utils/netfilter.go @@ -15,6 +15,8 @@ package utils import ( + "os/exec" + "github.com/coreos/go-iptables/iptables" "sigs.k8s.io/knftables" ) @@ -34,6 +36,20 @@ func SupportsIPTables() bool { return err == nil } +// IPTablesBinaryAvailable tests whether the iptables binary is present in PATH. +// Unlike SupportsIPTables, this does not require CAP_NET_ADMIN. +func IPTablesBinaryAvailable() bool { + _, err := exec.LookPath("iptables") + return err == nil +} + +// NFTablesBinaryAvailable tests whether the nft binary is present in PATH. +// Unlike SupportsNFTables, this does not require CAP_NET_ADMIN. +func NFTablesBinaryAvailable() bool { + _, err := exec.LookPath("nft") + return err == nil +} + // SupportsNFTables tests whether the system supports using netfilter via the nftables API // (ie, not via "iptables-nft"). (Note that this returns true if it is *possible* to use // nftables; it does not test whether any other components on the system are *actually* diff --git a/pkg/utils/netfilter_test.go b/pkg/utils/netfilter_test.go index d035ad387..892d34a29 100644 --- a/pkg/utils/netfilter_test.go +++ b/pkg/utils/netfilter_test.go @@ -48,5 +48,20 @@ var _ = Describe("netfilter support", func() { It("reports that nftables is not supported", func() { Expect(SupportsNFTables()).To(BeFalse(), "found nftables outside of PATH??") }) + It("reports that iptables binary is not available", func() { + Expect(IPTablesBinaryAvailable()).To(BeFalse(), "found iptables outside of PATH??") + }) + It("reports that nft binary is not available", func() { + Expect(NFTablesBinaryAvailable()).To(BeFalse(), "found nft outside of PATH??") + }) + }) + + When("binaries are available", func() { + It("reports that iptables binary is available", func() { + Expect(IPTablesBinaryAvailable()).To(BeTrue(), "This test should only fail if iptables is not available, but the test suite as a whole requires it to be available.") + }) + It("reports that nft binary is available", func() { + Expect(NFTablesBinaryAvailable()).To(BeTrue(), "This test should only fail if nft is not available, but the test suite as a whole requires it to be available.") + }) }) }) diff --git a/plugins/meta/portmap/main.go b/plugins/meta/portmap/main.go index 28dab921c..d6c4f4f5e 100644 --- a/plugins/meta/portmap/main.go +++ b/plugins/meta/portmap/main.go @@ -320,10 +320,21 @@ func ensureBackend(conf *PortMapConf) error { // If backend wasn't requested explicitly, default to iptables, unless it is not // available (and nftables is). FIXME: flip this default at some point. if conf.Backend == nil { - if !utils.SupportsIPTables() && utils.SupportsNFTables() { + supportsIPT := utils.SupportsIPTables() + supportsNFT := utils.SupportsNFTables() + switch { + case !supportsIPT && supportsNFT: conf.Backend = &nftablesBackend - } else { + case supportsIPT || supportsNFT: conf.Backend = &iptablesBackend + default: + // Both full checks failed (likely missing CAP_NET_ADMIN at detection + // time). Fall back to binary presence as tiebreaker. + if !utils.IPTablesBinaryAvailable() && utils.NFTablesBinaryAvailable() { + conf.Backend = &nftablesBackend + } else { + conf.Backend = &iptablesBackend + } } }