github.com/zhuohuang-hust/src-cbuild@v0.0.0-20230105071821-c7aab3e7c840/mergeCode/libnetwork/iptables/iptables.go (about)

     1  package iptables
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net"
     7  	"os/exec"
     8  	"regexp"
     9  	"strconv"
    10  	"strings"
    11  	"sync"
    12  
    13  	"github.com/Sirupsen/logrus"
    14  )
    15  
    16  // Action signifies the iptable action.
    17  type Action string
    18  
    19  // Table refers to Nat, Filter or Mangle.
    20  type Table string
    21  
    22  const (
    23  	// Append appends the rule at the end of the chain.
    24  	Append Action = "-A"
    25  	// Delete deletes the rule from the chain.
    26  	Delete Action = "-D"
    27  	// Insert inserts the rule at the top of the chain.
    28  	Insert Action = "-I"
    29  	// Nat table is used for nat translation rules.
    30  	Nat Table = "nat"
    31  	// Filter table is used for filter rules.
    32  	Filter Table = "filter"
    33  	// Mangle table is used for mangling the packet.
    34  	Mangle Table = "mangle"
    35  )
    36  
    37  var (
    38  	iptablesPath  string
    39  	supportsXlock = false
    40  	supportsCOpt  = false
    41  	// used to lock iptables commands if xtables lock is not supported
    42  	bestEffortLock sync.Mutex
    43  	// ErrIptablesNotFound is returned when the rule is not found.
    44  	ErrIptablesNotFound = errors.New("Iptables not found")
    45  	probeOnce           sync.Once
    46  	firewalldOnce       sync.Once
    47  )
    48  
    49  // ChainInfo defines the iptables chain.
    50  type ChainInfo struct {
    51  	Name        string
    52  	Table       Table
    53  	HairpinMode bool
    54  }
    55  
    56  // ChainError is returned to represent errors during ip table operation.
    57  type ChainError struct {
    58  	Chain  string
    59  	Output []byte
    60  }
    61  
    62  func (e ChainError) Error() string {
    63  	return fmt.Sprintf("Error iptables %s: %s", e.Chain, string(e.Output))
    64  }
    65  
    66  func probe() {
    67  	if out, err := exec.Command("modprobe", "-va", "nf_nat").CombinedOutput(); err != nil {
    68  		logrus.Warnf("Running modprobe nf_nat failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err)
    69  	}
    70  	if out, err := exec.Command("modprobe", "-va", "xt_conntrack").CombinedOutput(); err != nil {
    71  		logrus.Warnf("Running modprobe xt_conntrack failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err)
    72  	}
    73  }
    74  
    75  func initFirewalld() {
    76  	if err := FirewalldInit(); err != nil {
    77  		logrus.Debugf("Fail to initialize firewalld: %v, using raw iptables instead", err)
    78  	}
    79  }
    80  
    81  func initCheck() error {
    82  	if iptablesPath == "" {
    83  		probeOnce.Do(probe)
    84  		firewalldOnce.Do(initFirewalld)
    85  		path, err := exec.LookPath("iptables")
    86  		if err != nil {
    87  			return ErrIptablesNotFound
    88  		}
    89  		iptablesPath = path
    90  		supportsXlock = exec.Command(iptablesPath, "--wait", "-L", "-n").Run() == nil
    91  		mj, mn, mc, err := GetVersion()
    92  		if err != nil {
    93  			logrus.Warnf("Failed to read iptables version: %v", err)
    94  			return nil
    95  		}
    96  		supportsCOpt = supportsCOption(mj, mn, mc)
    97  	}
    98  	return nil
    99  }
   100  
   101  // NewChain adds a new chain to ip table.
   102  func NewChain(name string, table Table, hairpinMode bool) (*ChainInfo, error) {
   103  	c := &ChainInfo{
   104  		Name:        name,
   105  		Table:       table,
   106  		HairpinMode: hairpinMode,
   107  	}
   108  	if string(c.Table) == "" {
   109  		c.Table = Filter
   110  	}
   111  
   112  	// Add chain if it doesn't exist
   113  	if _, err := Raw("-t", string(c.Table), "-n", "-L", c.Name); err != nil {
   114  		if output, err := Raw("-t", string(c.Table), "-N", c.Name); err != nil {
   115  			return nil, err
   116  		} else if len(output) != 0 {
   117  			return nil, fmt.Errorf("Could not create %s/%s chain: %s", c.Table, c.Name, output)
   118  		}
   119  	}
   120  	return c, nil
   121  }
   122  
   123  // ProgramChain is used to add rules to a chain
   124  func ProgramChain(c *ChainInfo, bridgeName string, hairpinMode, enable bool) error {
   125  	if c.Name == "" {
   126  		return fmt.Errorf("Could not program chain, missing chain name.")
   127  	}
   128  
   129  	switch c.Table {
   130  	case Nat:
   131  		preroute := []string{
   132  			"-m", "addrtype",
   133  			"--dst-type", "LOCAL",
   134  			"-j", c.Name}
   135  		if !Exists(Nat, "PREROUTING", preroute...) && enable {
   136  			if err := c.Prerouting(Append, preroute...); err != nil {
   137  				return fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
   138  			}
   139  		} else if Exists(Nat, "PREROUTING", preroute...) && !enable {
   140  			if err := c.Prerouting(Delete, preroute...); err != nil {
   141  				return fmt.Errorf("Failed to remove docker in PREROUTING chain: %s", err)
   142  			}
   143  		}
   144  		output := []string{
   145  			"-m", "addrtype",
   146  			"--dst-type", "LOCAL",
   147  			"-j", c.Name}
   148  		if !hairpinMode {
   149  			output = append(output, "!", "--dst", "127.0.0.0/8")
   150  		}
   151  		if !Exists(Nat, "OUTPUT", output...) && enable {
   152  			if err := c.Output(Append, output...); err != nil {
   153  				return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
   154  			}
   155  		} else if Exists(Nat, "OUTPUT", output...) && !enable {
   156  			if err := c.Output(Delete, output...); err != nil {
   157  				return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
   158  			}
   159  		}
   160  	case Filter:
   161  		if bridgeName == "" {
   162  			return fmt.Errorf("Could not program chain %s/%s, missing bridge name.",
   163  				c.Table, c.Name)
   164  		}
   165  		link := []string{
   166  			"-o", bridgeName,
   167  			"-j", c.Name}
   168  		if !Exists(Filter, "FORWARD", link...) && enable {
   169  			insert := append([]string{string(Insert), "FORWARD"}, link...)
   170  			if output, err := Raw(insert...); err != nil {
   171  				return err
   172  			} else if len(output) != 0 {
   173  				return fmt.Errorf("Could not create linking rule to %s/%s: %s", c.Table, c.Name, output)
   174  			}
   175  		} else if Exists(Filter, "FORWARD", link...) && !enable {
   176  			del := append([]string{string(Delete), "FORWARD"}, link...)
   177  			if output, err := Raw(del...); err != nil {
   178  				return err
   179  			} else if len(output) != 0 {
   180  				return fmt.Errorf("Could not delete linking rule from %s/%s: %s", c.Table, c.Name, output)
   181  			}
   182  
   183  		}
   184  	}
   185  	return nil
   186  }
   187  
   188  // RemoveExistingChain removes existing chain from the table.
   189  func RemoveExistingChain(name string, table Table) error {
   190  	c := &ChainInfo{
   191  		Name:  name,
   192  		Table: table,
   193  	}
   194  	if string(c.Table) == "" {
   195  		c.Table = Filter
   196  	}
   197  	return c.Remove()
   198  }
   199  
   200  // Forward adds forwarding rule to 'filter' table and corresponding nat rule to 'nat' table.
   201  func (c *ChainInfo) Forward(action Action, ip net.IP, port int, proto, destAddr string, destPort int, bridgeName string) error {
   202  	daddr := ip.String()
   203  	if ip.IsUnspecified() {
   204  		// iptables interprets "0.0.0.0" as "0.0.0.0/32", whereas we
   205  		// want "0.0.0.0/0". "0/0" is correctly interpreted as "any
   206  		// value" by both iptables and ip6tables.
   207  		daddr = "0/0"
   208  	}
   209  
   210  	args := []string{
   211  		"-p", proto,
   212  		"-d", daddr,
   213  		"--dport", strconv.Itoa(port),
   214  		"-j", "DNAT",
   215  		"--to-destination", net.JoinHostPort(destAddr, strconv.Itoa(destPort))}
   216  	if !c.HairpinMode {
   217  		args = append(args, "!", "-i", bridgeName)
   218  	}
   219  	if err := ProgramRule(Nat, c.Name, action, args); err != nil {
   220  		return err
   221  	}
   222  
   223  	args = []string{
   224  		"!", "-i", bridgeName,
   225  		"-o", bridgeName,
   226  		"-p", proto,
   227  		"-d", destAddr,
   228  		"--dport", strconv.Itoa(destPort),
   229  		"-j", "ACCEPT",
   230  	}
   231  	if err := ProgramRule(Filter, c.Name, action, args); err != nil {
   232  		return err
   233  	}
   234  
   235  	args = []string{
   236  		"-p", proto,
   237  		"-s", destAddr,
   238  		"-d", destAddr,
   239  		"--dport", strconv.Itoa(destPort),
   240  		"-j", "MASQUERADE",
   241  	}
   242  	if err := ProgramRule(Nat, "POSTROUTING", action, args); err != nil {
   243  		return err
   244  	}
   245  
   246  	return nil
   247  }
   248  
   249  // Link adds reciprocal ACCEPT rule for two supplied IP addresses.
   250  // Traffic is allowed from ip1 to ip2 and vice-versa
   251  func (c *ChainInfo) Link(action Action, ip1, ip2 net.IP, port int, proto string, bridgeName string) error {
   252  	// forward
   253  	args := []string{
   254  		"-i", bridgeName, "-o", bridgeName,
   255  		"-p", proto,
   256  		"-s", ip1.String(),
   257  		"-d", ip2.String(),
   258  		"--dport", strconv.Itoa(port),
   259  		"-j", "ACCEPT",
   260  	}
   261  	if err := ProgramRule(Filter, c.Name, action, args); err != nil {
   262  		return err
   263  	}
   264  	// reverse
   265  	args[7], args[9] = args[9], args[7]
   266  	args[10] = "--sport"
   267  	if err := ProgramRule(Filter, c.Name, action, args); err != nil {
   268  		return err
   269  	}
   270  	return nil
   271  }
   272  
   273  // ProgramRule adds the rule specified by args only if the
   274  // rule is not already present in the chain. Reciprocally,
   275  // it removes the rule only if present.
   276  func ProgramRule(table Table, chain string, action Action, args []string) error {
   277  	if Exists(table, chain, args...) != (action == Delete) {
   278  		return nil
   279  	}
   280  	return RawCombinedOutput(append([]string{"-t", string(table), string(action), chain}, args...)...)
   281  }
   282  
   283  // Prerouting adds linking rule to nat/PREROUTING chain.
   284  func (c *ChainInfo) Prerouting(action Action, args ...string) error {
   285  	a := []string{"-t", string(Nat), string(action), "PREROUTING"}
   286  	if len(args) > 0 {
   287  		a = append(a, args...)
   288  	}
   289  	if output, err := Raw(a...); err != nil {
   290  		return err
   291  	} else if len(output) != 0 {
   292  		return ChainError{Chain: "PREROUTING", Output: output}
   293  	}
   294  	return nil
   295  }
   296  
   297  // Output adds linking rule to an OUTPUT chain.
   298  func (c *ChainInfo) Output(action Action, args ...string) error {
   299  	a := []string{"-t", string(c.Table), string(action), "OUTPUT"}
   300  	if len(args) > 0 {
   301  		a = append(a, args...)
   302  	}
   303  	if output, err := Raw(a...); err != nil {
   304  		return err
   305  	} else if len(output) != 0 {
   306  		return ChainError{Chain: "OUTPUT", Output: output}
   307  	}
   308  	return nil
   309  }
   310  
   311  // Remove removes the chain.
   312  func (c *ChainInfo) Remove() error {
   313  	// Ignore errors - This could mean the chains were never set up
   314  	if c.Table == Nat {
   315  		c.Prerouting(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "-j", c.Name)
   316  		c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", c.Name)
   317  		c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "-j", c.Name) // Created in versions <= 0.1.6
   318  
   319  		c.Prerouting(Delete)
   320  		c.Output(Delete)
   321  	}
   322  	Raw("-t", string(c.Table), "-F", c.Name)
   323  	Raw("-t", string(c.Table), "-X", c.Name)
   324  	return nil
   325  }
   326  
   327  // Exists checks if a rule exists
   328  func Exists(table Table, chain string, rule ...string) bool {
   329  	if string(table) == "" {
   330  		table = Filter
   331  	}
   332  
   333  	initCheck()
   334  
   335  	if supportsCOpt {
   336  		// if exit status is 0 then return true, the rule exists
   337  		_, err := Raw(append([]string{"-t", string(table), "-C", chain}, rule...)...)
   338  		return err == nil
   339  	}
   340  
   341  	// parse "iptables -S" for the rule (it checks rules in a specific chain
   342  	// in a specific table and it is very unreliable)
   343  	return existsRaw(table, chain, rule...)
   344  }
   345  
   346  func existsRaw(table Table, chain string, rule ...string) bool {
   347  	ruleString := fmt.Sprintf("%s %s\n", chain, strings.Join(rule, " "))
   348  	existingRules, _ := exec.Command(iptablesPath, "-t", string(table), "-S", chain).Output()
   349  
   350  	return strings.Contains(string(existingRules), ruleString)
   351  }
   352  
   353  // Raw calls 'iptables' system command, passing supplied arguments.
   354  func Raw(args ...string) ([]byte, error) {
   355  	if firewalldRunning {
   356  		output, err := Passthrough(Iptables, args...)
   357  		if err == nil || !strings.Contains(err.Error(), "was not provided by any .service files") {
   358  			return output, err
   359  		}
   360  	}
   361  	return raw(args...)
   362  }
   363  
   364  func raw(args ...string) ([]byte, error) {
   365  	if err := initCheck(); err != nil {
   366  		return nil, err
   367  	}
   368  	if supportsXlock {
   369  		args = append([]string{"--wait"}, args...)
   370  	} else {
   371  		bestEffortLock.Lock()
   372  		defer bestEffortLock.Unlock()
   373  	}
   374  
   375  	logrus.Debugf("%s, %v", iptablesPath, args)
   376  
   377  	output, err := exec.Command(iptablesPath, args...).CombinedOutput()
   378  	if err != nil {
   379  		return nil, fmt.Errorf("iptables failed: iptables %v: %s (%s)", strings.Join(args, " "), output, err)
   380  	}
   381  
   382  	// ignore iptables' message about xtables lock
   383  	if strings.Contains(string(output), "waiting for it to exit") {
   384  		output = []byte("")
   385  	}
   386  
   387  	return output, err
   388  }
   389  
   390  // RawCombinedOutput inernally calls the Raw function and returns a non nil
   391  // error if Raw returned a non nil error or a non empty output
   392  func RawCombinedOutput(args ...string) error {
   393  	if output, err := Raw(args...); err != nil || len(output) != 0 {
   394  		return fmt.Errorf("%s (%v)", string(output), err)
   395  	}
   396  	return nil
   397  }
   398  
   399  // RawCombinedOutputNative behave as RawCombinedOutput with the difference it
   400  // will always invoke `iptables` binary
   401  func RawCombinedOutputNative(args ...string) error {
   402  	if output, err := raw(args...); err != nil || len(output) != 0 {
   403  		return fmt.Errorf("%s (%v)", string(output), err)
   404  	}
   405  	return nil
   406  }
   407  
   408  // ExistChain checks if a chain exists
   409  func ExistChain(chain string, table Table) bool {
   410  	if _, err := Raw("-t", string(table), "-L", chain); err == nil {
   411  		return true
   412  	}
   413  	return false
   414  }
   415  
   416  // GetVersion reads the iptables version numbers
   417  func GetVersion() (major, minor, micro int, err error) {
   418  	out, err := Raw("--version")
   419  	if err == nil {
   420  		major, minor, micro = parseVersionNumbers(string(out))
   421  	}
   422  	return
   423  }
   424  
   425  func parseVersionNumbers(input string) (major, minor, micro int) {
   426  	re := regexp.MustCompile(`v\d*.\d*.\d*`)
   427  	line := re.FindString(input)
   428  	fmt.Sscanf(line, "v%d.%d.%d", &major, &minor, &micro)
   429  	return
   430  }
   431  
   432  // iptables -C, --check option was added in v.1.4.11
   433  // http://ftp.netfilter.org/pub/iptables/changes-iptables-1.4.11.txt
   434  func supportsCOption(mj, mn, mc int) bool {
   435  	return mj > 1 || (mj == 1 && (mn > 4 || (mn == 4 && mc >= 11)))
   436  }