diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index d1de7671..df42da8d 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -7,7 +7,7 @@ "Deps": [ { "ImportPath": "github.com/coreos/go-iptables/iptables", - "Rev": "83dfad0f13fd7310fb3c1cb8563248d8d604b95b" + "Rev": "90456be57fcb8185b264b77ce42a9539df42df25" }, { "ImportPath": "github.com/coreos/go-systemd/activation", diff --git a/Godeps/_workspace/src/github.com/coreos/go-iptables/iptables/iptables.go b/Godeps/_workspace/src/github.com/coreos/go-iptables/iptables/iptables.go index 36a8ec7c..ea9fedf0 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-iptables/iptables/iptables.go +++ b/Godeps/_workspace/src/github.com/coreos/go-iptables/iptables/iptables.go @@ -17,6 +17,7 @@ package iptables import ( "bytes" "fmt" + "io" "log" "os/exec" "regexp" @@ -40,7 +41,11 @@ func (e *Error) Error() string { } type IPTables struct { - path string + path string + hasCheck bool + hasWait bool + + fmu *fileLock } func New() (*IPTables, error) { @@ -48,33 +53,41 @@ func New() (*IPTables, error) { if err != nil { return nil, err } - - return &IPTables{path}, nil + checkPresent, waitPresent, err := getIptablesCommandSupport() + if err != nil { + log.Printf("Error checking iptables version, assuming version at least 1.4.20: %v", err) + checkPresent = true + waitPresent = true + } + ipt := IPTables{ + path: path, + hasCheck: checkPresent, + hasWait: waitPresent, + } + if !waitPresent { + ipt.fmu, err = newXtablesFileLock() + if err != nil { + return nil, err + } + } + return &ipt, nil } // Exists checks if given rulespec in specified table/chain exists -func (ipt *IPTables) Exists(table, chain string, rulespec...string) (bool, error) { - checkPresent, err := getIptablesHasCheckCommand() - if err != nil { - log.Printf("Error checking iptables version, assuming version at least 1.4.11: %v", err) - checkPresent = true +func (ipt *IPTables) Exists(table, chain string, rulespec ...string) (bool, error) { + if !ipt.hasCheck { + return ipt.existsForOldIptables(table, chain, rulespec) + } - - if !checkPresent { - cmd := append([]string{"-A", chain}, rulespec...) - return existsForOldIpTables(table, strings.Join(cmd, " ")) - } else { - cmd := append([]string{"-t", table, "-C", chain}, rulespec...) - err := ipt.run(cmd...) - - switch { - case err == nil: - return true, nil - case err.(*Error).ExitStatus() == 1: - return false, nil - default: - return false, err - } + cmd := append([]string{"-t", table, "-C", chain}, rulespec...) + err := ipt.run(cmd...) + switch { + case err == nil: + return true, nil + case err.(*Error).ExitStatus() == 1: + return false, nil + default: + return false, err } } @@ -112,16 +125,10 @@ func (ipt *IPTables) Delete(table, chain string, rulespec ...string) error { // List rules in specified table/chain func (ipt *IPTables) List(table, chain string) ([]string, error) { - var stdout, stderr bytes.Buffer - cmd := exec.Cmd{ - Path: ipt.path, - Args: []string{ipt.path, "-t", table, "-S", chain}, - Stdout: &stdout, - Stderr: &stderr, - } - - if err := cmd.Run(); err != nil { - return nil, &Error{*(err.(*exec.ExitError)), stderr.String()} + args := []string{"-t", table, "-S", chain} + var stdout bytes.Buffer + if err := ipt.runWithOutput(args, &stdout); err != nil { + return nil, err } rules := strings.Split(stdout.String(), "\n") @@ -136,8 +143,8 @@ func (ipt *IPTables) NewChain(table, chain string) error { return ipt.run("-t", table, "-N", chain) } -// ClearChain flushed (deletes all rules) in the specifed table/chain. -// If the chain does not exist, new one will be created +// ClearChain flushed (deletes all rules) in the specified table/chain. +// If the chain does not exist, a new one will be created func (ipt *IPTables) ClearChain(table, chain string) error { err := ipt.NewChain(table, chain) @@ -152,17 +159,42 @@ func (ipt *IPTables) ClearChain(table, chain string) error { } } +// RenameChain renames the old chain to the new one. +func (ipt *IPTables) RenameChain(table, oldChain, newChain string) error { + return ipt.run("-t", table, "-E", oldChain, newChain) +} + // DeleteChain deletes the chain in the specified table. // The chain must be empty func (ipt *IPTables) DeleteChain(table, chain string) error { return ipt.run("-t", table, "-X", chain) } -func (ipt *IPTables) run(args... string) error { +// run runs an iptables command with the given arguments, ignoring +// any stdout output +func (ipt *IPTables) run(args ...string) error { + return ipt.runWithOutput(args, nil) +} + +// runWithOutput runs an iptables command with the given arguments, +// writing any stdout output to the given writer +func (ipt *IPTables) runWithOutput(args []string, stdout io.Writer) error { + args = append([]string{ipt.path}, args...) + if ipt.hasWait { + args = append(args, "--wait") + } else { + ul, err := ipt.fmu.tryLock() + if err != nil { + return err + } + defer ul.Unlock() + } + var stderr bytes.Buffer cmd := exec.Cmd{ - Path: ipt.path, - Args: append([]string{ipt.path}, args...), + Path: ipt.path, + Args: args, + Stdout: stdout, Stderr: &stderr, } @@ -173,19 +205,19 @@ func (ipt *IPTables) run(args... string) error { return nil } -// Checks if iptables has the "-C" flag -func getIptablesHasCheckCommand() (bool, error) { +// Checks if iptables has the "-C" and "--wait" flag +func getIptablesCommandSupport() (bool, bool, error) { vstring, err := getIptablesVersionString() if err != nil { - return false, err + return false, false, err } v1, v2, v3, err := extractIptablesVersion(vstring) if err != nil { - return false, err + return false, false, err } - return iptablesHasCheckCommand(v1, v2, v3), nil + return iptablesHasCheckCommand(v1, v2, v3), iptablesHasWaitCommand(v1, v2, v3), nil } // getIptablesVersion returns the first three components of the iptables version. @@ -241,15 +273,28 @@ func iptablesHasCheckCommand(v1 int, v2 int, v3 int) bool { return false } +// Checks if an iptables version is after 1.4.20, when --wait was added +func iptablesHasWaitCommand(v1 int, v2 int, v3 int) bool { + if v1 > 1 { + return true + } + if v1 == 1 && v2 > 4 { + return true + } + if v1 == 1 && v2 == 4 && v3 >= 20 { + return true + } + return false +} + // Checks if a rule specification exists for a table -func existsForOldIpTables(table string, ruleSpec string) (bool, error) { - cmd := exec.Command("iptables", "-t", table, "-S") - var out bytes.Buffer - cmd.Stdout = &out - err := cmd.Run() +func (ipt *IPTables) existsForOldIptables(table, chain string, rulespec []string) (bool, error) { + rs := strings.Join(append([]string{"-A", chain}, rulespec...), " ") + args := []string{"-t", table, "-S"} + var stdout bytes.Buffer + err := ipt.runWithOutput(args, &stdout) if err != nil { return false, err } - rules := out.String() - return strings.Contains(rules, ruleSpec), nil + return strings.Contains(stdout.String(), rs), nil } diff --git a/Godeps/_workspace/src/github.com/coreos/go-iptables/iptables/iptables_test.go b/Godeps/_workspace/src/github.com/coreos/go-iptables/iptables/iptables_test.go index e4f3b171..66ce4bbf 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-iptables/iptables/iptables_test.go +++ b/Godeps/_workspace/src/github.com/coreos/go-iptables/iptables/iptables_test.go @@ -67,8 +67,15 @@ func TestChain(t *testing.T) { t.Fatalf("ClearChain (of non-empty) failed: %v", err) } + // rename the chain + newChain := randChain(t) + err = ipt.RenameChain("filter", chain, newChain) + if err != nil { + t.Fatalf("RenameChain failed: %v", err) + } + // chain empty, should be ok - err = ipt.DeleteChain("filter", chain) + err = ipt.DeleteChain("filter", newChain) if err != nil { t.Fatalf("DeleteChain of empty chain failed: %v", err) } @@ -115,7 +122,7 @@ func TestRules(t *testing.T) { err = ipt.Delete("filter", chain, "-s", "10.1.0.0/16", "-d", "9.9.9.9/32", "-j", "ACCEPT") if err != nil { - t.Fatalf("Insert failed: %v", err) + t.Fatalf("Delete failed: %v", err) } rules, err := ipt.List("filter", chain) @@ -133,4 +140,16 @@ func TestRules(t *testing.T) { if !reflect.DeepEqual(rules, expected) { t.Fatalf("List mismatch: \ngot %#v \nneed %#v", rules, expected) } + + // Clear the chain that was created. + err = ipt.ClearChain("filter", chain) + if err != nil { + t.Fatalf("Failed to clear test chain: %v", err) + } + + // Delete the chain that was created + err = ipt.DeleteChain("filter", chain) + if err != nil { + t.Fatalf("Failed to delete test chain: %v", err) + } } diff --git a/Godeps/_workspace/src/github.com/coreos/go-iptables/iptables/lock.go b/Godeps/_workspace/src/github.com/coreos/go-iptables/iptables/lock.go new file mode 100644 index 00000000..a88e92b4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-iptables/iptables/lock.go @@ -0,0 +1,84 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package iptables + +import ( + "os" + "sync" + "syscall" +) + +const ( + // In earlier versions of iptables, the xtables lock was implemented + // via a Unix socket, but now flock is used via this lockfile: + // http://git.netfilter.org/iptables/commit/?id=aa562a660d1555b13cffbac1e744033e91f82707 + // Note the LSB-conforming "/run" directory does not exist on old + // distributions, so assume "/var" is symlinked + xtablesLockFilePath = "/var/run/xtables.lock" + + defaultFilePerm = 0600 +) + +type Unlocker interface { + Unlock() error +} + +type nopUnlocker struct{} + +func (_ nopUnlocker) Unlock() error { return nil } + +type fileLock struct { + // mu is used to protect against concurrent invocations from within this process + mu sync.Mutex + fd int +} + +// tryLock takes an exclusive lock on the xtables lock file without blocking. +// This is best-effort only: if the exclusive lock would block (i.e. because +// another process already holds it), no error is returned. Otherwise, any +// error encountered during the locking operation is returned. +// The returned Unlocker should be used to release the lock when the caller is +// done invoking iptables commands. +func (l *fileLock) tryLock() (Unlocker, error) { + l.mu.Lock() + err := syscall.Flock(l.fd, syscall.LOCK_EX|syscall.LOCK_NB) + switch err { + case syscall.EWOULDBLOCK: + l.mu.Unlock() + return nopUnlocker{}, nil + case nil: + return l, nil + default: + l.mu.Unlock() + return nil, err + } +} + +// Unlock closes the underlying file, which implicitly unlocks it as well. It +// also unlocks the associated mutex. +func (l *fileLock) Unlock() error { + defer l.mu.Unlock() + return syscall.Close(l.fd) +} + +// newXtablesFileLock opens a new lock on the xtables lockfile without +// acquiring the lock +func newXtablesFileLock() (*fileLock, error) { + fd, err := syscall.Open(xtablesLockFilePath, os.O_CREATE, defaultFilePerm) + if err != nil { + return nil, err + } + return &fileLock{fd: fd}, nil +}