...

Source file src/k8s.io/kubernetes/pkg/util/iptables/iptables.go

Documentation: k8s.io/kubernetes/pkg/util/iptables

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

View as plain text