Explorar o código

Merge pull request #1040 from vainu-arto/masq_use_random_fully

Use --random-fully when creating MASQ rules
Tom Denham %!s(int64=6) %!d(string=hai) anos
pai
achega
22afd72c99
Modificáronse 4 ficheiros con 160 adicións e 48 borrados
  1. 1 1
      glide.lock
  2. 2 1
      glide.yaml
  3. 27 9
      network/iptables.go
  4. 130 37
      vendor/github.com/coreos/go-iptables/iptables/iptables.go

+ 1 - 1
glide.lock

@@ -46,7 +46,7 @@ imports:
   - pkg/transport
   - pkg/transport
   - pkg/types
   - pkg/types
 - name: github.com/coreos/go-iptables
 - name: github.com/coreos/go-iptables
-  version: 259c8e6a4275d497442c721fa52204d7a58bde8b
+  version: 47f22b0dd3355c0ba570ba12b0b8a36bf214c04b
   subpackages:
   subpackages:
   - iptables
   - iptables
 - name: github.com/coreos/go-systemd
 - name: github.com/coreos/go-systemd

+ 2 - 1
glide.yaml

@@ -16,7 +16,8 @@ import:
   - pkg/transport
   - pkg/transport
 - package: github.com/coreos/go-iptables
 - package: github.com/coreos/go-iptables
   subpackages:
   subpackages:
-  - iptables
+    - iptables
+  version: ~0.4.0
 - package: github.com/coreos/go-systemd
 - package: github.com/coreos/go-systemd
   subpackages:
   subpackages:
   - activation
   - activation

+ 27 - 9
network/iptables.go

