ensure iptables chain creation is idempotent

Concurrent use of the `portmap` and `firewall` plugins can result in
errors during iptables chain creation:

- The `portmap` plugin has a time-of-check-time-of-use race where it
  checks for existence of the chain but the operation isn't atomic.
- The `firewall` plugin doesn't check for existing chains and just
  returns an error.

This commit makes both operations idempotent by creating the chain and
then discarding the error if it's caused by the chain already
existing. It also factors the chain creation out into `pkg/utils` as a
site for future refactoring work.

Signed-off-by: Tim Gross <tim@0x74696d.com>
This commit is contained in:
Tim Gross
2019-11-04 15:54:47 -05:00
parent a16232968d
commit 58dd90b996
6 changed files with 160 additions and 42 deletions

View File

@ -18,6 +18,7 @@ import (
"fmt"
"strings"
"github.com/containernetworking/plugins/pkg/utils"
"github.com/coreos/go-iptables/iptables"
"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.
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 {
return err
}
if !exists {
if err := ipt.NewChain(c.table, c.name); err != nil {
return err
}
}
// Add the rules to the chain
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.
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 {
return err
}