github.com/docker/engine@v22.0.0-20211208180946-d456264580cf+incompatible/libnetwork/iptables/iptables.go (about)

     1  //go:build linux
     2  // +build linux
     3  
     4  package iptables
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"net"
    10  	"os/exec"
    11  	"regexp"
    12  	"strconv"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  
    17  	"github.com/sirupsen/logrus"
    18  )
    19  
    20  // Action signifies the iptable action.
    21  type Action string
    22  
    23  // Policy is the default iptable policies
    24  type Policy string
    25  
    26  // Table refers to Nat, Filter or Mangle.
    27  type Table string
    28  
    29  // IPVersion refers to IP version, v4 or v6
    30  type IPVersion string
    31  
    32  const (
    33  	// Append appends the rule at the end of the chain.
    34  	Append Action = "-A"
    35  	// Delete deletes the rule from the chain.
    36  	Delete Action = "-D"
    37  	// Insert inserts the rule at the top of the chain.
    38  	Insert Action = "-I"
    39  	// Nat table is used for nat translation rules.
    40  	Nat Table = "nat"
    41  	// Filter table is used for filter rules.
    42  	Filter Table = "filter"
    43  	// Mangle table is used for mangling the packet.
    44  	Mangle Table = "mangle"
    45  	// Drop is the default iptables DROP policy
    46  	Drop Policy = "DROP"
    47  	// Accept is the default iptables ACCEPT policy
    48  	Accept Policy = "ACCEPT"
    49  	// IPv4 is version 4
    50  	IPv4 IPVersion = "IPV4"
    51  	// IPv6 is version 6
    52  	IPv6 IPVersion = "IPV6"
    53  )
    54  
    55  var (
    56  	iptablesPath  string
    57  	ip6tablesPath string
    58  	supportsXlock = false
    59  	supportsCOpt  = false
    60  	xLockWaitMsg  = "Another app is currently holding the xtables lock"
    61  	// used to lock iptables commands if xtables lock is not supported
    62  	bestEffortLock sync.Mutex
    63  	// ErrIptablesNotFound is returned when the rule is not found.
    64  	ErrIptablesNotFound = errors.New("Iptables not found")
    65  	initOnce            sync.Once
    66  )
    67  
    68  // IPTable defines struct with IPVersion
    69  type IPTable struct {
    70  	Version IPVersion
    71  }
    72  
    73  // ChainInfo defines the iptables chain.
    74  type ChainInfo struct {
    75  	Name        string
    76  	Table       Table
    77  	HairpinMode bool
    78  	IPTable     IPTable
    79  }
    80  
    81  // ChainError is returned to represent errors during ip table operation.
    82  type ChainError struct {
    83  	Chain  string
    84  	Output []byte
    85  }
    86  
    87  func (e ChainError) Error() string {
    88  	return fmt.Sprintf("Error iptables %s: %s", e.Chain, string(e.Output))
    89  }
    90  
    91  func probe() {
    92  	path, err := exec.LookPath("iptables")
    93  	if err != nil {
    94  		logrus.Warnf("Failed to find iptables: %v", err)
    95  		return
    96  	}
    97  	if out, err := exec.Command(path, "--wait", "-t", "nat", "-L", "-n").CombinedOutput(); err != nil {
    98  		logrus.Warnf("Running iptables --wait -t nat -L -n failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err)
    99  	}
   100  	_, err = exec.LookPath("ip6tables")
   101  	if err != nil {
   102  		logrus.Warnf("Failed to find ip6tables: %v", err)
   103  		return
   104  	}
   105  }
   106  
   107  func initFirewalld() {
   108  	if err := FirewalldInit(); err != nil {
   109  		logrus.Debugf("Fail to initialize firewalld: %v, using raw iptables instead", err)
   110  	}
   111  }
   112  
   113  func detectIptables() {
   114  	path, err := exec.LookPath("iptables")
   115  	if err != nil {
   116  		return
   117  	}
   118  	iptablesPath = path
   119  	path, err = exec.LookPath("ip6tables")
   120  	if err != nil {
   121  		return
   122  	}
   123  	ip6tablesPath = path
   124  	supportsXlock = exec.Command(iptablesPath, "--wait", "-L", "-n").Run() == nil
   125  	mj, mn, mc, err := GetVersion()
   126  	if err != nil {
   127  		logrus.Warnf("Failed to read iptables version: %v", err)
   128  		return
   129  	}
   130  	supportsCOpt = supportsCOption(mj, mn, mc)
   131  }
   132  
   133  func initDependencies() {
   134  	probe()
   135  	initFirewalld()
   136  	detectIptables()
   137  }
   138  
   139  func initCheck() error {
   140  	initOnce.Do(initDependencies)
   141  
   142  	if iptablesPath == "" {
   143  		return ErrIptablesNotFound
   144  	}
   145  	return nil
   146  }
   147  
   148  // GetIptable returns an instance of IPTable with specified version
   149  func GetIptable(version IPVersion) *IPTable {
   150  	return &IPTable{Version: version}
   151  }
   152  
   153  // NewChain adds a new chain to ip table.
   154  func (iptable IPTable) NewChain(name string, table Table, hairpinMode bool) (*ChainInfo, error) {
   155  	c := &ChainInfo{
   156  		Name:        name,
   157  		Table:       table,
   158  		HairpinMode: hairpinMode,
   159  		IPTable:     iptable,
   160  	}
   161  	if string(c.Table) == "" {
   162  		c.Table = Filter
   163  	}
   164  
   165  	// Add chain if it doesn't exist
   166  	if _, err := iptable.Raw("-t", string(c.Table), "-n", "-L", c.Name); err != nil {
   167  		if output, err := iptable.Raw("-t", string(c.Table), "-N", c.Name); err != nil {
   168  			return nil, err
   169  		} else if len(output) != 0 {
   170  			return nil, fmt.Errorf("Could not create %s/%s chain: %s", c.Table, c.Name, output)
   171  		}
   172  	}
   173  	return c, nil
   174  }
   175  
   176  // LoopbackByVersion returns loopback address by version
   177  func (iptable IPTable) LoopbackByVersion() string {
   178  	if iptable.Version == IPv6 {
   179  		return "::1/128"
   180  	}
   181  	return "127.0.0.0/8"
   182  }
   183  
   184  // ProgramChain is used to add rules to a chain
   185  func (iptable IPTable) ProgramChain(c *ChainInfo, bridgeName string, hairpinMode, enable bool) error {
   186  	if c.Name == "" {
   187  		return errors.New("Could not program chain, missing chain name")
   188  	}
   189  
   190  	// Either add or remove the interface from the firewalld zone
   191  	if firewalldRunning {
   192  		if enable {
   193  			if err := AddInterfaceFirewalld(bridgeName); err != nil {
   194  				return err
   195  			}
   196  		} else {
   197  			if err := DelInterfaceFirewalld(bridgeName); err != nil {
   198  				return err
   199  			}
   200  		}
   201  	}
   202  
   203  	switch c.Table {
   204  	case Nat:
   205  		preroute := []string{
   206  			"-m", "addrtype",
   207  			"--dst-type", "LOCAL",
   208  			"-j", c.Name}
   209  		if !iptable.Exists(Nat, "PREROUTING", preroute...) && enable {
   210  			if err := c.Prerouting(Append, preroute...); err != nil {
   211  				return fmt.Errorf("Failed to inject %s in PREROUTING chain: %s", c.Name, err)
   212  			}
   213  		} else if iptable.Exists(Nat, "PREROUTING", preroute...) && !enable {
   214  			if err := c.Prerouting(Delete, preroute...); err != nil {
   215  				return fmt.Errorf("Failed to remove %s in PREROUTING chain: %s", c.Name, err)
   216  			}
   217  		}
   218  		output := []string{
   219  			"-m", "addrtype",
   220  			"--dst-type", "LOCAL",
   221  			"-j", c.Name}
   222  		if !hairpinMode {
   223  			output = append(output, "!", "--dst", iptable.LoopbackByVersion())
   224  		}
   225  		if !iptable.Exists(Nat, "OUTPUT", output...) && enable {
   226  			if err := c.Output(Append, output...); err != nil {
   227  				return fmt.Errorf("Failed to inject %s in OUTPUT chain: %s", c.Name, err)
   228  			}
   229  		} else if iptable.Exists(Nat, "OUTPUT", output...) && !enable {
   230  			if err := c.Output(Delete, output...); err != nil {
   231  				return fmt.Errorf("Failed to inject %s in OUTPUT chain: %s", c.Name, err)
   232  			}
   233  		}
   234  	case Filter:
   235  		if bridgeName == "" {
   236  			return fmt.Errorf("Could not program chain %s/%s, missing bridge name",
   237  				c.Table, c.Name)
   238  		}
   239  		link := []string{
   240  			"-o", bridgeName,
   241  			"-j", c.Name}
   242  		if !iptable.Exists(Filter, "FORWARD", link...) && enable {
   243  			insert := append([]string{string(Insert), "FORWARD"}, link...)
   244  			if output, err := iptable.Raw(insert...); err != nil {
   245  				return err
   246  			} else if len(output) != 0 {
   247  				return fmt.Errorf("Could not create linking rule to %s/%s: %s", c.Table, c.Name, output)
   248  			}
   249  		} else if iptable.Exists(Filter, "FORWARD", link...) && !enable {
   250  			del := append([]string{string(Delete), "FORWARD"}, link...)
   251  			if output, err := iptable.Raw(del...); err != nil {
   252  				return err
   253  			} else if len(output) != 0 {
   254  				return fmt.Errorf("Could not delete linking rule from %s/%s: %s", c.Table, c.Name, output)
   255  			}
   256  
   257  		}
   258  		establish := []string{
   259  			"-o", bridgeName,
   260  			"-m", "conntrack",
   261  			"--ctstate", "RELATED,ESTABLISHED",
   262  			"-j", "ACCEPT"}
   263  		if !iptable.Exists(Filter, "FORWARD", establish...) && enable {
   264  			insert := append([]string{string(Insert), "FORWARD"}, establish...)
   265  			if output, err := iptable.Raw(insert...); err != nil {
   266  				return err
   267  			} else if len(output) != 0 {
   268  				return fmt.Errorf("Could not create establish rule to %s: %s", c.Table, output)
   269  			}
   270  		} else if iptable.Exists(Filter, "FORWARD", establish...) && !enable {
   271  			del := append([]string{string(Delete), "FORWARD"}, establish...)
   272  			if output, err := iptable.Raw(del...); err != nil {
   273  				return err
   274  			} else if len(output) != 0 {
   275  				return fmt.Errorf("Could not delete establish rule from %s: %s", c.Table, output)
   276  			}
   277  		}
   278  	}
   279  	return nil
   280  }
   281  
   282  // RemoveExistingChain removes existing chain from the table.
   283  func (iptable IPTable) RemoveExistingChain(name string, table Table) error {
   284  	c := &ChainInfo{
   285  		Name:    name,
   286  		Table:   table,
   287  		IPTable: iptable,
   288  	}
   289  	if string(c.Table) == "" {
   290  		c.Table = Filter
   291  	}
   292  	return c.Remove()
   293  }
   294  
   295  // Forward adds forwarding rule to 'filter' table and corresponding nat rule to 'nat' table.
   296  func (c *ChainInfo) Forward(action Action, ip net.IP, port int, proto, destAddr string, destPort int, bridgeName string) error {
   297  
   298  	iptable := GetIptable(c.IPTable.Version)
   299  	daddr := ip.String()
   300  	if ip.IsUnspecified() {
   301  		// iptables interprets "0.0.0.0" as "0.0.0.0/32", whereas we
   302  		// want "0.0.0.0/0". "0/0" is correctly interpreted as "any
   303  		// value" by both iptables and ip6tables.
   304  		daddr = "0/0"
   305  	}
   306  
   307  	args := []string{
   308  		"-p", proto,
   309  		"-d", daddr,
   310  		"--dport", strconv.Itoa(port),
   311  		"-j", "DNAT",
   312  		"--to-destination", net.JoinHostPort(destAddr, strconv.Itoa(destPort))}
   313  
   314  	if !c.HairpinMode {
   315  		args = append(args, "!", "-i", bridgeName)
   316  	}
   317  	if err := iptable.ProgramRule(Nat, c.Name, action, args); err != nil {
   318  		return err
   319  	}
   320  
   321  	args = []string{
   322  		"!", "-i", bridgeName,
   323  		"-o", bridgeName,
   324  		"-p", proto,
   325  		"-d", destAddr,
   326  		"--dport", strconv.Itoa(destPort),
   327  		"-j", "ACCEPT",
   328  	}
   329  	if err := iptable.ProgramRule(Filter, c.Name, action, args); err != nil {
   330  		return err
   331  	}
   332  
   333  	args = []string{
   334  		"-p", proto,
   335  		"-s", destAddr,
   336  		"-d", destAddr,
   337  		"--dport", strconv.Itoa(destPort),
   338  		"-j", "MASQUERADE",
   339  	}
   340  
   341  	if err := iptable.ProgramRule(Nat, "POSTROUTING", action, args); err != nil {
   342  		return err
   343  	}
   344  
   345  	if proto == "sctp" {
   346  		// Linux kernel v4.9 and below enables NETIF_F_SCTP_CRC for veth by
   347  		// the following commit.
   348  		// This introduces a problem when conbined with a physical NIC without
   349  		// NETIF_F_SCTP_CRC. As for a workaround, here we add an iptables entry
   350  		// to fill the checksum.
   351  		//
   352  		// https://github.com/torvalds/linux/commit/c80fafbbb59ef9924962f83aac85531039395b18
   353  		args = []string{
   354  			"-p", proto,
   355  			"--sport", strconv.Itoa(destPort),
   356  			"-j", "CHECKSUM",
   357  			"--checksum-fill",
   358  		}
   359  		if err := iptable.ProgramRule(Mangle, "POSTROUTING", action, args); err != nil {
   360  			return err
   361  		}
   362  	}
   363  
   364  	return nil
   365  }
   366  
   367  // Link adds reciprocal ACCEPT rule for two supplied IP addresses.
   368  // Traffic is allowed from ip1 to ip2 and vice-versa
   369  func (c *ChainInfo) Link(action Action, ip1, ip2 net.IP, port int, proto string, bridgeName string) error {
   370  	iptable := GetIptable(c.IPTable.Version)
   371  	// forward
   372  	args := []string{
   373  		"-i", bridgeName, "-o", bridgeName,
   374  		"-p", proto,
   375  		"-s", ip1.String(),
   376  		"-d", ip2.String(),
   377  		"--dport", strconv.Itoa(port),
   378  		"-j", "ACCEPT",
   379  	}
   380  
   381  	if err := iptable.ProgramRule(Filter, c.Name, action, args); err != nil {
   382  		return err
   383  	}
   384  	// reverse
   385  	args[7], args[9] = args[9], args[7]
   386  	args[10] = "--sport"
   387  	return iptable.ProgramRule(Filter, c.Name, action, args)
   388  }
   389  
   390  // ProgramRule adds the rule specified by args only if the
   391  // rule is not already present in the chain. Reciprocally,
   392  // it removes the rule only if present.
   393  func (iptable IPTable) ProgramRule(table Table, chain string, action Action, args []string) error {
   394  	if iptable.Exists(table, chain, args...) != (action == Delete) {
   395  		return nil
   396  	}
   397  	return iptable.RawCombinedOutput(append([]string{"-t", string(table), string(action), chain}, args...)...)
   398  }
   399  
   400  // Prerouting adds linking rule to nat/PREROUTING chain.
   401  func (c *ChainInfo) Prerouting(action Action, args ...string) error {
   402  	iptable := GetIptable(c.IPTable.Version)
   403  	a := []string{"-t", string(Nat), string(action), "PREROUTING"}
   404  	if len(args) > 0 {
   405  		a = append(a, args...)
   406  	}
   407  	if output, err := iptable.Raw(a...); err != nil {
   408  		return err
   409  	} else if len(output) != 0 {
   410  		return ChainError{Chain: "PREROUTING", Output: output}
   411  	}
   412  	return nil
   413  }
   414  
   415  // Output adds linking rule to an OUTPUT chain.
   416  func (c *ChainInfo) Output(action Action, args ...string) error {
   417  	iptable := GetIptable(c.IPTable.Version)
   418  	a := []string{"-t", string(c.Table), string(action), "OUTPUT"}
   419  	if len(args) > 0 {
   420  		a = append(a, args...)
   421  	}
   422  	if output, err := iptable.Raw(a...); err != nil {
   423  		return err
   424  	} else if len(output) != 0 {
   425  		return ChainError{Chain: "OUTPUT", Output: output}
   426  	}
   427  	return nil
   428  }
   429  
   430  // Remove removes the chain.
   431  func (c *ChainInfo) Remove() error {
   432  	iptable := GetIptable(c.IPTable.Version)
   433  	// Ignore errors - This could mean the chains were never set up
   434  	if c.Table == Nat {
   435  		c.Prerouting(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "-j", c.Name)
   436  		c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", iptable.LoopbackByVersion(), "-j", c.Name)
   437  		c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "-j", c.Name) // Created in versions <= 0.1.6
   438  
   439  		c.Prerouting(Delete)
   440  		c.Output(Delete)
   441  	}
   442  	iptable.Raw("-t", string(c.Table), "-F", c.Name)
   443  	iptable.Raw("-t", string(c.Table), "-X", c.Name)
   444  	return nil
   445  }
   446  
   447  // Exists checks if a rule exists
   448  func (iptable IPTable) Exists(table Table, chain string, rule ...string) bool {
   449  	return iptable.exists(false, table, chain, rule...)
   450  }
   451  
   452  // ExistsNative behaves as Exists with the difference it
   453  // will always invoke `iptables` binary.
   454  func (iptable IPTable) ExistsNative(table Table, chain string, rule ...string) bool {
   455  	return iptable.exists(true, table, chain, rule...)
   456  }
   457  
   458  func (iptable IPTable) exists(native bool, table Table, chain string, rule ...string) bool {
   459  	f := iptable.Raw
   460  	if native {
   461  		f = iptable.raw
   462  	}
   463  
   464  	if string(table) == "" {
   465  		table = Filter
   466  	}
   467  
   468  	if err := initCheck(); err != nil {
   469  		// The exists() signature does not allow us to return an error, but at least
   470  		// we can skip the (likely invalid) exec invocation.
   471  		return false
   472  	}
   473  
   474  	if supportsCOpt {
   475  		// if exit status is 0 then return true, the rule exists
   476  		_, err := f(append([]string{"-t", string(table), "-C", chain}, rule...)...)
   477  		return err == nil
   478  	}
   479  
   480  	// parse "iptables -S" for the rule (it checks rules in a specific chain
   481  	// in a specific table and it is very unreliable)
   482  	return iptable.existsRaw(table, chain, rule...)
   483  }
   484  
   485  func (iptable IPTable) existsRaw(table Table, chain string, rule ...string) bool {
   486  	path := iptablesPath
   487  	if iptable.Version == IPv6 {
   488  		path = ip6tablesPath
   489  	}
   490  	ruleString := fmt.Sprintf("%s %s\n", chain, strings.Join(rule, " "))
   491  	existingRules, _ := exec.Command(path, "-t", string(table), "-S", chain).Output()
   492  
   493  	return strings.Contains(string(existingRules), ruleString)
   494  }
   495  
   496  // Maximum duration that an iptables operation can take
   497  // before flagging a warning.
   498  const opWarnTime = 2 * time.Second
   499  
   500  func filterOutput(start time.Time, output []byte, args ...string) []byte {
   501  	// Flag operations that have taken a long time to complete
   502  	opTime := time.Since(start)
   503  	if opTime > opWarnTime {
   504  		logrus.Warnf("xtables contention detected while running [%s]: Waited for %.2f seconds and received %q", strings.Join(args, " "), float64(opTime)/float64(time.Second), string(output))
   505  	}
   506  	// ignore iptables' message about xtables lock:
   507  	// it is a warning, not an error.
   508  	if strings.Contains(string(output), xLockWaitMsg) {
   509  		output = []byte("")
   510  	}
   511  	// Put further filters here if desired
   512  	return output
   513  }
   514  
   515  // Raw calls 'iptables' system command, passing supplied arguments.
   516  func (iptable IPTable) Raw(args ...string) ([]byte, error) {
   517  	if firewalldRunning {
   518  		// select correct IP version for firewalld
   519  		ipv := Iptables
   520  		if iptable.Version == IPv6 {
   521  			ipv = IP6Tables
   522  		}
   523  
   524  		startTime := time.Now()
   525  		output, err := Passthrough(ipv, args...)
   526  		if err == nil || !strings.Contains(err.Error(), "was not provided by any .service files") {
   527  			return filterOutput(startTime, output, args...), err
   528  		}
   529  	}
   530  	return iptable.raw(args...)
   531  }
   532  
   533  func (iptable IPTable) raw(args ...string) ([]byte, error) {
   534  	if err := initCheck(); err != nil {
   535  		return nil, err
   536  	}
   537  	if supportsXlock {
   538  		args = append([]string{"--wait"}, args...)
   539  	} else {
   540  		bestEffortLock.Lock()
   541  		defer bestEffortLock.Unlock()
   542  	}
   543  
   544  	path := iptablesPath
   545  	commandName := "iptables"
   546  	if iptable.Version == IPv6 {
   547  		path = ip6tablesPath
   548  		commandName = "ip6tables"
   549  	}
   550  
   551  	logrus.Debugf("%s, %v", path, args)
   552  
   553  	startTime := time.Now()
   554  	output, err := exec.Command(path, args...).CombinedOutput()
   555  	if err != nil {
   556  		return nil, fmt.Errorf("iptables failed: %s %v: %s (%s)", commandName, strings.Join(args, " "), output, err)
   557  	}
   558  
   559  	return filterOutput(startTime, output, args...), err
   560  }
   561  
   562  // RawCombinedOutput internally calls the Raw function and returns a non nil
   563  // error if Raw returned a non nil error or a non empty output
   564  func (iptable IPTable) RawCombinedOutput(args ...string) error {
   565  	if output, err := iptable.Raw(args...); err != nil || len(output) != 0 {
   566  		return fmt.Errorf("%s (%v)", string(output), err)
   567  	}
   568  	return nil
   569  }
   570  
   571  // RawCombinedOutputNative behave as RawCombinedOutput with the difference it
   572  // will always invoke `iptables` binary
   573  func (iptable IPTable) RawCombinedOutputNative(args ...string) error {
   574  	if output, err := iptable.raw(args...); err != nil || len(output) != 0 {
   575  		return fmt.Errorf("%s (%v)", string(output), err)
   576  	}
   577  	return nil
   578  }
   579  
   580  // ExistChain checks if a chain exists
   581  func (iptable IPTable) ExistChain(chain string, table Table) bool {
   582  	if _, err := iptable.Raw("-t", string(table), "-nL", chain); err == nil {
   583  		return true
   584  	}
   585  	return false
   586  }
   587  
   588  // GetVersion reads the iptables version numbers during initialization
   589  func GetVersion() (major, minor, micro int, err error) {
   590  	out, err := exec.Command(iptablesPath, "--version").CombinedOutput()
   591  	if err == nil {
   592  		major, minor, micro = parseVersionNumbers(string(out))
   593  	}
   594  	return
   595  }
   596  
   597  // SetDefaultPolicy sets the passed default policy for the table/chain
   598  func (iptable IPTable) SetDefaultPolicy(table Table, chain string, policy Policy) error {
   599  	if err := iptable.RawCombinedOutput("-t", string(table), "-P", chain, string(policy)); err != nil {
   600  		return fmt.Errorf("setting default policy to %v in %v chain failed: %v", policy, chain, err)
   601  	}
   602  	return nil
   603  }
   604  
   605  func parseVersionNumbers(input string) (major, minor, micro int) {
   606  	re := regexp.MustCompile(`v\d*.\d*.\d*`)
   607  	line := re.FindString(input)
   608  	fmt.Sscanf(line, "v%d.%d.%d", &major, &minor, &micro)
   609  	return
   610  }
   611  
   612  // iptables -C, --check option was added in v.1.4.11
   613  // http://ftp.netfilter.org/pub/iptables/changes-iptables-1.4.11.txt
   614  func supportsCOption(mj, mn, mc int) bool {
   615  	return mj > 1 || (mj == 1 && (mn > 4 || (mn == 4 && mc >= 11)))
   616  }
   617  
   618  // AddReturnRule adds a return rule for the chain in the filter table
   619  func (iptable IPTable) AddReturnRule(chain string) error {
   620  	var (
   621  		table = Filter
   622  		args  = []string{"-j", "RETURN"}
   623  	)
   624  
   625  	if iptable.Exists(table, chain, args...) {
   626  		return nil
   627  	}
   628  
   629  	err := iptable.RawCombinedOutput(append([]string{"-A", chain}, args...)...)
   630  	if err != nil {
   631  		return fmt.Errorf("unable to add return rule in %s chain: %s", chain, err.Error())
   632  	}
   633  
   634  	return nil
   635  }
   636  
   637  // EnsureJumpRule ensures the jump rule is on top
   638  func (iptable IPTable) EnsureJumpRule(fromChain, toChain string) error {
   639  	var (
   640  		table = Filter
   641  		args  = []string{"-j", toChain}
   642  	)
   643  
   644  	if iptable.Exists(table, fromChain, args...) {
   645  		err := iptable.RawCombinedOutput(append([]string{"-D", fromChain}, args...)...)
   646  		if err != nil {
   647  			return fmt.Errorf("unable to remove jump to %s rule in %s chain: %s", toChain, fromChain, err.Error())
   648  		}
   649  	}
   650  
   651  	err := iptable.RawCombinedOutput(append([]string{"-I", fromChain}, args...)...)
   652  	if err != nil {
   653  		return fmt.Errorf("unable to insert jump to %s rule in %s chain: %s", toChain, fromChain, err.Error())
   654  	}
   655  
   656  	return nil
   657  }