Merge pull request #408 from tgross/idempotent_chain_creation
ensure iptables chain creation is idempotent
This commit is contained in:
commit
497560f35f
64
pkg/utils/iptables.go
Normal file
64
pkg/utils/iptables.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// Copyright 2017 CNI authors
|
||||||
|
//
|
||||||
|
// 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 utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/coreos/go-iptables/iptables"
|
||||||
|
)
|
||||||
|
|
||||||
|
const statusChainExists = 1
|
||||||
|
|
||||||
|
// EnsureChain idempotently creates the iptables chain. It does not
|
||||||
|
// return an error if the chain already exists.
|
||||||
|
func EnsureChain(ipt *iptables.IPTables, table, chain string) error {
|
||||||
|
if ipt == nil {
|
||||||
|
return errors.New("failed to ensure iptable chain: IPTables was nil")
|
||||||
|
}
|
||||||
|
exists, err := ChainExists(ipt, table, chain)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to list iptables chains: %v", err)
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
err = ipt.NewChain(table, chain)
|
||||||
|
if err != nil {
|
||||||
|
eerr, eok := err.(*iptables.Error)
|
||||||
|
if eok && eerr.ExitStatus() != statusChainExists {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainExists checks whether an iptables chain exists.
|
||||||
|
func ChainExists(ipt *iptables.IPTables, table, chain string) (bool, error) {
|
||||||
|
if ipt == nil {
|
||||||
|
return false, errors.New("failed to check iptable chain: IPTables was nil")
|
||||||
|
}
|
||||||
|
chains, err := ipt.ListChains(table)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ch := range chains {
|
||||||
|
if ch == chain {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
78
pkg/utils/iptables_test.go
Normal file
78
pkg/utils/iptables_test.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// Copyright 2017-2018 CNI authors
|
||||||
|
//
|
||||||
|
// 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 utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/containernetworking/plugins/pkg/ns"
|
||||||
|
"github.com/containernetworking/plugins/pkg/testutils"
|
||||||
|
"github.com/coreos/go-iptables/iptables"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TABLE = "filter" // We'll monkey around here
|
||||||
|
|
||||||
|
var _ = Describe("chain tests", func() {
|
||||||
|
var testChain string
|
||||||
|
var ipt *iptables.IPTables
|
||||||
|
var cleanup func()
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
|
||||||
|
// Save a reference to the original namespace,
|
||||||
|
// Add a new NS
|
||||||
|
currNs, err := ns.GetCurrentNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
testNs, err := testutils.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
testChain = fmt.Sprintf("cni-test-%d", rand.Intn(10000000))
|
||||||
|
|
||||||
|
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
runtime.LockOSThread()
|
||||||
|
err = testNs.Set()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
cleanup = func() {
|
||||||
|
if ipt == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ipt.ClearChain(TABLE, testChain)
|
||||||
|
ipt.DeleteChain(TABLE, testChain)
|
||||||
|
currNs.Set()
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
cleanup()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("creates chains idempotently", func() {
|
||||||
|
err := EnsureChain(ipt, TABLE, testChain)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Create it again!
|
||||||
|
err = EnsureChain(ipt, TABLE, testChain)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
@ -270,6 +270,13 @@ var _ = Describe("firewall plugin iptables backend", func() {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
validateFullRuleset(fullConf)
|
validateFullRuleset(fullConf)
|
||||||
|
|
||||||
|
// ensure creation is idempotent
|
||||||
|
_, _, err = testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error {
|
||||||
|
return cmdAdd(args)
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/containernetworking/cni/pkg/types/current"
|
"github.com/containernetworking/cni/pkg/types/current"
|
||||||
|
"github.com/containernetworking/plugins/pkg/utils"
|
||||||
"github.com/coreos/go-iptables/iptables"
|
"github.com/coreos/go-iptables/iptables"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,20 +33,6 @@ func getPrivChainRules(ip string) [][]string {
|
|||||||
return rules
|
return rules
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureChain(ipt *iptables.IPTables, table, chain string) error {
|
|
||||||
chains, err := ipt.ListChains(table)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to list iptables chains: %v", err)
|
|
||||||
}
|
|
||||||
for _, ch := range chains {
|
|
||||||
if ch == chain {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ipt.NewChain(table, chain)
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateFilterRule(privChainName string) []string {
|
func generateFilterRule(privChainName string) []string {
|
||||||
return []string{"-m", "comment", "--comment", "CNI firewall plugin rules", "-j", privChainName}
|
return []string{"-m", "comment", "--comment", "CNI firewall plugin rules", "-j", privChainName}
|
||||||
}
|
}
|
||||||
@ -73,10 +60,10 @@ func (ib *iptablesBackend) setupChains(ipt *iptables.IPTables) error {
|
|||||||
adminRule := generateFilterRule(ib.adminChainName)
|
adminRule := generateFilterRule(ib.adminChainName)
|
||||||
|
|
||||||
// Ensure our private chains exist
|
// Ensure our private chains exist
|
||||||
if err := ensureChain(ipt, "filter", ib.privChainName); err != nil {
|
if err := utils.EnsureChain(ipt, "filter", ib.privChainName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := ensureChain(ipt, "filter", ib.adminChainName); err != nil {
|
if err := utils.EnsureChain(ipt, "filter", ib.adminChainName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,10 +147,10 @@ func (ib *iptablesBackend) checkRules(conf *FirewallNetConf, result *current.Res
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure our private chains exist
|
// Ensure our private chains exist
|
||||||
if err := ensureChain(ipt, "filter", ib.privChainName); err != nil {
|
if err := utils.EnsureChain(ipt, "filter", ib.privChainName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := ensureChain(ipt, "filter", ib.adminChainName); err != nil {
|
if err := utils.EnsureChain(ipt, "filter", ib.adminChainName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containernetworking/plugins/pkg/utils"
|
||||||
"github.com/coreos/go-iptables/iptables"
|
"github.com/coreos/go-iptables/iptables"
|
||||||
"github.com/mattn/go-shellwords"
|
"github.com/mattn/go-shellwords"
|
||||||
)
|
)
|
||||||
@ -35,16 +36,11 @@ type chain struct {
|
|||||||
|
|
||||||
// setup idempotently creates the chain. It will not error if the chain exists.
|
// setup idempotently creates the chain. It will not error if the chain exists.
|
||||||
func (c *chain) setup(ipt *iptables.IPTables) error {
|
func (c *chain) setup(ipt *iptables.IPTables) error {
|
||||||
// create the chain
|
|
||||||
exists, err := chainExists(ipt, c.table, c.name)
|
err := utils.EnsureChain(ipt, c.table, c.name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !exists {
|
|
||||||
if err := ipt.NewChain(c.table, c.name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the rules to the chain
|
// Add the rules to the chain
|
||||||
for _, rule := range c.rules {
|
for _, rule := range c.rules {
|
||||||
@ -125,24 +121,10 @@ func insertUnique(ipt *iptables.IPTables, table, chain string, prepend bool, rul
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func chainExists(ipt *iptables.IPTables, tableName, chainName string) (bool, error) {
|
|
||||||
chains, err := ipt.ListChains(tableName)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ch := range chains {
|
|
||||||
if ch == chainName {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// check the chain.
|
// check the chain.
|
||||||
func (c *chain) check(ipt *iptables.IPTables) error {
|
func (c *chain) check(ipt *iptables.IPTables) error {
|
||||||
|
|
||||||
exists, err := chainExists(ipt, c.table, c.name)
|
exists, err := utils.ChainExists(ipt, c.table, c.name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -124,7 +124,7 @@ func checkPorts(config *PortMapConf, containerIP net.IP) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ip4t != nil {
|
if ip4t != nil {
|
||||||
exists, err := chainExists(ip4t, dnatChain.table, dnatChain.name)
|
exists, err := utils.ChainExists(ip4t, dnatChain.table, dnatChain.name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -137,7 +137,7 @@ func checkPorts(config *PortMapConf, containerIP net.IP) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ip6t != nil {
|
if ip6t != nil {
|
||||||
exists, err := chainExists(ip6t, dnatChain.table, dnatChain.name)
|
exists, err := utils.ChainExists(ip6t, dnatChain.table, dnatChain.name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user