Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/cni-plugin-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
strategy:
matrix:
cni: [flannel, calico, cilium]
iptables-mode: [legacy, nft]
iptables-mode: [legacy, nft, plain]
timeout-minutes: 15
runs-on: ubuntu-24.04
steps:
Expand Down
9 changes: 6 additions & 3 deletions cni-plugin/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,17 @@ func main() {
)
}

func configureLoggingLevel(logLevel string) {
switch strings.ToLower(logLevel) {
// configureLogging sets log level and configures outputs to stderr.
func configureLogging(conf *PluginConf) {
switch strings.ToLower(conf.LogLevel) {
case "debug":
logrus.SetLevel(logrus.DebugLevel)
case "info":
logrus.SetLevel(logrus.InfoLevel)
default:
logrus.SetLevel(logrus.WarnLevel)
}
logrus.SetOutput(os.Stderr)
}

// parseConfig parses the supplied configuration (and prevResult) from stdin.
Expand Down Expand Up @@ -147,7 +149,8 @@ func cmdAdd(args *skel.CmdArgs) error {
logrus.Errorf("error parsing config: %e", err)
return err
}
configureLoggingLevel(conf.LogLevel)
// Configure logging level and outputs with rotation
configureLogging(conf)

if conf.PrevResult != nil {
logrus.WithFields(logrus.Fields{
Expand Down
66 changes: 64 additions & 2 deletions pkg/iptables/iptables.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,9 @@ type FirewallConfiguration struct {
// https://github.com/istio/istio/blob/e83411e/pilot/docker/prepare_proxy.sh
func ConfigureFirewall(firewallConfiguration FirewallConfiguration) error {
log.Debugf("tracing script execution as [%s]", executionTraceID)
log.Debugf("using '%s' to set-up firewall rules", firewallConfiguration.BinPath)
log.Debugf("using '%s' to list all available rules", firewallConfiguration.SaveBinPath)

// Before executing, ensure the configured iptables binaries exist; if not, attempt a fallback.
resolveBinFallback(&firewallConfiguration, exec.LookPath)

existingRules, err := executeCommand(firewallConfiguration, firewallConfiguration.makeShowAllRules())
if err != nil {
Expand Down Expand Up @@ -112,6 +113,9 @@ func CleanupFirewallConfig(firewallConfiguration FirewallConfiguration) error {
log.Debugf("using '%s' to clean-up firewall rules", firewallConfiguration.BinPath)
log.Debugf("using '%s' to list all available rules", firewallConfiguration.SaveBinPath)

// Ensure binaries exist before attempting cleanup as well
resolveBinFallback(&firewallConfiguration, exec.LookPath)

commands := make([]*exec.Cmd, 0)
commands = firewallConfiguration.cleanupRules(commands)

Expand Down Expand Up @@ -448,3 +452,61 @@ func asDestination(portRange util.PortRange) string {

return fmt.Sprintf("%d:%d", portRange.LowerBound, portRange.UpperBound)
}

// resolveBinFallback ensures the configured BinPath and SaveBinPath exist on PATH; if not, it
// tries reasonable alternatives of the same family (ip6tables vs iptables).
func resolveBinFallback(fc *FirewallConfiguration, lookPath func(string) (string, error)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a getCommands function in cmd/root.go where the binaries to use is determined. I think that's a better place to place this logic, where we the ipv6 case is also considered without resorting to string comparison.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alpeb My only concern with moving this to the cmd/ package is that it in effect limits the implementation to the proxy-init command only. If we leave it in iptables/ all calls to Configure|Cleanup will benefit from the resolution. Thoughts?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes you're right, I missed the initialization is a bit different for cni vs proxy-init. Should be good as-is then 👍

// helper to check presence
has := func(name string) bool {
_, err := lookPath(name)
return err == nil
}

// Both present? nothing to do
if has(fc.BinPath) && has(fc.SaveBinPath) {
log.WithFields(log.Fields{
"requestedBin": fc.BinPath,
"requestedSaveBin": fc.SaveBinPath,
}).Debug("iptables: using configured binaries")
return
}

// Decide family based on current name
ipv6 := strings.Contains(fc.BinPath, "ip6tables") || strings.Contains(fc.SaveBinPath, "ip6tables")

// Candidate orders: prefer nft, then plain, then legacy
var candidates [][2]string
if ipv6 {
candidates = [][2]string{
{"ip6tables-nft", "ip6tables-nft-save"},
{"ip6tables", "ip6tables-save"},
{"ip6tables-legacy", "ip6tables-legacy-save"},
}
} else {
candidates = [][2]string{
{"iptables-nft", "iptables-nft-save"},
{"iptables", "iptables-save"},
{"iptables-legacy", "iptables-legacy-save"},
}
}

// Use first candidate where both exist
for _, pair := range candidates {
if has(pair[0]) && has(pair[1]) {
if pair[0] != fc.BinPath || pair[1] != fc.SaveBinPath {
log.WithFields(log.Fields{
"requestedBin": fc.BinPath,
"requestedSaveBin": fc.SaveBinPath,
"fallbackBin": pair[0],
"fallbackSaveBin": pair[1],
}).Warn("iptables: configured binaries not found; applying fallback to available binaries")
}
fc.BinPath = pair[0]
fc.SaveBinPath = pair[1]
return
}
}

// No candidates found; keep as-is and let execution fail with a clear error later
log.WithFields(log.Fields{"binPath": fc.BinPath, "saveBinPath": fc.SaveBinPath}).Error("iptables: no suitable binaries found on PATH; commands may fail")
}
89 changes: 89 additions & 0 deletions pkg/iptables/resolve_fallback_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package iptables

import (
"errors"
"testing"
)

// fakeLookPath returns a LookPath-like function backed by a set of available names.
func fakeLookPath(available []string) func(string) (string, error) {
return func(name string) (string, error) {
for _, a := range available {
if a == name {
return "/fake/" + name, nil
}
}
return "", errors.New("not found")
}
}

func TestResolveBinFallback_KeepWhenPresent(t *testing.T) {
fc := &FirewallConfiguration{BinPath: "iptables-nft", SaveBinPath: "iptables-nft-save"}
lp := fakeLookPath([]string{
"iptables-nft",
"iptables-nft-save",
})

resolveBinFallback(fc, lp)

if fc.BinPath != "iptables-nft" || fc.SaveBinPath != "iptables-nft-save" {
t.Fatalf("expected to keep configured binaries, got bin=%q save=%q", fc.BinPath, fc.SaveBinPath)
}
}

func TestResolveBinFallback_FallbackToNFT_IPv4(t *testing.T) {
fc := &FirewallConfiguration{BinPath: "iptables-notreal", SaveBinPath: "iptables-notreal-save"}
lp := fakeLookPath([]string{
// Only nft pair is available
"iptables-nft",
"iptables-nft-save",
})

resolveBinFallback(fc, lp)

if fc.BinPath != "iptables-nft" || fc.SaveBinPath != "iptables-nft-save" {
t.Fatalf("expected fallback to iptables-nft, got bin=%q save=%q", fc.BinPath, fc.SaveBinPath)
}
}

func TestResolveBinFallback_FallbackOrder_Plain(t *testing.T) {
fc := &FirewallConfiguration{BinPath: "iptables-missing", SaveBinPath: "iptables-missing-save"}
lp := fakeLookPath([]string{
// Only plain iptables present
"iptables",
"iptables-save",
})

resolveBinFallback(fc, lp)

if fc.BinPath != "iptables" || fc.SaveBinPath != "iptables-save" {
t.Fatalf("expected fallback to iptables/iptable-save, got bin=%q save=%q", fc.BinPath, fc.SaveBinPath)
}
}

func TestResolveBinFallback_IPv6_FallbackLegacy(t *testing.T) {
fc := &FirewallConfiguration{BinPath: "ip6tables-missing", SaveBinPath: "ip6tables-missing-save"}
lp := fakeLookPath([]string{
// Only legacy pair present for IPv6
"ip6tables-legacy",
"ip6tables-legacy-save",
})

resolveBinFallback(fc, lp)

if fc.BinPath != "ip6tables-legacy" || fc.SaveBinPath != "ip6tables-legacy-save" {
t.Fatalf("expected fallback to ip6tables-legacy, got bin=%q save=%q", fc.BinPath, fc.SaveBinPath)
}
}

func TestResolveBinFallback_NoCandidates(t *testing.T) {
origBin, origSave := "iptables-missing", "iptables-missing-save"
fc := &FirewallConfiguration{BinPath: origBin, SaveBinPath: origSave}
lp := fakeLookPath([]string{})

resolveBinFallback(fc, lp)

if fc.BinPath != origBin || fc.SaveBinPath != origSave {
t.Fatalf("expected no change when no candidates found, got bin=%q save=%q", fc.BinPath, fc.SaveBinPath)
}
}