@@ -43,16 +43,34 @@ type IPTablesRule struct {
 func MasqRules(ipn ip.IP4Net, lease *subnet.Lease) []IPTablesRule {
 func MasqRules(ipn ip.IP4Net, lease *subnet.Lease) []IPTablesRule {
 	n := ipn.String()
 	n := ipn.String()
 	sn := lease.Subnet.String()
 	sn := lease.Subnet.String()
+	supports_random_fully := false
+	ipt, err := iptables.New()
+	if err == nil {
+		supports_random_fully = ipt.HasRandomFully()
+	}
 
 
-	return []IPTablesRule{
-		// This rule makes sure we don't NAT traffic within overlay network (e.g. coming out of docker0)
-		{"nat", "POSTROUTING", []string{"-s", n, "-d", n, "-j", "RETURN"}},
-		// NAT if it's not multicast traffic
-		{"nat", "POSTROUTING", []string{"-s", n, "!", "-d", "224.0.0.0/4", "-j", "MASQUERADE"}},
-		// Prevent performing Masquerade on external traffic which arrives from a Node that owns the container/pod IP address
-		{"nat", "POSTROUTING", []string{"!", "-s", n, "-d", sn, "-j", "RETURN"}},
-		// Masquerade anything headed towards flannel from the host
-		{"nat", "POSTROUTING", []string{"!", "-s", n, "-d", n, "-j", "MASQUERADE"}},
+	if supports_random_fully {
+		return []IPTablesRule{
+			// This rule makes sure we don't NAT traffic within overlay network (e.g. coming out of docker0)
+			{"nat", "POSTROUTING", []string{"-s", n, "-d", n, "-j", "RETURN"}},
+			// NAT if it's not multicast traffic
+			{"nat", "POSTROUTING", []string{"-s", n, "!", "-d", "224.0.0.0/4", "-j", "MASQUERADE", "--random-fully"}},
+			// Prevent performing Masquerade on external traffic which arrives from a Node that owns the container/pod IP address
+			{"nat", "POSTROUTING", []string{"!", "-s", n, "-d", sn, "-j", "RETURN"}},
+			// Masquerade anything headed towards flannel from the host
+			{"nat", "POSTROUTING", []string{"!", "-s", n, "-d", n, "-j", "MASQUERADE", "--random-fully"}},
+		}
+	} else {
+		return []IPTablesRule{
+			// This rule makes sure we don't NAT traffic within overlay network (e.g. coming out of docker0)
+			{"nat", "POSTROUTING", []string{"-s", n, "-d", n, "-j", "RETURN"}},
+			// NAT if it's not multicast traffic
+			{"nat", "POSTROUTING", []string{"-s", n, "!", "-d", "224.0.0.0/4", "-j", "MASQUERADE"}},
+			// Prevent performing Masquerade on external traffic which arrives from a Node that owns the container/pod IP address
+			{"nat", "POSTROUTING", []string{"!", "-s", n, "-d", sn, "-j", "RETURN"}},
+			// Masquerade anything headed towards flannel from the host
+			{"nat", "POSTROUTING", []string{"!", "-s", n, "-d", n, "-j", "MASQUERADE"}},
+		}
 	}
 	}
 }
 }
 
 

+ 130 - 37
vendor/github.com/coreos/go-iptables/iptables/iptables.go

@@ -29,11 +29,15 @@ import (
 // Adds the output of stderr to exec.ExitError
 // Adds the output of stderr to exec.ExitError
 type Error struct {
 type Error struct {
 	exec.ExitError
 	exec.ExitError
-	cmd exec.Cmd
-	msg string
+	cmd        exec.Cmd
+	msg        string
+	exitStatus *int //for overriding
 }
 }
 
 
 func (e *Error) ExitStatus() int {
 func (e *Error) ExitStatus() int {
+	if e.exitStatus != nil {
+		return *e.exitStatus
+	}
 	return e.Sys().(syscall.WaitStatus).ExitStatus()
 	return e.Sys().(syscall.WaitStatus).ExitStatus()
 }
 }
 
 
@@ -41,6 +45,13 @@ func (e *Error) Error() string {
 	return fmt.Sprintf("running %v: exit status %v: %v", e.cmd.Args, e.ExitStatus(), e.msg)
 	return fmt.Sprintf("running %v: exit status %v: %v", e.cmd.Args, e.ExitStatus(), e.msg)
 }
 }
 
 
+// IsNotExist returns true if the error is due to the chain or rule not existing
+func (e *Error) IsNotExist() bool {
+	return e.ExitStatus() == 1 &&
+		(e.msg == "iptables: Bad rule (does a matching rule exist in that chain?).\n" ||
+			e.msg == "iptables: No chain/target/match by that name.\n")
+}
+
 // Protocol to differentiate between IPv4 and IPv6
 // Protocol to differentiate between IPv4 and IPv6
 type Protocol byte
 type Protocol byte
 
 
@@ -50,10 +61,15 @@ const (
 )
 )
 
 
 type IPTables struct {
 type IPTables struct {
-	path     string
-	proto    Protocol
-	hasCheck bool
-	hasWait  bool
+	path           string
+	proto          Protocol
+	hasCheck       bool
+	hasWait        bool
+	hasRandomFully bool
+	v1             int
+	v2             int
+	v3             int
+	mode           string // the underlying iptables operating mode, e.g. nf_tables
 }
 }
 
 
 // New creates a new IPTables.
 // New creates a new IPTables.
@@ -69,15 +85,21 @@ func NewWithProtocol(proto Protocol) (*IPTables, error) {
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	checkPresent, waitPresent, err := getIptablesCommandSupport(path)
-	if err != nil {
-		return nil, fmt.Errorf("error checking iptables version: %v", err)
-	}
+	vstring, err := getIptablesVersionString(path)
+	v1, v2, v3, mode, err := extractIptablesVersion(vstring)
+
+	checkPresent, waitPresent, randomFullyPresent := getIptablesCommandSupport(v1, v2, v3)
+
 	ipt := IPTables{
 	ipt := IPTables{
-		path:     path,
-		proto:    proto,
-		hasCheck: checkPresent,
-		hasWait:  waitPresent,
+		path:           path,
+		proto:          proto,
+		hasCheck:       checkPresent,
+		hasWait:        waitPresent,
+		hasRandomFully: randomFullyPresent,
+		v1:             v1,
+		v2:             v2,
+		v3:             v3,
+		mode:           mode,
 	}
 	}
 	return &ipt, nil
 	return &ipt, nil
 }
 }
@@ -248,10 +270,27 @@ func (ipt *IPTables) executeList(args []string) ([]string, error) {
 	}
 	}
 
 
 	rules := strings.Split(stdout.String(), "\n")
 	rules := strings.Split(stdout.String(), "\n")
+
+	// strip trailing newline
 	if len(rules) > 0 && rules[len(rules)-1] == "" {
 	if len(rules) > 0 && rules[len(rules)-1] == "" {
 		rules = rules[:len(rules)-1]
 		rules = rules[:len(rules)-1]
 	}
 	}
 
 
