github.com/jfrazelle/docker@v1.1.2-0.20210712172922-bf78e25fe508/libnetwork/iptables/iptables.go (about)

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