Merge pull request #421 from aojea/portmapErrors2

Portmap doesn't fail if chain doesn't exist
This commit is contained in:
Bruce Ma 2019-12-19 00:16:58 +08:00 committed by GitHub
commit ec8f6c99d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 255 additions and 128 deletions

2
go.mod
View File

@ -8,7 +8,7 @@ require (
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44 github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44
github.com/containernetworking/cni v0.7.1 github.com/containernetworking/cni v0.7.1
github.com/coreos/go-iptables v0.4.2 github.com/coreos/go-iptables v0.4.5
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c
github.com/d2g/dhcp4client v1.0.0 github.com/d2g/dhcp4client v1.0.0

4
go.sum
View File

@ -10,6 +10,10 @@ github.com/containernetworking/cni v0.7.1 h1:fE3r16wpSEyaqY4Z4oFrLMmIGfBYIKpPrHK
github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
github.com/coreos/go-iptables v0.4.2 h1:KH0EwId05JwWIfb96gWvkiT2cbuOu8ygqUaB+yPAwIg= github.com/coreos/go-iptables v0.4.2 h1:KH0EwId05JwWIfb96gWvkiT2cbuOu8ygqUaB+yPAwIg=
github.com/coreos/go-iptables v0.4.2/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-iptables v0.4.2/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/coreos/go-iptables v0.4.4 h1:5oOUvU7Fk53Hn/rkdJ0zcYGCffotqXpyi4ADCkO1TJ8=
github.com/coreos/go-iptables v0.4.4/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/coreos/go-iptables v0.4.5 h1:DpHb9vJrZQEFMcVLFKAAGMUVX0XoRC0ptCthinRYm38=
github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c h1:Xo2rK1pzOm0jO6abTPIQwbAmqBIOj132otexc1mmzFc= github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c h1:Xo2rK1pzOm0jO6abTPIQwbAmqBIOj132otexc1mmzFc=

View File

@ -62,3 +62,60 @@ func ChainExists(ipt *iptables.IPTables, table, chain string) (bool, error) {
} }
return false, nil return false, nil
} }
// DeleteRule idempotently delete the iptables rule in the specified table/chain.
// It does not return an error if the referring chain doesn't exist
func DeleteRule(ipt *iptables.IPTables, table, chain string, rulespec ...string) error {
if ipt == nil {
return errors.New("failed to ensure iptable chain: IPTables was nil")
}
if err := ipt.Delete(table, chain, rulespec...); err != nil {
eerr, eok := err.(*iptables.Error)
switch {
case eok && eerr.IsNotExist():
// swallow here, the chain was already deleted
return nil
case eok && eerr.ExitStatus() == 2:
// swallow here, invalid command line parameter because the referring rule is missing
return nil
default:
return fmt.Errorf("Failed to delete referring rule %s %s: %v", table, chain, err)
}
}
return nil
}
// DeleteChain idempotently deletes the specified table/chain.
// It does not return an errors if the chain does not exist
func DeleteChain(ipt *iptables.IPTables, table, chain string) error {
if ipt == nil {
return errors.New("failed to ensure iptable chain: IPTables was nil")
}
err := ipt.DeleteChain(table, chain)
eerr, eok := err.(*iptables.Error)
switch {
case eok && eerr.IsNotExist():
// swallow here, the chain was already deleted
return nil
default:
return err
}
}
// ClearChain idempotently clear the iptables rules in the specified table/chain.
// If the chain does not exist, a new one will be created
func ClearChain(ipt *iptables.IPTables, table, chain string) error {
if ipt == nil {
return errors.New("failed to ensure iptable chain: IPTables was nil")
}
err := ipt.ClearChain(table, chain)
eerr, eok := err.(*iptables.Error)
switch {
case eok && eerr.IsNotExist():
// swallow here, the chain was already deleted
return EnsureChain(ipt, table, chain)
default:
return err
}
}

View File

@ -67,6 +67,7 @@ var _ = Describe("chain tests", func() {
cleanup() cleanup()
}) })
Describe("EnsureChain", func() {
It("creates chains idempotently", func() { It("creates chains idempotently", func() {
err := EnsureChain(ipt, TABLE, testChain) err := EnsureChain(ipt, TABLE, testChain)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
@ -76,3 +77,21 @@ var _ = Describe("chain tests", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
}) })
}) })
Describe("DeleteChain", func() {
It("delete chains idempotently", func() {
// Create chain
err := EnsureChain(ipt, TABLE, testChain)
Expect(err).NotTo(HaveOccurred())
// Delete chain
err = DeleteChain(ipt, TABLE, testChain)
Expect(err).NotTo(HaveOccurred())
// Delete it again!
err = DeleteChain(ipt, TABLE, testChain)
Expect(err).NotTo(HaveOccurred())
})
})
})

