diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 6461ef5c..b9730b51 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -116,8 +116,8 @@ }, { "ImportPath": "github.com/coreos/go-iptables/iptables", - "Comment": "v0.3.0", - "Rev": "b5b1876b170881a8259f036445ee89c8669db386" + "Comment": "v0.4.1", + "Rev": "78b5fff24e6df8886ef8eca9411f683a884349a5" }, { "ImportPath": "github.com/coreos/go-systemd/activation", diff --git a/pkg/ip/ipmasq_linux.go b/pkg/ip/ipmasq_linux.go index 892667bd..cc640a60 100644 --- a/pkg/ip/ipmasq_linux.go +++ b/pkg/ip/ipmasq_linux.go @@ -22,7 +22,7 @@ import ( ) // SetupIPMasq installs iptables rules to masquerade traffic -// coming from ipn and going outside of it +// coming from ip of ipn and going outside of ipn func SetupIPMasq(ipn *net.IPNet, chain string, comment string) error { isV6 := ipn.IP.To4() == nil @@ -70,7 +70,8 @@ func SetupIPMasq(ipn *net.IPNet, chain string, comment string) error { return err } - return ipt.AppendUnique("nat", "POSTROUTING", "-s", ipn.String(), "-j", chain, "-m", "comment", "--comment", comment) + // Packets from the specific IP of this network will hit the chain + return ipt.AppendUnique("nat", "POSTROUTING", "-s", ipn.IP.String(), "-j", chain, "-m", "comment", "--comment", comment) } // TeardownIPMasq undoes the effects of SetupIPMasq @@ -89,6 +90,12 @@ func TeardownIPMasq(ipn *net.IPNet, chain string, comment string) error { return fmt.Errorf("failed to locate iptables: %v", err) } + err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.IP.String(), "-j", chain, "-m", "comment", "--comment", comment) + if err != nil && !isNotExist(err) { + return err + } + + // for downward compatibility err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.String(), "-j", chain, "-m", "comment", "--comment", comment) if err != nil && !isNotExist(err) { return err diff --git a/plugins/main/ptp/README.md b/plugins/main/ptp/README.md index 1467fb84..190476e3 100644 --- a/plugins/main/ptp/README.md +++ b/plugins/main/ptp/README.md @@ -26,7 +26,7 @@ The traffic of the container interface will be routed through the interface of t * `name` (string, required): the name of the network * `type` (string, required): "ptp" -* `ipMasq` (boolean, optional): set up IP Masquerade on the host for traffic originating from this network and destined outside of it. Defaults to false. +* `ipMasq` (boolean, optional): set up IP Masquerade on the host for traffic originating from ip of this network and destined outside of this network. Defaults to false. * `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to value chosen by the kernel. * `ipam` (dictionary, required): IPAM configuration to be used for this network. * `dns` (dictionary, optional): DNS information to return as described in the [Result](https://github.com/containernetworking/cni/blob/master/SPEC.md#result). diff --git a/vendor/github.com/coreos/go-iptables/iptables/iptables.go b/vendor/github.com/coreos/go-iptables/iptables/iptables.go index 3d523fc8..9601bc78 100644 --- a/vendor/github.com/coreos/go-iptables/iptables/iptables.go +++ b/vendor/github.com/coreos/go-iptables/iptables/iptables.go @@ -29,11 +29,16 @@ import ( // Adds the output of stderr to exec.ExitError type Error struct { exec.ExitError - cmd exec.Cmd - msg string + cmd exec.Cmd + msg string + proto Protocol + exitStatus *int //for overriding } func (e *Error) ExitStatus() int { + if e.exitStatus != nil { + return *e.exitStatus + } return e.Sys().(syscall.WaitStatus).ExitStatus() } @@ -44,8 +49,8 @@ func (e *Error) Error() string { // 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") + (e.msg == fmt.Sprintf("%s: Bad rule (does a matching rule exist in that chain?).\n", getIptablesCommand(e.proto)) || + e.msg == fmt.Sprintf("%s: No chain/target/match by that name.\n", getIptablesCommand(e.proto))) } // Protocol to differentiate between IPv4 and IPv6 @@ -57,10 +62,15 @@ const ( ) 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. @@ -76,15 +86,21 @@ func NewWithProtocol(proto Protocol) (*IPTables, error) { if err != nil { 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{ - 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 } @@ -255,10 +271,28 @@ func (ipt *IPTables) executeList(args []string) ([]string, error) { } rules := strings.Split(stdout.String(), "\n") + + // strip trailing newline if len(rules) > 0 && 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: fmt.Sprintf("%s: No chain/target/match by that name.\n", getIptablesCommand(ipt.proto)), + proto: ipt.proto, + exitStatus: &v, + } + } + + for i, rule := range rules { + rules[i] = filterRuleOutput(rule) + } + return rules, nil } @@ -273,11 +307,18 @@ func (ipt *IPTables) NewChain(table, chain string) error { func (ipt *IPTables) ClearChain(table, chain string) error { 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) switch { case err == nil: return nil - case eok && eerr.ExitStatus() == 1: + case eok && eerr.ExitStatus() == existsErr: // chain already exists. Flush (clear) it. return ipt.run("-t", table, "-F", chain) default: @@ -301,6 +342,16 @@ 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 // any stdout output func (ipt *IPTables) run(args ...string) error { @@ -336,7 +387,7 @@ func (ipt *IPTables) runWithOutput(args []string, stdout io.Writer) error { if err := cmd.Run(); err != nil { switch e := err.(type) { case *exec.ExitError: - return &Error{*e, cmd, stderr.String()} + return &Error{*e, cmd, stderr.String(), ipt.proto, nil} default: return err } @@ -355,45 +406,40 @@ func getIptablesCommand(proto Protocol) string { } // 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) 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]) if err != nil { - return 0, 0, 0, err + return 0, 0, 0, "", err } v2, err := strconv.Atoi(result[2]) if err != nil { - return 0, 0, 0, err + return 0, 0, 0, "", err } v3, err := strconv.Atoi(result[3]) 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 @@ -436,6 +482,20 @@ func iptablesHasWaitCommand(v1 int, v2 int, v3 int) bool { 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 func (ipt *IPTables) existsForOldIptables(table, chain string, rulespec []string) (bool, error) { rs := strings.Join(append([]string{"-A", chain}, rulespec...), " ") @@ -447,3 +507,26 @@ func (ipt *IPTables) existsForOldIptables(table, chain string, rulespec []string } 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 +}