k8s.io/kubernetes@v1.29.3/pkg/util/iptables/iptables.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package iptables
    18  
    19  import (
    20  	"bufio"
    21  	"bytes"
    22  	"context"
    23  	"fmt"
    24  	"regexp"
    25  	"strconv"
    26  	"strings"
    27  	"sync"
    28  	"time"
    29  
    30  	"k8s.io/apimachinery/pkg/util/sets"
    31  	utilversion "k8s.io/apimachinery/pkg/util/version"
    32  	utilwait "k8s.io/apimachinery/pkg/util/wait"
    33  	"k8s.io/klog/v2"
    34  	utilexec "k8s.io/utils/exec"
    35  	utiltrace "k8s.io/utils/trace"
    36  )
    37  
    38  // RulePosition holds the -I/-A flags for iptable
    39  type RulePosition string
    40  
    41  const (
    42  	// Prepend is the insert flag for iptable
    43  	Prepend RulePosition = "-I"
    44  	// Append is the append flag for iptable
    45  	Append RulePosition = "-A"
    46  )
    47  
    48  // Interface is an injectable interface for running iptables commands.  Implementations must be goroutine-safe.
    49  type Interface interface {
    50  	// EnsureChain checks if the specified chain exists and, if not, creates it.  If the chain existed, return true.
    51  	EnsureChain(table Table, chain Chain) (bool, error)
    52  	// FlushChain clears the specified chain.  If the chain did not exist, return error.
    53  	FlushChain(table Table, chain Chain) error
    54  	// DeleteChain deletes the specified chain.  If the chain did not exist, return error.
    55  	DeleteChain(table Table, chain Chain) error
    56  	// ChainExists tests whether the specified chain exists, returning an error if it
    57  	// does not, or if it is unable to check.
    58  	ChainExists(table Table, chain Chain) (bool, error)
    59  	// EnsureRule checks if the specified rule is present and, if not, creates it.  If the rule existed, return true.
    60  	EnsureRule(position RulePosition, table Table, chain Chain, args ...string) (bool, error)
    61  	// DeleteRule checks if the specified rule is present and, if so, deletes it.
    62  	DeleteRule(table Table, chain Chain, args ...string) error
    63  	// IsIPv6 returns true if this is managing ipv6 tables.
    64  	IsIPv6() bool
    65  	// Protocol returns the IP family this instance is managing,
    66  	Protocol() Protocol
    67  	// SaveInto calls `iptables-save` for table and stores result in a given buffer.
    68  	SaveInto(table Table, buffer *bytes.Buffer) error
    69  	// Restore runs `iptables-restore` passing data through []byte.
    70  	// table is the Table to restore
    71  	// data should be formatted like the output of SaveInto()
    72  	// flush sets the presence of the "--noflush" flag. see: FlushFlag
    73  	// counters sets the "--counters" flag. see: RestoreCountersFlag
    74  	Restore(table Table, data []byte, flush FlushFlag, counters RestoreCountersFlag) error
    75  	// RestoreAll is the same as Restore except that no table is specified.
    76  	RestoreAll(data []byte, flush FlushFlag, counters RestoreCountersFlag) error
    77  	// Monitor detects when the given iptables tables have been flushed by an external
    78  	// tool (e.g. a firewall reload) by creating canary chains and polling to see if
    79  	// they have been deleted. (Specifically, it polls tables[0] every interval until
    80  	// the canary has been deleted from there, then waits a short additional time for
    81  	// the canaries to be deleted from the remaining tables as well. You can optimize
    82  	// the polling by listing a relatively empty table in tables[0]). When a flush is
    83  	// detected, this calls the reloadFunc so the caller can reload their own iptables
    84  	// rules. If it is unable to create the canary chains (either initially or after
    85  	// a reload) it will log an error and stop monitoring.
    86  	// (This function should be called from a goroutine.)
    87  	Monitor(canary Chain, tables []Table, reloadFunc func(), interval time.Duration, stopCh <-chan struct{})
    88  	// HasRandomFully reveals whether `-j MASQUERADE` takes the
    89  	// `--random-fully` option.  This is helpful to work around a
    90  	// Linux kernel bug that sometimes causes multiple flows to get
    91  	// mapped to the same IP:PORT and consequently some suffer packet
    92  	// drops.
    93  	HasRandomFully() bool
    94  
    95  	// Present checks if the kernel supports the iptable interface
    96  	Present() bool
    97  }
    98  
    99  // Protocol defines the ip protocol either ipv4 or ipv6
   100  type Protocol string
   101  
   102  const (
   103  	// ProtocolIPv4 represents ipv4 protocol in iptables
   104  	ProtocolIPv4 Protocol = "IPv4"
   105  	// ProtocolIPv6 represents ipv6 protocol in iptables
   106  	ProtocolIPv6 Protocol = "IPv6"
   107  )
   108  
   109  // Table represents different iptable like filter,nat, mangle and raw
   110  type Table string
   111  
   112  const (
   113  	// TableNAT represents the built-in nat table
   114  	TableNAT Table = "nat"
   115  	// TableFilter represents the built-in filter table
   116  	TableFilter Table = "filter"
   117  	// TableMangle represents the built-in mangle table
   118  	TableMangle Table = "mangle"
   119  )
   120  
   121  // Chain represents the different rules
   122  type Chain string
   123  
   124  const (
   125  	// ChainPostrouting used for source NAT in nat table
   126  	ChainPostrouting Chain = "POSTROUTING"
   127  	// ChainPrerouting used for DNAT (destination NAT) in nat table
   128  	ChainPrerouting Chain = "PREROUTING"
   129  	// ChainOutput used for the packets going out from local
   130  	ChainOutput Chain = "OUTPUT"
   131  	// ChainInput used for incoming packets
   132  	ChainInput Chain = "INPUT"
   133  	// ChainForward used for the packets for another NIC
   134  	ChainForward Chain = "FORWARD"
   135  )
   136  
   137  const (
   138  	cmdIPTablesSave     string = "iptables-save"
   139  	cmdIPTablesRestore  string = "iptables-restore"
   140  	cmdIPTables         string = "iptables"
   141  	cmdIP6TablesRestore string = "ip6tables-restore"
   142  	cmdIP6TablesSave    string = "ip6tables-save"
   143  	cmdIP6Tables        string = "ip6tables"
   144  )
   145  
   146  // RestoreCountersFlag is an option flag for Restore
   147  type RestoreCountersFlag bool
   148  
   149  // RestoreCounters a boolean true constant for the option flag RestoreCountersFlag
   150  const RestoreCounters RestoreCountersFlag = true
   151  
   152  // NoRestoreCounters a boolean false constant for the option flag RestoreCountersFlag
   153  const NoRestoreCounters RestoreCountersFlag = false
   154  
   155  // FlushFlag an option flag for Flush
   156  type FlushFlag bool
   157  
   158  // FlushTables a boolean true constant for option flag FlushFlag
   159  const FlushTables FlushFlag = true
   160  
   161  // NoFlushTables a boolean false constant for option flag FlushFlag
   162  const NoFlushTables FlushFlag = false
   163  
   164  // MinCheckVersion minimum version to be checked
   165  // Versions of iptables less than this do not support the -C / --check flag
   166  // (test whether a rule exists).
   167  var MinCheckVersion = utilversion.MustParseGeneric("1.4.11")
   168  
   169  // RandomFullyMinVersion is the minimum version from which the --random-fully flag is supported,
   170  // used for port mapping to be fully randomized
   171  var RandomFullyMinVersion = utilversion.MustParseGeneric("1.6.2")
   172  
   173  // WaitMinVersion a minimum iptables versions supporting the -w and -w<seconds> flags
   174  var WaitMinVersion = utilversion.MustParseGeneric("1.4.20")
   175  
   176  // WaitIntervalMinVersion a minimum iptables versions supporting the wait interval useconds
   177  var WaitIntervalMinVersion = utilversion.MustParseGeneric("1.6.1")
   178  
   179  // WaitSecondsMinVersion a minimum iptables versions supporting the wait seconds
   180  var WaitSecondsMinVersion = utilversion.MustParseGeneric("1.4.22")
   181  
   182  // WaitRestoreMinVersion a minimum iptables versions supporting the wait restore seconds
   183  var WaitRestoreMinVersion = utilversion.MustParseGeneric("1.6.2")
   184  
   185  // WaitString a constant for specifying the wait flag
   186  const WaitString = "-w"
   187  
   188  // WaitSecondsValue a constant for specifying the default wait seconds
   189  const WaitSecondsValue = "5"
   190  
   191  // WaitIntervalString a constant for specifying the wait interval flag
   192  const WaitIntervalString = "-W"
   193  
   194  // WaitIntervalUsecondsValue a constant for specifying the default wait interval useconds
   195  const WaitIntervalUsecondsValue = "100000"
   196  
   197  // LockfilePath16x is the iptables 1.6.x lock file acquired by any process that's making any change in the iptable rule
   198  const LockfilePath16x = "/run/xtables.lock"
   199  
   200  // LockfilePath14x is the iptables 1.4.x lock file acquired by any process that's making any change in the iptable rule
   201  const LockfilePath14x = "@xtables"
   202  
   203  // runner implements Interface in terms of exec("iptables").
   204  type runner struct {
   205  	mu              sync.Mutex
   206  	exec            utilexec.Interface
   207  	protocol        Protocol
   208  	hasCheck        bool
   209  	hasRandomFully  bool
   210  	waitFlag        []string
   211  	restoreWaitFlag []string
   212  	lockfilePath14x string
   213  	lockfilePath16x string
   214  }
   215  
   216  // newInternal returns a new Interface which will exec iptables, and allows the
   217  // caller to change the iptables-restore lockfile path
   218  func newInternal(exec utilexec.Interface, protocol Protocol, lockfilePath14x, lockfilePath16x string) Interface {
   219  	version, err := getIPTablesVersion(exec, protocol)
   220  	if err != nil {
   221  		klog.InfoS("Error checking iptables version, assuming version at least", "version", MinCheckVersion, "err", err)
   222  		version = MinCheckVersion
   223  	}
   224  
   225  	if lockfilePath16x == "" {
   226  		lockfilePath16x = LockfilePath16x
   227  	}
   228  	if lockfilePath14x == "" {
   229  		lockfilePath14x = LockfilePath14x
   230  	}
   231  
   232  	runner := &runner{
   233  		exec:            exec,
   234  		protocol:        protocol,
   235  		hasCheck:        version.AtLeast(MinCheckVersion),
   236  		hasRandomFully:  version.AtLeast(RandomFullyMinVersion),
   237  		waitFlag:        getIPTablesWaitFlag(version),
   238  		restoreWaitFlag: getIPTablesRestoreWaitFlag(version, exec, protocol),
   239  		lockfilePath14x: lockfilePath14x,
   240  		lockfilePath16x: lockfilePath16x,
   241  	}
   242  	return runner
   243  }
   244  
   245  // New returns a new Interface which will exec iptables.
   246  func New(exec utilexec.Interface, protocol Protocol) Interface {
   247  	return newInternal(exec, protocol, "", "")
   248  }
   249  
   250  // EnsureChain is part of Interface.
   251  func (runner *runner) EnsureChain(table Table, chain Chain) (bool, error) {
   252  	fullArgs := makeFullArgs(table, chain)
   253  
   254  	runner.mu.Lock()
   255  	defer runner.mu.Unlock()
   256  
   257  	out, err := runner.run(opCreateChain, fullArgs)
   258  	if err != nil {
   259  		if ee, ok := err.(utilexec.ExitError); ok {
   260  			if ee.Exited() && ee.ExitStatus() == 1 {
   261  				return true, nil
   262  			}
   263  		}
   264  		return false, fmt.Errorf("error creating chain %q: %v: %s", chain, err, out)
   265  	}
   266  	return false, nil
   267  }
   268  
   269  // FlushChain is part of Interface.
   270  func (runner *runner) FlushChain(table Table, chain Chain) error {
   271  	fullArgs := makeFullArgs(table, chain)
   272  
   273  	runner.mu.Lock()
   274  	defer runner.mu.Unlock()
   275  
   276  	out, err := runner.run(opFlushChain, fullArgs)
   277  	if err != nil {
   278  		return fmt.Errorf("error flushing chain %q: %v: %s", chain, err, out)
   279  	}
   280  	return nil
   281  }
   282  
   283  // DeleteChain is part of Interface.
   284  func (runner *runner) DeleteChain(table Table, chain Chain) error {
   285  	fullArgs := makeFullArgs(table, chain)
   286  
   287  	runner.mu.Lock()
   288  	defer runner.mu.Unlock()
   289  
   290  	out, err := runner.run(opDeleteChain, fullArgs)
   291  	if err != nil {
   292  		return fmt.Errorf("error deleting chain %q: %v: %s", chain, err, out)
   293  	}
   294  	return nil
   295  }
   296  
   297  // EnsureRule is part of Interface.
   298  func (runner *runner) EnsureRule(position RulePosition, table Table, chain Chain, args ...string) (bool, error) {
   299  	fullArgs := makeFullArgs(table, chain, args...)
   300  
   301  	runner.mu.Lock()
   302  	defer runner.mu.Unlock()
   303  
   304  	exists, err := runner.checkRule(table, chain, args...)
   305  	if err != nil {
   306  		return false, err
   307  	}
   308  	if exists {
   309  		return true, nil
   310  	}
   311  	out, err := runner.run(operation(position), fullArgs)
   312  	if err != nil {
   313  		return false, fmt.Errorf("error appending rule: %v: %s", err, out)
   314  	}
   315  	return false, nil
   316  }
   317  
   318  // DeleteRule is part of Interface.
   319  func (runner *runner) DeleteRule(table Table, chain Chain, args ...string) error {
   320  	fullArgs := makeFullArgs(table, chain, args...)
   321  
   322  	runner.mu.Lock()
   323  	defer runner.mu.Unlock()
   324  
   325  	exists, err := runner.checkRule(table, chain, args...)
   326  	if err != nil {
   327  		return err
   328  	}
   329  	if !exists {
   330  		return nil
   331  	}
   332  	out, err := runner.run(opDeleteRule, fullArgs)
   333  	if err != nil {
   334  		return fmt.Errorf("error deleting rule: %v: %s", err, out)
   335  	}
   336  	return nil
   337  }
   338  
   339  func (runner *runner) IsIPv6() bool {
   340  	return runner.protocol == ProtocolIPv6
   341  }
   342  
   343  func (runner *runner) Protocol() Protocol {
   344  	return runner.protocol
   345  }
   346  
   347  // SaveInto is part of Interface.
   348  func (runner *runner) SaveInto(table Table, buffer *bytes.Buffer) error {
   349  	runner.mu.Lock()
   350  	defer runner.mu.Unlock()
   351  
   352  	trace := utiltrace.New("iptables save")
   353  	defer trace.LogIfLong(2 * time.Second)
   354  
   355  	// run and return
   356  	iptablesSaveCmd := iptablesSaveCommand(runner.protocol)
   357  	args := []string{"-t", string(table)}
   358  	klog.V(4).InfoS("Running", "command", iptablesSaveCmd, "arguments", args)
   359  	cmd := runner.exec.Command(iptablesSaveCmd, args...)
   360  	cmd.SetStdout(buffer)
   361  	stderrBuffer := bytes.NewBuffer(nil)
   362  	cmd.SetStderr(stderrBuffer)
   363  
   364  	err := cmd.Run()
   365  	if err != nil {
   366  		stderrBuffer.WriteTo(buffer) // ignore error, since we need to return the original error
   367  	}
   368  	return err
   369  }
   370  
   371  // Restore is part of Interface.
   372  func (runner *runner) Restore(table Table, data []byte, flush FlushFlag, counters RestoreCountersFlag) error {
   373  	// setup args
   374  	args := []string{"-T", string(table)}
   375  	return runner.restoreInternal(args, data, flush, counters)
   376  }
   377  
   378  // RestoreAll is part of Interface.
   379  func (runner *runner) RestoreAll(data []byte, flush FlushFlag, counters RestoreCountersFlag) error {
   380  	// setup args
   381  	args := make([]string, 0)
   382  	return runner.restoreInternal(args, data, flush, counters)
   383  }
   384  
   385  type iptablesLocker interface {
   386  	Close() error
   387  }
   388  
   389  // restoreInternal is the shared part of Restore/RestoreAll
   390  func (runner *runner) restoreInternal(args []string, data []byte, flush FlushFlag, counters RestoreCountersFlag) error {
   391  	runner.mu.Lock()
   392  	defer runner.mu.Unlock()
   393  
   394  	trace := utiltrace.New("iptables restore")
   395  	defer trace.LogIfLong(2 * time.Second)
   396  
   397  	if !flush {
   398  		args = append(args, "--noflush")
   399  	}
   400  	if counters {
   401  		args = append(args, "--counters")
   402  	}
   403  
   404  	// Grab the iptables lock to prevent iptables-restore and iptables
   405  	// from stepping on each other.  iptables-restore 1.6.2 will have
   406  	// a --wait option like iptables itself, but that's not widely deployed.
   407  	if len(runner.restoreWaitFlag) == 0 {
   408  		locker, err := grabIptablesLocks(runner.lockfilePath14x, runner.lockfilePath16x)
   409  		if err != nil {
   410  			return err
   411  		}
   412  		trace.Step("Locks grabbed")
   413  		defer func(locker iptablesLocker) {
   414  			if err := locker.Close(); err != nil {
   415  				klog.ErrorS(err, "Failed to close iptables locks")
   416  			}
   417  		}(locker)
   418  	}
   419  
   420  	// run the command and return the output or an error including the output and error
   421  	fullArgs := append(runner.restoreWaitFlag, args...)
   422  	iptablesRestoreCmd := iptablesRestoreCommand(runner.protocol)
   423  	klog.V(4).InfoS("Running", "command", iptablesRestoreCmd, "arguments", fullArgs)
   424  	cmd := runner.exec.Command(iptablesRestoreCmd, fullArgs...)
   425  	cmd.SetStdin(bytes.NewBuffer(data))
   426  	b, err := cmd.CombinedOutput()
   427  	if err != nil {
   428  		pErr, ok := parseRestoreError(string(b))
   429  		if ok {
   430  			return pErr
   431  		}
   432  		return fmt.Errorf("%w: %s", err, b)
   433  	}
   434  	return nil
   435  }
   436  
   437  func iptablesSaveCommand(protocol Protocol) string {
   438  	if protocol == ProtocolIPv6 {
   439  		return cmdIP6TablesSave
   440  	}
   441  	return cmdIPTablesSave
   442  }
   443  
   444  func iptablesRestoreCommand(protocol Protocol) string {
   445  	if protocol == ProtocolIPv6 {
   446  		return cmdIP6TablesRestore
   447  	}
   448  	return cmdIPTablesRestore
   449  
   450  }
   451  
   452  func iptablesCommand(protocol Protocol) string {
   453  	if protocol == ProtocolIPv6 {
   454  		return cmdIP6Tables
   455  	}
   456  	return cmdIPTables
   457  }
   458  
   459  func (runner *runner) run(op operation, args []string) ([]byte, error) {
   460  	return runner.runContext(context.TODO(), op, args)
   461  }
   462  
   463  func (runner *runner) runContext(ctx context.Context, op operation, args []string) ([]byte, error) {
   464  	iptablesCmd := iptablesCommand(runner.protocol)
   465  	fullArgs := append(runner.waitFlag, string(op))
   466  	fullArgs = append(fullArgs, args...)
   467  	klog.V(5).InfoS("Running", "command", iptablesCmd, "arguments", fullArgs)
   468  	if ctx == nil {
   469  		return runner.exec.Command(iptablesCmd, fullArgs...).CombinedOutput()
   470  	}
   471  	return runner.exec.CommandContext(ctx, iptablesCmd, fullArgs...).CombinedOutput()
   472  	// Don't log err here - callers might not think it is an error.
   473  }
   474  
   475  // Returns (bool, nil) if it was able to check the existence of the rule, or
   476  // (<undefined>, error) if the process of checking failed.
   477  func (runner *runner) checkRule(table Table, chain Chain, args ...string) (bool, error) {
   478  	if runner.hasCheck {
   479  		return runner.checkRuleUsingCheck(makeFullArgs(table, chain, args...))
   480  	}
   481  	return runner.checkRuleWithoutCheck(table, chain, args...)
   482  }
   483  
   484  var hexnumRE = regexp.MustCompile("0x0+([0-9])")
   485  
   486  func trimhex(s string) string {
   487  	return hexnumRE.ReplaceAllString(s, "0x$1")
   488  }
   489  
   490  // Executes the rule check without using the "-C" flag, instead parsing iptables-save.
   491  // Present for compatibility with <1.4.11 versions of iptables.  This is full
   492  // of hack and half-measures.  We should nix this ASAP.
   493  func (runner *runner) checkRuleWithoutCheck(table Table, chain Chain, args ...string) (bool, error) {
   494  	iptablesSaveCmd := iptablesSaveCommand(runner.protocol)
   495  	klog.V(1).InfoS("Running", "command", iptablesSaveCmd, "table", string(table))
   496  	out, err := runner.exec.Command(iptablesSaveCmd, "-t", string(table)).CombinedOutput()
   497  	if err != nil {
   498  		return false, fmt.Errorf("error checking rule: %v", err)
   499  	}
   500  
   501  	// Sadly, iptables has inconsistent quoting rules for comments. Just remove all quotes.
   502  	// Also, quoted multi-word comments (which are counted as a single arg)
   503  	// will be unpacked into multiple args,
   504  	// in order to compare against iptables-save output (which will be split at whitespace boundary)
   505  	// e.g. a single arg('"this must be before the NodePort rules"') will be unquoted and unpacked into 7 args.
   506  	var argsCopy []string
   507  	for i := range args {
   508  		tmpField := strings.Trim(args[i], "\"")
   509  		tmpField = trimhex(tmpField)
   510  		argsCopy = append(argsCopy, strings.Fields(tmpField)...)
   511  	}
   512  	argset := sets.NewString(argsCopy...)
   513  
   514  	for _, line := range strings.Split(string(out), "\n") {
   515  		var fields = strings.Fields(line)
   516  
   517  		// Check that this is a rule for the correct chain, and that it has
   518  		// the correct number of argument (+2 for "-A <chain name>")
   519  		if !strings.HasPrefix(line, fmt.Sprintf("-A %s", string(chain))) || len(fields) != len(argsCopy)+2 {
   520  			continue
   521  		}
   522  
   523  		// Sadly, iptables has inconsistent quoting rules for comments.
   524  		// Just remove all quotes.
   525  		for i := range fields {
   526  			fields[i] = strings.Trim(fields[i], "\"")
   527  			fields[i] = trimhex(fields[i])
   528  		}
   529  
   530  		// TODO: This misses reorderings e.g. "-x foo ! -y bar" will match "! -x foo -y bar"
   531  		if sets.NewString(fields...).IsSuperset(argset) {
   532  			return true, nil
   533  		}
   534  		klog.V(5).InfoS("DBG: fields is not a superset of args", "fields", fields, "arguments", args)
   535  	}
   536  
   537  	return false, nil
   538  }
   539  
   540  // Executes the rule check using the "-C" flag
   541  func (runner *runner) checkRuleUsingCheck(args []string) (bool, error) {
   542  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
   543  	defer cancel()
   544  
   545  	out, err := runner.runContext(ctx, opCheckRule, args)
   546  	if ctx.Err() == context.DeadlineExceeded {
   547  		return false, fmt.Errorf("timed out while checking rules")
   548  	}
   549  	if err == nil {
   550  		return true, nil
   551  	}
   552  	if ee, ok := err.(utilexec.ExitError); ok {
   553  		// iptables uses exit(1) to indicate a failure of the operation,
   554  		// as compared to a malformed commandline, for example.
   555  		if ee.Exited() && ee.ExitStatus() == 1 {
   556  			return false, nil
   557  		}
   558  	}
   559  	return false, fmt.Errorf("error checking rule: %v: %s", err, out)
   560  }
   561  
   562  const (
   563  	// Max time we wait for an iptables flush to complete after we notice it has started
   564  	iptablesFlushTimeout = 5 * time.Second
   565  	// How often we poll while waiting for an iptables flush to complete
   566  	iptablesFlushPollTime = 100 * time.Millisecond
   567  )
   568  
   569  // Monitor is part of Interface
   570  func (runner *runner) Monitor(canary Chain, tables []Table, reloadFunc func(), interval time.Duration, stopCh <-chan struct{}) {
   571  	for {
   572  		_ = utilwait.PollImmediateUntil(interval, func() (bool, error) {
   573  			for _, table := range tables {
   574  				if _, err := runner.EnsureChain(table, canary); err != nil {
   575  					klog.ErrorS(err, "Could not set up iptables canary", "table", table, "chain", canary)
   576  					return false, nil
   577  				}
   578  			}
   579  			return true, nil
   580  		}, stopCh)
   581  
   582  		// Poll until stopCh is closed or iptables is flushed
   583  		err := utilwait.PollUntil(interval, func() (bool, error) {
   584  			if exists, err := runner.ChainExists(tables[0], canary); exists {
   585  				return false, nil
   586  			} else if isResourceError(err) {
   587  				klog.ErrorS(err, "Could not check for iptables canary", "table", tables[0], "chain", canary)
   588  				return false, nil
   589  			}
   590  			klog.V(2).InfoS("IPTables canary deleted", "table", tables[0], "chain", canary)
   591  			// Wait for the other canaries to be deleted too before returning
   592  			// so we don't start reloading too soon.
   593  			err := utilwait.PollImmediate(iptablesFlushPollTime, iptablesFlushTimeout, func() (bool, error) {
   594  				for i := 1; i < len(tables); i++ {
   595  					if exists, err := runner.ChainExists(tables[i], canary); exists || isResourceError(err) {
   596  						return false, nil
   597  					}
   598  				}
   599  				return true, nil
   600  			})
   601  			if err != nil {
   602  				klog.InfoS("Inconsistent iptables state detected")
   603  			}
   604  			return true, nil
   605  		}, stopCh)
   606  
   607  		if err != nil {
   608  			// stopCh was closed
   609  			for _, table := range tables {
   610  				_ = runner.DeleteChain(table, canary)
   611  			}
   612  			return
   613  		}
   614  
   615  		klog.V(2).InfoS("Reloading after iptables flush")
   616  		reloadFunc()
   617  	}
   618  }
   619  
   620  // ChainExists is part of Interface
   621  func (runner *runner) ChainExists(table Table, chain Chain) (bool, error) {
   622  	fullArgs := makeFullArgs(table, chain)
   623  
   624  	runner.mu.Lock()
   625  	defer runner.mu.Unlock()
   626  
   627  	trace := utiltrace.New("iptables ChainExists")
   628  	defer trace.LogIfLong(2 * time.Second)
   629  
   630  	_, err := runner.run(opListChain, fullArgs)
   631  	return err == nil, err
   632  }
   633  
   634  type operation string
   635  
   636  const (
   637  	opCreateChain operation = "-N"
   638  	opFlushChain  operation = "-F"
   639  	opDeleteChain operation = "-X"
   640  	opListChain   operation = "-S"
   641  	opCheckRule   operation = "-C"
   642  	opDeleteRule  operation = "-D"
   643  )
   644  
   645  func makeFullArgs(table Table, chain Chain, args ...string) []string {
   646  	return append([]string{string(chain), "-t", string(table)}, args...)
   647  }
   648  
   649  const iptablesVersionPattern = `v([0-9]+(\.[0-9]+)+)`
   650  
   651  // getIPTablesVersion runs "iptables --version" and parses the returned version
   652  func getIPTablesVersion(exec utilexec.Interface, protocol Protocol) (*utilversion.Version, error) {
   653  	// this doesn't access mutable state so we don't need to use the interface / runner
   654  	iptablesCmd := iptablesCommand(protocol)
   655  	bytes, err := exec.Command(iptablesCmd, "--version").CombinedOutput()
   656  	if err != nil {
   657  		return nil, err
   658  	}
   659  	versionMatcher := regexp.MustCompile(iptablesVersionPattern)
   660  	match := versionMatcher.FindStringSubmatch(string(bytes))
   661  	if match == nil {
   662  		return nil, fmt.Errorf("no iptables version found in string: %s", bytes)
   663  	}
   664  	version, err := utilversion.ParseGeneric(match[1])
   665  	if err != nil {
   666  		return nil, fmt.Errorf("iptables version %q is not a valid version string: %v", match[1], err)
   667  	}
   668  
   669  	return version, nil
   670  }
   671  
   672  // Checks if iptables version has a "wait" flag
   673  func getIPTablesWaitFlag(version *utilversion.Version) []string {
   674  	switch {
   675  	case version.AtLeast(WaitIntervalMinVersion):
   676  		return []string{WaitString, WaitSecondsValue, WaitIntervalString, WaitIntervalUsecondsValue}
   677  	case version.AtLeast(WaitSecondsMinVersion):
   678  		return []string{WaitString, WaitSecondsValue}
   679  	case version.AtLeast(WaitMinVersion):
   680  		return []string{WaitString}
   681  	default:
   682  		return nil
   683  	}
   684  }
   685  
   686  // Checks if iptables-restore has a "wait" flag
   687  func getIPTablesRestoreWaitFlag(version *utilversion.Version, exec utilexec.Interface, protocol Protocol) []string {
   688  	if version.AtLeast(WaitRestoreMinVersion) {
   689  		return []string{WaitString, WaitSecondsValue, WaitIntervalString, WaitIntervalUsecondsValue}
   690  	}
   691  
   692  	// Older versions may have backported features; if iptables-restore supports
   693  	// --version, assume it also supports --wait
   694  	vstring, err := getIPTablesRestoreVersionString(exec, protocol)
   695  	if err != nil || vstring == "" {
   696  		klog.V(3).InfoS("Couldn't get iptables-restore version; assuming it doesn't support --wait")
   697  		return nil
   698  	}
   699  	if _, err := utilversion.ParseGeneric(vstring); err != nil {
   700  		klog.V(3).InfoS("Couldn't parse iptables-restore version; assuming it doesn't support --wait")
   701  		return nil
   702  	}
   703  	return []string{WaitString}
   704  }
   705  
   706  // getIPTablesRestoreVersionString runs "iptables-restore --version" to get the version string
   707  // in the form "X.X.X"
   708  func getIPTablesRestoreVersionString(exec utilexec.Interface, protocol Protocol) (string, error) {
   709  	// this doesn't access mutable state so we don't need to use the interface / runner
   710  
   711  	// iptables-restore hasn't always had --version, and worse complains
   712  	// about unrecognized commands but doesn't exit when it gets them.
   713  	// Work around that by setting stdin to nothing so it exits immediately.
   714  	iptablesRestoreCmd := iptablesRestoreCommand(protocol)
   715  	cmd := exec.Command(iptablesRestoreCmd, "--version")
   716  	cmd.SetStdin(bytes.NewReader([]byte{}))
   717  	bytes, err := cmd.CombinedOutput()
   718  	if err != nil {
   719  		return "", err
   720  	}
   721  	versionMatcher := regexp.MustCompile(iptablesVersionPattern)
   722  	match := versionMatcher.FindStringSubmatch(string(bytes))
   723  	if match == nil {
   724  		return "", fmt.Errorf("no iptables version found in string: %s", bytes)
   725  	}
   726  	return match[1], nil
   727  }
   728  
   729  func (runner *runner) HasRandomFully() bool {
   730  	return runner.hasRandomFully
   731  }
   732  
   733  // Present tests if iptable is supported on current kernel by checking the existence
   734  // of default table and chain
   735  func (runner *runner) Present() bool {
   736  	if _, err := runner.ChainExists(TableNAT, ChainPostrouting); err != nil {
   737  		return false
   738  	}
   739  
   740  	return true
   741  }
   742  
   743  var iptablesNotFoundStrings = []string{
   744  	// iptables-legacy [-A|-I] BAD-CHAIN [...]
   745  	// iptables-legacy [-C|-D] GOOD-CHAIN [...non-matching rule...]
   746  	// iptables-legacy [-X|-F|-Z] BAD-CHAIN
   747  	// iptables-nft -X BAD-CHAIN
   748  	// NB: iptables-nft [-F|-Z] BAD-CHAIN exits with no error
   749  	"No chain/target/match by that name",
   750  
   751  	// iptables-legacy [...] -j BAD-CHAIN
   752  	// iptables-nft-1.8.0 [-A|-I] BAD-CHAIN [...]
   753  	// iptables-nft-1.8.0 [-A|-I] GOOD-CHAIN -j BAD-CHAIN
   754  	// NB: also matches some other things like "-m BAD-MODULE"
   755  	"No such file or directory",
   756  
   757  	// iptables-legacy [-C|-D] BAD-CHAIN [...]
   758  	// iptables-nft [-C|-D] GOOD-CHAIN [...non-matching rule...]
   759  	"does a matching rule exist",
   760  
   761  	// iptables-nft-1.8.2 [-A|-C|-D|-I] BAD-CHAIN [...]
   762  	// iptables-nft-1.8.2 [...] -j BAD-CHAIN
   763  	"does not exist",
   764  }
   765  
   766  // IsNotFoundError returns true if the error indicates "not found".  It parses
   767  // the error string looking for known values, which is imperfect; beware using
   768  // this function for anything beyond deciding between logging or ignoring an
   769  // error.
   770  func IsNotFoundError(err error) bool {
   771  	es := err.Error()
   772  	for _, str := range iptablesNotFoundStrings {
   773  		if strings.Contains(es, str) {
   774  			return true
   775  		}
   776  	}
   777  	return false
   778  }
   779  
   780  const iptablesStatusResourceProblem = 4
   781  
   782  // isResourceError returns true if the error indicates that iptables ran into a "resource
   783  // problem" and was unable to attempt the request. In particular, this will be true if it
   784  // times out trying to get the iptables lock.
   785  func isResourceError(err error) bool {
   786  	if ee, isExitError := err.(utilexec.ExitError); isExitError {
   787  		return ee.ExitStatus() == iptablesStatusResourceProblem
   788  	}
   789  	return false
   790  }
   791  
   792  // ParseError records the payload when iptables reports an error parsing its input.
   793  type ParseError interface {
   794  	// Line returns the line number on which the parse error was reported.
   795  	// NOTE: First line is 1.
   796  	Line() int
   797  	// Error returns the error message of the parse error, including line number.
   798  	Error() string
   799  }
   800  
   801  type parseError struct {
   802  	cmd  string
   803  	line int
   804  }
   805  
   806  func (e parseError) Line() int {
   807  	return e.line
   808  }
   809  
   810  func (e parseError) Error() string {
   811  	return fmt.Sprintf("%s: input error on line %d: ", e.cmd, e.line)
   812  }
   813  
   814  // LineData represents a single numbered line of data.
   815  type LineData struct {
   816  	// Line holds the line number (the first line is 1).
   817  	Line int
   818  	// The data of the line.
   819  	Data string
   820  }
   821  
   822  var regexpParseError = regexp.MustCompile("line ([1-9][0-9]*) failed$")
   823  
   824  // parseRestoreError extracts the line from the error, if it matches returns parseError
   825  // for example:
   826  // input: iptables-restore: line 51 failed
   827  // output: parseError:  cmd = iptables-restore, line = 51
   828  // NOTE: parseRestoreError depends on the error format of iptables, if it ever changes
   829  // we need to update this function
   830  func parseRestoreError(str string) (ParseError, bool) {
   831  	errors := strings.Split(str, ":")
   832  	if len(errors) != 2 {
   833  		return nil, false
   834  	}
   835  	cmd := errors[0]
   836  	matches := regexpParseError.FindStringSubmatch(errors[1])
   837  	if len(matches) != 2 {
   838  		return nil, false
   839  	}
   840  	line, errMsg := strconv.Atoi(matches[1])
   841  	if errMsg != nil {
   842  		return nil, false
   843  	}
   844  	return parseError{cmd: cmd, line: line}, true
   845  }
   846  
   847  // ExtractLines extracts the -count and +count data from the lineNum row of lines and return
   848  // NOTE: lines start from line 1
   849  func ExtractLines(lines []byte, line, count int) []LineData {
   850  	// first line is line 1, so line can't be smaller than 1
   851  	if line < 1 {
   852  		return nil
   853  	}
   854  	start := line - count
   855  	if start <= 0 {
   856  		start = 1
   857  	}
   858  	end := line + count + 1
   859  
   860  	offset := 1
   861  	scanner := bufio.NewScanner(bytes.NewBuffer(lines))
   862  	extractLines := make([]LineData, 0, count*2)
   863  	for scanner.Scan() {
   864  		if offset >= start && offset < end {
   865  			extractLines = append(extractLines, LineData{
   866  				Line: offset,
   867  				Data: scanner.Text(),
   868  			})
   869  		}
   870  		if offset == end {
   871  			break
   872  		}
   873  		offset++
   874  	}
   875  	return extractLines
   876  }