View File

@ -70,7 +70,7 @@ func (c *chain) teardown(ipt *iptables.IPTables) error {
// flush the chain // flush the chain
// This will succeed *and create the chain* if it does not exist. // This will succeed *and create the chain* if it does not exist.
// If the chain doesn't exist, the next checks will fail. // If the chain doesn't exist, the next checks will fail.
if err := ipt.ClearChain(c.table, c.name); err != nil { if err := utils.ClearChain(ipt, c.table, c.name); err != nil {
return err return err
} }
@ -90,17 +90,15 @@ func (c *chain) teardown(ipt *iptables.IPTables) error {
} }
chainParts = chainParts[2:] // List results always include an -A CHAINNAME chainParts = chainParts[2:] // List results always include an -A CHAINNAME
if err := ipt.Delete(c.table, entryChain, chainParts...); err != nil { if err := utils.DeleteRule(ipt, c.table, entryChain, chainParts...); err != nil {
return fmt.Errorf("Failed to delete referring rule %s %s: %v", c.table, entryChainRule, err) return err
} }
} }
} }
} }
if err := ipt.DeleteChain(c.table, c.name); err != nil { return utils.DeleteChain(ipt, c.table, c.name)
return err
}
return nil
} }
// insertUnique will add a rule to a chain if it does not already exist. // insertUnique will add a rule to a chain if it does not already exist.

View File

@ -18,6 +18,7 @@ import (
"fmt" "fmt"
"math/rand" "math/rand"
"runtime" "runtime"
"sync"
"github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils" "github.com/containernetworking/plugins/pkg/testutils"
@ -32,6 +33,7 @@ const TABLE = "filter" // We'll monkey around here
var _ = Describe("chain tests", func() { var _ = Describe("chain tests", func() {
var testChain chain var testChain chain
var ipt *iptables.IPTables var ipt *iptables.IPTables
var testNs ns.NetNS
var cleanup func() var cleanup func()
BeforeEach(func() { BeforeEach(func() {
@ -41,7 +43,7 @@ var _ = Describe("chain tests", func() {
currNs, err := ns.GetCurrentNS() currNs, err := ns.GetCurrentNS()
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
testNs, err := testutils.NewNS() testNs, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
tlChainName := fmt.Sprintf("cni-test-%d", rand.Intn(10000000)) tlChainName := fmt.Sprintf("cni-test-%d", rand.Intn(10000000))
@ -195,4 +197,38 @@ var _ = Describe("chain tests", func() {
} }
} }
}) })
It("deletes chains idempotently in parallel", func() {
defer cleanup()
// number of parallel executions
N := 10
var wg sync.WaitGroup
err := testChain.setup(ipt)
Expect(err).NotTo(HaveOccurred())
errCh := make(chan error, N)
for i := 0; i < N; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// teardown chain
errCh <- testNs.Do(func(ns.NetNS) error {
return testChain.teardown(ipt)
})
}()
}
wg.Wait()
close(errCh)
for err := range errCh {
Expect(err).NotTo(HaveOccurred())
}
chains, err := ipt.ListChains(TABLE)
Expect(err).NotTo(HaveOccurred())
for _, chain := range chains {
if chain == testChain.name {
Fail("Chain was not deleted")
}
}
})
}) })

View File