+	// nftables mode doesn't return an error code when listing a non-existent
+	// chain. Patch that up.
+	if len(rules) == 0 && ipt.mode == "nf_tables" {
+		v := 1
+		return nil, &Error{
+			cmd:        exec.Cmd{Args: args},
+			msg:        "iptables: No chain/target/match by that name.",
+			exitStatus: &v,
+		}
+	}
+
+	for i, rule := range rules {
+		rules[i] = filterRuleOutput(rule)
+	}
+
 	return rules, nil
 	return rules, nil
 }
 }
 
 
@@ -266,11 +305,18 @@ func (ipt *IPTables) NewChain(table, chain string) error {
 func (ipt *IPTables) ClearChain(table, chain string) error {
 func (ipt *IPTables) ClearChain(table, chain string) error {
 	err := ipt.NewChain(table, chain)
 	err := ipt.NewChain(table, chain)
 
 
+	// the exit code for "this table already exists" is different for
+	// different iptables modes
+	existsErr := 1
+	if ipt.mode == "nf_tables" {
+		existsErr = 4
+	}
+
 	eerr, eok := err.(*Error)
 	eerr, eok := err.(*Error)
 	switch {
 	switch {
 	case err == nil:
 	case err == nil:
 		return nil
 		return nil
-	case eok && eerr.ExitStatus() == 1:
+	case eok && eerr.ExitStatus() == existsErr:
 		// chain already exists. Flush (clear) it.
 		// chain already exists. Flush (clear) it.
 		return ipt.run("-t", table, "-F", chain)
 		return ipt.run("-t", table, "-F", chain)
 	default:
 	default:
@@ -289,6 +335,21 @@ func (ipt *IPTables) DeleteChain(table, chain string) error {
 	return ipt.run("-t", table, "-X", chain)
 	return ipt.run("-t", table, "-X", chain)
 }
 }
 
 
+// ChangePolicy changes policy on chain to target
+func (ipt *IPTables) ChangePolicy(table, chain, target string) error {
+	return ipt.run("-t", table, "-P", chain, target)
+}
+
+// Check if the underlying iptables command supports the --random-fully flag
+func (ipt *IPTables) HasRandomFully() bool {
+	return ipt.hasRandomFully
+}
+
+// Return version components of the underlying iptables command
+func (ipt *IPTables) GetIptablesVersion() (int, int, int) {
+	return ipt.v1, ipt.v2, ipt.v3
+}
+
 // run runs an iptables command with the given arguments, ignoring
 // run runs an iptables command with the given arguments, ignoring
 // any stdout output
 // any stdout output
 func (ipt *IPTables) run(args ...string) error {
 func (ipt *IPTables) run(args ...string) error {
@@ -324,7 +385,7 @@ func (ipt *IPTables) runWithOutput(args []string, stdout io.Writer) error {
 	if err := cmd.Run(); err != nil {
 	if err := cmd.Run(); err != nil {
 		switch e := err.(type) {
 		switch e := err.(type) {
 		case *exec.ExitError:
 		case *exec.ExitError:
-			return &Error{*e, cmd, stderr.String()}
+			return &Error{*e, cmd, stderr.String(), nil}
 		default:
 		default:
 			return err
 			return err
 		}
 		}
@@ -343,45 +404,40 @@ func getIptablesCommand(proto Protocol) string {
 }
 }
 
 
 // Checks if iptables has the "-C" and "--wait" flag
 // Checks if iptables has the "-C" and "--wait" flag
-func getIptablesCommandSupport(path string) (bool, bool, error) {
-	vstring, err := getIptablesVersionString(path)
-	if err != nil {
-		return false, false, err
-	}
-
-	v1, v2, v3, err := extractIptablesVersion(vstring)
-	if err != nil {
-		return false, false, err
-	}
-
-	return iptablesHasCheckCommand(v1, v2, v3), iptablesHasWaitCommand(v1, v2, v3), nil
+func getIptablesCommandSupport(v1 int, v2 int, v3 int) (bool, bool, bool) {
+	return iptablesHasCheckCommand(v1, v2, v3), iptablesHasWaitCommand(v1, v2, v3), iptablesHasRandomFully(v1, v2, v3)
 }
 }
 
 
-// getIptablesVersion returns the first three components of the iptables version.
-// e.g. "iptables v1.3.66" would return (1, 3, 66, nil)
-func extractIptablesVersion(str string) (int, int, int, error) {
-	versionMatcher := regexp.MustCompile("v([0-9]+)\\.([0-9]+)\\.([0-9]+)")
+// getIptablesVersion returns the first three components of the iptables version
+// and the operating mode (e.g. nf_tables or legacy)
+// e.g. "iptables v1.3.66" would return (1, 3, 66, legacy, nil)
+func extractIptablesVersion(str string) (int, int, int, string, error) {
+	versionMatcher := regexp.MustCompile(`v([0-9]+)\.([0-9]+)\.([0-9]+)(?:\s+\((\w+))?`)
 	result := versionMatcher.FindStringSubmatch(str)
 	result := versionMatcher.FindStringSubmatch(str)
 	if result == nil {
 	if result == nil {
-		return 0, 0, 0, fmt.Errorf("no iptables version found in string: %s", str)
+		return 0, 0, 0, "", fmt.Errorf("no iptables version found in string: %s", str)
 	}
 	}
 
 
 	v1, err := strconv.Atoi(result[1])
 	v1, err := strconv.Atoi(result[1])
 	if err != nil {
 	if err != nil {
-		return 0, 0, 0, err
+		return 0, 0, 0, "", err
 	}
 	}
 
 
 	v2, err := strconv.Atoi(result[2])
 	v2, err := strconv.Atoi(result[2])
 	if err != nil {
 	if err != nil {
-		return 0, 0, 0, err
+		return 0, 0, 0, "", err
 	}
 	}
 
 
 	v3, err := strconv.Atoi(result[3])
 	v3, err := strconv.Atoi(result[3])
 	if err != nil {
 	if err != nil {
-		return 0, 0, 0, err
+		return 0, 0, 0, "", err
 	}
 	}
 
 
-	return v1, v2, v3, nil
+	mode := "legacy"
+	if result[4] != "" {
+		mode = result[4]
+	}
+	return v1, v2, v3, mode, nil
 }
 }
 
 
 // Runs "iptables --version" to get the version string
 // Runs "iptables --version" to get the version string
@@ -424,6 +480,20 @@ func iptablesHasWaitCommand(v1 int, v2 int, v3 int) bool {
 	return false
 	return false
 }
 }
 
 
+// Checks if an iptables version is after 1.6.2, when --random-fully was added
+func iptablesHasRandomFully(v1 int, v2 int, v3 int) bool {
+	if v1 > 1 {
+		return true
+	}
+	if v1 == 1 && v2 > 6 {
+		return true
+	}
+	if v1 == 1 && v2 == 6 && v3 >= 2 {
+		return true
+	}
+	return false
+}
+
 // Checks if a rule specification exists for a table
 // Checks if a rule specification exists for a table
 func (ipt *IPTables) existsForOldIptables(table, chain string, rulespec []string) (bool, error) {
 func (ipt *IPTables) existsForOldIptables(table, chain string, rulespec []string) (bool, error) {
 	rs := strings.Join(append([]string{"-A", chain}, rulespec...), " ")
 	rs := strings.Join(append([]string{"-A", chain}, rulespec...), " ")
@@ -435,3 +505,26 @@ func (ipt *IPTables) existsForOldIptables(table, chain string, rulespec []string
 	}
 	}
 	return strings.Contains(stdout.String(), rs), nil
 	return strings.Contains(stdout.String(), rs), nil
 }
 }
+
+// counterRegex is the regex used to detect nftables counter format
+var counterRegex = regexp.MustCompile(`^\[([0-9]+):([0-9]+)\] `)
+
+// filterRuleOutput works around some inconsistencies in output.
+// For example, when iptables is in legacy vs. nftables mode, it produces
+// different results.
+func filterRuleOutput(rule string) string {
+	out := rule
+
+	// work around an output difference in nftables mode where counters
+	// are output in iptables-save format, rather than iptables -S format
+	// The string begins with "[0:0]"
+	//
+	// Fixes #49
+	if groups := counterRegex.FindStringSubmatch(out); groups != nil {
+		// drop the brackets
+		out = out[len(groups[0]):]
+		out = fmt.Sprintf("%s -c %s %s", out, groups[1], groups[2])
+	}
+
+	return out
+}