@ -96,6 +96,7 @@ var _ = Describe("portmap integration tests", func() {
} }
}) })
Describe("Creating an interface in a namespace with the ptp plugin", func() {
// This needs to be done using Ginkgo's asynchronous testing mode. // This needs to be done using Ginkgo's asynchronous testing mode.
It("forwards a TCP port on ipv4", func(done Done) { It("forwards a TCP port on ipv4", func(done Done) {
var err error var err error
@ -171,6 +172,18 @@ var _ = Describe("portmap integration tests", func() {
cmd.Stdout = GinkgoWriter cmd.Stdout = GinkgoWriter
Expect(cmd.Run()).To(Succeed()) Expect(cmd.Run()).To(Succeed())
// dump ip routes output for debugging
cmd = exec.Command("ip", "route")
cmd.Stderr = GinkgoWriter
cmd.Stdout = GinkgoWriter
Expect(cmd.Run()).To(Succeed())
// dump ip addresses output for debugging
cmd = exec.Command("ip", "addr")
cmd.Stderr = GinkgoWriter
cmd.Stdout = GinkgoWriter
Expect(cmd.Run()).To(Succeed())
// Sanity check: verify that the container is reachable directly // Sanity check: verify that the container is reachable directly
contOK := testEchoServer(contIP.String(), containerPort, "") contOK := testEchoServer(contIP.String(), containerPort, "")
@ -210,6 +223,7 @@ var _ = Describe("portmap integration tests", func() {
}, TIMEOUT*9) }, TIMEOUT*9)
}) })
})
// testEchoServer returns true if we found an echo server on the port // testEchoServer returns true if we found an echo server on the port
func testEchoServer(address string, port int, netns string) bool { func testEchoServer(address string, port int, netns string) bool {

View File

@ -48,9 +48,13 @@ func (e *Error) Error() string {
// IsNotExist returns true if the error is due to the chain or rule not existing // IsNotExist returns true if the error is due to the chain or rule not existing
func (e *Error) IsNotExist() bool { func (e *Error) IsNotExist() bool {
return e.ExitStatus() == 1 && if e.ExitStatus() != 1 {
(e.msg == fmt.Sprintf("%s: Bad rule (does a matching rule exist in that chain?).\n", getIptablesCommand(e.proto)) || return false
e.msg == fmt.Sprintf("%s: No chain/target/match by that name.\n", getIptablesCommand(e.proto))) }
cmdIptables := getIptablesCommand(e.proto)
msgNoRuleExist := fmt.Sprintf("%s: Bad rule (does a matching rule exist in that chain?).\n", cmdIptables)
msgNoChainExist := fmt.Sprintf("%s: No chain/target/match by that name.\n", cmdIptables)
return strings.Contains(e.msg, msgNoRuleExist) || strings.Contains(e.msg, msgNoChainExist)
} }
// Protocol to differentiate between IPv4 and IPv6 // Protocol to differentiate between IPv4 and IPv6
@ -101,7 +105,13 @@ func NewWithProtocol(proto Protocol) (*IPTables, error) {
return nil, err return nil, err
} }
vstring, err := getIptablesVersionString(path) vstring, err := getIptablesVersionString(path)
if err != nil {
return nil, fmt.Errorf("could not get iptables version: %v", err)
}
v1, v2, v3, mode, err := extractIptablesVersion(vstring) v1, v2, v3, mode, err := extractIptablesVersion(vstring)
if err != nil {
return nil, fmt.Errorf("failed to extract iptables version from [%s]: %v", vstring, err)
}
checkPresent, waitPresent, randomFullyPresent := getIptablesCommandSupport(v1, v2, v3) checkPresent, waitPresent, randomFullyPresent := getIptablesCommandSupport(v1, v2, v3)
@ -348,18 +358,6 @@ func (ipt *IPTables) executeList(args []string) ([]string, error) {
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: fmt.Sprintf("%s: No chain/target/match by that name.\n", getIptablesCommand(ipt.proto)),
proto: ipt.proto,
exitStatus: &v,
}
}
for i, rule := range rules { for i, rule := range rules {
rules[i] = filterRuleOutput(rule) rules[i] = filterRuleOutput(rule)
} }
@ -437,6 +435,7 @@ func (ipt *IPTables) runWithOutput(args []string, stdout io.Writer) error {
} }
ul, err := fmu.tryLock() ul, err := fmu.tryLock()
if err != nil { if err != nil {
syscall.Close(fmu.fd)
return err return err
} }
defer ul.Unlock() defer ul.Unlock()

2
vendor/modules.txt vendored
View File

@ -32,7 +32,7 @@ github.com/containernetworking/cni/pkg/skel
github.com/containernetworking/cni/pkg/version github.com/containernetworking/cni/pkg/version
github.com/containernetworking/cni/pkg/types/020 github.com/containernetworking/cni/pkg/types/020
github.com/containernetworking/cni/libcni github.com/containernetworking/cni/libcni
# github.com/coreos/go-iptables v0.4.2 # github.com/coreos/go-iptables v0.4.5
github.com/coreos/go-iptables/iptables github.com/coreos/go-iptables/iptables
# github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 # github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7
github.com/coreos/go-systemd/activation github.com/coreos/go-systemd/activation