Merge pull request #139247 from danwinship/utiliptables-cleanup

remove old workarounds in utiliptables
This commit is contained in:
kubernetes-prow[bot]
2026-07-17 12:41:25 +00:00
committed by GitHub
9 changed files with 144 additions and 1367 deletions

View File

@@ -307,12 +307,6 @@ func NewProxier(ctx context.Context,
go ipt.Monitor(kubeProxyCanaryChain, []utiliptables.Table{utiliptables.TableMangle, utiliptables.TableNAT, utiliptables.TableFilter},
proxier.forceSyncProxyRules, syncPeriod, wait.NeverStop)
if ipt.HasRandomFully() {
logger.V(2).Info("Iptables supports --random-fully")
} else {
logger.V(2).Info("Iptables does not support --random-fully")
}
return proxier, nil
}
@@ -754,15 +748,12 @@ func (proxier *Proxier) syncProxyRules() (retryError error) {
"-A", string(kubePostroutingChain),
"-j", "MARK", "--xor-mark", proxier.masqueradeMark,
)
masqRule := []string{
proxier.natRules.Write(
"-A", string(kubePostroutingChain),
"-m", "comment", "--comment", `"kubernetes service traffic requiring SNAT"`,
"-j", "MASQUERADE",
}
if proxier.iptables.HasRandomFully() {
masqRule = append(masqRule, "--random-fully")
}
proxier.natRules.Write(masqRule)
"--random-fully",
)
// Install the kubernetes-specific masquerade mark rule. We use a whole chain for
// this so that it is easier to flush and change, for example if the mark

View File

@@ -204,7 +204,7 @@ func TestParseIPTablesData(t *testing.T) {
:KUBE-SEP-SXIVWICOYRO3J4NJ - [0:0]
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --xor-mark 0x4000
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully
-A KUBE-MARK-MASQ -j MARK --or-mark 0x4000
-A KUBE-SERVICES -m comment --comment "ns1/svc1:p80 cluster IP" -m tcp -p tcp -d 10.20.30.41 --dport 80 -j KUBE-SVC-XPGD46QRK7WJZT7O
-A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment "ns1/svc1:p80 cluster IP" -m tcp -p tcp -d 10.20.30.41 --dport 80 ! -s 10.0.0.0/24 -j KUBE-MARK-MASQ
@@ -237,7 +237,7 @@ func TestParseIPTablesData(t *testing.T) {
`:KUBE-SEP-SXIVWICOYRO3J4NJ - [0:0]`,
`-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN`,
`-A KUBE-POSTROUTING -j MARK --xor-mark 0x4000`,
`-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE`,
`-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully`,
`-A KUBE-MARK-MASQ -j MARK --or-mark 0x4000`,
`-A KUBE-SERVICES -m comment --comment "ns1/svc1:p80 cluster IP" -m tcp -p tcp -d 10.20.30.41 --dport 80 -j KUBE-SVC-XPGD46QRK7WJZT7O`,
`-A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment "ns1/svc1:p80 cluster IP" -m tcp -p tcp -d 10.20.30.41 --dport 80 ! -s 10.0.0.0/24 -j KUBE-MARK-MASQ`,
@@ -780,7 +780,7 @@ func TestSortIPTablesRules(t *testing.T) {
:KUBE-SEP-C6EBXVWJJZMIWKLZ - [0:0]
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --xor-mark 0x4000
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully
-A KUBE-MARK-MASQ -j MARK --or-mark 0x4000
-A KUBE-SERVICES -m comment --comment "ns1/svc1:p80 cluster IP" -m tcp -p tcp -d 172.30.0.41 --dport 80 -j KUBE-SVC-XPGD46QRK7WJZT7O
-A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment "ns1/svc1:p80 cluster IP" -m tcp -p tcp -d 172.30.0.41 --dport 80 ! -s 10.0.0.0/8 -j KUBE-MARK-MASQ
@@ -876,7 +876,7 @@ func TestSortIPTablesRules(t *testing.T) {
-A KUBE-MARK-MASQ -j MARK --or-mark 0x4000
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --xor-mark 0x4000
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully
-A KUBE-SEP-C6EBXVWJJZMIWKLZ -m comment --comment ns4/svc4:p80 -s 10.180.0.5 -j KUBE-MARK-MASQ
-A KUBE-SEP-C6EBXVWJJZMIWKLZ -m comment --comment ns4/svc4:p80 -m tcp -p tcp -j DNAT --to-destination 10.180.0.5:80
-A KUBE-SEP-OYPFS5VJICHGATKP -m comment --comment ns3/svc3:p80 -s 10.180.0.3 -j KUBE-MARK-MASQ
@@ -1427,7 +1427,7 @@ func TestTracePacket(t *testing.T) {
-A POSTROUTING -m comment --comment kubernetes postrouting rules -j KUBE-POSTROUTING
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --xor-mark 0x4000
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully
-A KUBE-MARK-MASQ -j MARK --or-mark 0x4000
-A KUBE-NODEPORTS -m comment --comment ns2/svc2:p80 -m tcp -p tcp --dport 3001 -j KUBE-EXT-GNZBNJ2PO5MGZ6GT
-A KUBE-NODEPORTS -m comment --comment ns3/svc3:p80 -m tcp -p tcp --dport 3003 -j KUBE-EXT-X27LE4BHSL4DOUIK
@@ -1775,7 +1775,7 @@ func TestOverallIPTablesRules(t *testing.T) {
-A KUBE-MARK-MASQ -j MARK --or-mark 0x4000
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --xor-mark 0x4000
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully
-A KUBE-SEP-C6EBXVWJJZMIWKLZ -m comment --comment ns4/svc4:p80 -s 10.180.0.5 -j KUBE-MARK-MASQ
-A KUBE-SEP-C6EBXVWJJZMIWKLZ -m comment --comment ns4/svc4:p80 -m tcp -p tcp -j DNAT --to-destination 10.180.0.5:80
-A KUBE-SEP-I77PXRDZVX7PMWMN -m comment --comment ns5/svc5:p80 -s 10.180.0.3 -j KUBE-MARK-MASQ
@@ -2595,29 +2595,6 @@ func TestDropInvalidRule(t *testing.T) {
}
}
func TestMasqueradeRule(t *testing.T) {
for _, randomFully := range []bool{false, true} {
t.Run(fmt.Sprintf("randomFully %t", randomFully), func(t *testing.T) {
ipt := iptablestest.NewFake().SetHasRandomFully(randomFully)
fp := NewFakeProxier(ipt)
fp.syncProxyRules()
expectedFmt := dedent.Dedent(`
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --xor-mark 0x4000
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE%s
`)
var expected string
if randomFully {
expected = fmt.Sprintf(expectedFmt, " --random-fully")
} else {
expected = fmt.Sprintf(expectedFmt, "")
}
assertIPTablesChainEqual(t, getLine(), utiliptables.TableNAT, kubePostroutingChain, expected, fp.iptablesData.String())
})
}
}
// TestExternalTrafficPolicyLocal tests that traffic to externally-facing IPs does not get
// masqueraded when using Local traffic policy. For traffic from external sources, that
// means it can also only be routed to local endpoints, but for traffic from internal
@@ -5823,7 +5800,7 @@ func TestSyncProxyRulesRepeated(t *testing.T) {
-A KUBE-MARK-MASQ -j MARK --or-mark 0x4000
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --xor-mark 0x4000
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully
-A KUBE-SEP-SNQ3ZNILQDEJNDQO -m comment --comment ns1/svc1:p80 -s 10.0.1.1 -j KUBE-MARK-MASQ
-A KUBE-SEP-SNQ3ZNILQDEJNDQO -m comment --comment ns1/svc1:p80 -m tcp -p tcp -j DNAT --to-destination 10.0.1.1:80
-A KUBE-SEP-UHEGFW77JX3KXTOV -m comment --comment ns2/svc2:p8080 -s 10.0.2.1 -j KUBE-MARK-MASQ
@@ -5905,7 +5882,7 @@ func TestSyncProxyRulesRepeated(t *testing.T) {
-A KUBE-MARK-MASQ -j MARK --or-mark 0x4000
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --xor-mark 0x4000
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully
-A KUBE-SEP-BSWRHOQ77KEXZLNL -m comment --comment ns3/svc3:p80 -s 10.0.3.1 -j KUBE-MARK-MASQ
-A KUBE-SEP-BSWRHOQ77KEXZLNL -m comment --comment ns3/svc3:p80 -m tcp -p tcp -j DNAT --to-destination 10.0.3.1:80
-A KUBE-SVC-X27LE4BHSL4DOUIK -m comment --comment "ns3/svc3:p80 cluster IP" -m tcp -p tcp -d 172.30.0.43 --dport 80 ! -s 10.0.0.0/8 -j KUBE-MARK-MASQ
@@ -5958,7 +5935,7 @@ func TestSyncProxyRulesRepeated(t *testing.T) {
-A KUBE-MARK-MASQ -j MARK --or-mark 0x4000
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --xor-mark 0x4000
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully
-X KUBE-SEP-UHEGFW77JX3KXTOV
-X KUBE-SVC-2VJB64SDSIJUP5T6
COMMIT
@@ -6020,7 +5997,7 @@ func TestSyncProxyRulesRepeated(t *testing.T) {
-A KUBE-MARK-MASQ -j MARK --or-mark 0x4000
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --xor-mark 0x4000
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully
COMMIT
`)
assertIPTablesRulesEqual(t, getLine(), false, expected, fp.iptablesData.String())
@@ -6079,7 +6056,7 @@ func TestSyncProxyRulesRepeated(t *testing.T) {
-A KUBE-MARK-MASQ -j MARK --or-mark 0x4000
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --xor-mark 0x4000
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully
-A KUBE-SEP-AYCN5HPXMIRJNJXU -m comment --comment ns4/svc4:p80 -s 10.0.4.1 -j KUBE-MARK-MASQ
-A KUBE-SEP-AYCN5HPXMIRJNJXU -m comment --comment ns4/svc4:p80 -m tcp -p tcp -j DNAT --to-destination 10.0.4.1:80
-A KUBE-SVC-4SW47YFZTEDKD3PK -m comment --comment "ns4/svc4:p80 cluster IP" -m tcp -p tcp -d 172.30.0.44 --dport 80 ! -s 10.0.0.0/8 -j KUBE-MARK-MASQ
@@ -6137,7 +6114,7 @@ func TestSyncProxyRulesRepeated(t *testing.T) {
-A KUBE-MARK-MASQ -j MARK --or-mark 0x4000
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --xor-mark 0x4000
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully
-A KUBE-SEP-DKCFIS26GWF2WLWC -m comment --comment ns3/svc3:p80 -s 10.0.3.2 -j KUBE-MARK-MASQ
-A KUBE-SEP-DKCFIS26GWF2WLWC -m comment --comment ns3/svc3:p80 -m tcp -p tcp -j DNAT --to-destination 10.0.3.2:80
-A KUBE-SVC-X27LE4BHSL4DOUIK -m comment --comment "ns3/svc3:p80 cluster IP" -m tcp -p tcp -d 172.30.0.43 --dport 80 ! -s 10.0.0.0/8 -j KUBE-MARK-MASQ
@@ -6193,7 +6170,7 @@ func TestSyncProxyRulesRepeated(t *testing.T) {
-A KUBE-MARK-MASQ -j MARK --or-mark 0x4000
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --xor-mark 0x4000
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully
-A KUBE-SEP-DKCFIS26GWF2WLWC -m comment --comment ns3/svc3:p80 -s 10.0.3.2 -j KUBE-MARK-MASQ
-A KUBE-SEP-DKCFIS26GWF2WLWC -m comment --comment ns3/svc3:p80 -m tcp -p tcp -j DNAT --to-destination 10.0.3.2:80
-A KUBE-SEP-JVVZVJ7BSEPPRNBS -m comment --comment ns3/svc3:p80 -s 10.0.3.3 -j KUBE-MARK-MASQ
@@ -6248,7 +6225,7 @@ func TestSyncProxyRulesRepeated(t *testing.T) {
-A KUBE-MARK-MASQ -j MARK --or-mark 0x4000
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --xor-mark 0x4000
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully
COMMIT
`)
assertIPTablesRulesEqual(t, getLine(), false, expected, fp.iptablesData.String())
@@ -6345,7 +6322,7 @@ func TestSyncProxyRulesRepeated(t *testing.T) {
-A KUBE-MARK-MASQ -j MARK --or-mark 0x4000
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --xor-mark 0x4000
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully
-A KUBE-SEP-DKCFIS26GWF2WLWC -m comment --comment ns3/svc3:p80 -s 10.0.3.2 -j KUBE-MARK-MASQ
-A KUBE-SEP-DKCFIS26GWF2WLWC -m comment --comment ns3/svc3:p80 -m tcp -p tcp -j DNAT --to-destination 10.0.3.2:80
-A KUBE-SEP-JVVZVJ7BSEPPRNBS -m comment --comment ns3/svc3:p80 -s 10.0.3.3 -j KUBE-MARK-MASQ
@@ -6701,7 +6678,7 @@ func TestBadIPs(t *testing.T) {
-A KUBE-MARK-MASQ -j MARK --or-mark 0x4000
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --xor-mark 0x4000
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully
-A KUBE-SEP-SXIVWICOYRO3J4NJ -m comment --comment ns1/svc1:p80 -s 10.180.0.1 -j KUBE-MARK-MASQ
-A KUBE-SEP-SXIVWICOYRO3J4NJ -m comment --comment ns1/svc1:p80 -m tcp -p tcp -j DNAT --to-destination 10.180.0.1:80
-A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment "ns1/svc1:p80 cluster IP" -m tcp -p tcp -d 172.30.0.41 --dport 80 ! -s 10.0.0.0/8 -j KUBE-MARK-MASQ

View File

@@ -1468,15 +1468,12 @@ func (proxier *Proxier) writeIptablesRules() {
// XOR proxier.masqueradeMark to unset it
"-j", "MARK", "--xor-mark", proxier.masqueradeMark,
)
masqRule := []string{
proxier.natRules.Write(
"-A", string(kubePostroutingChain),
"-m", "comment", "--comment", `"kubernetes service traffic requiring SNAT"`,
"-j", "MASQUERADE",
}
if proxier.iptables.HasRandomFully() {
masqRule = append(masqRule, "--random-fully")
}
proxier.natRules.Write(masqRule)
"--random-fully",
)
// Install the kubernetes-specific masquerade mark rule. We use a whole chain for
// this so that it is easier to flush and change, for example if the mark

View File

@@ -1563,41 +1563,6 @@ func TestIPv6Proxier(t *testing.T) {
}
}
func TestMasqueradeRule(t *testing.T) {
for _, testcase := range []bool{false, true} {
_, ctx := ktesting.NewTestContext(t)
ipt := iptablestest.NewFake().SetHasRandomFully(testcase)
ipvs := ipvstest.NewFake()
ipset := ipsettest.NewFake(testIPSetVersion)
fp := NewFakeProxier(ctx, ipt, ipvs, ipset, nil, nil, v1.IPv4Protocol)
makeServiceMap(fp)
fp.syncProxyRules()
buf := bytes.NewBuffer(nil)
_ = ipt.SaveInto(utiliptables.TableNAT, buf)
natRules := strings.Split(buf.String(), "\n")
var hasMasqueradeJump, hasMasqRandomFully bool
for _, line := range natRules {
rule, _ := iptablestest.ParseRule(line, false)
if rule != nil && rule.Chain == kubePostroutingChain && rule.Jump != nil && rule.Jump.Value == "MASQUERADE" {
hasMasqueradeJump = true
if rule.RandomFully != nil {
hasMasqRandomFully = true
}
break
}
}
if !hasMasqueradeJump {
t.Errorf("Failed to find -j MASQUERADE in %s chain", kubePostroutingChain)
}
if hasMasqRandomFully != testcase {
probs := map[bool]string{false: "found", true: "did not find"}
t.Errorf("%s --random-fully in -j MASQUERADE rule in %s chain for HasRandomFully()=%v", probs[testcase], kubePostroutingChain, testcase)
}
}
}
func TestExternalIPsNoEndpoint(t *testing.T) {
_, ctx := ktesting.NewTestContext(t)
ipt := iptablestest.NewFake()

View File

@@ -26,12 +26,9 @@ import (
"regexp"
"strconv"
"strings"
"sync"
"time"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/sets"
utilversion "k8s.io/apimachinery/pkg/util/version"
utilwait "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/klog/v2"
utilexec "k8s.io/utils/exec"
@@ -88,12 +85,6 @@ type Interface interface {
// a reload) it will log an error and stop monitoring.
// (This function should be called from a goroutine.)
Monitor(canary Chain, tables []Table, reloadFunc func(), interval time.Duration, stopCh <-chan struct{})
// HasRandomFully reveals whether `-j MASQUERADE` takes the
// `--random-fully` option. This is helpful to work around a
// Linux kernel bug that sometimes causes multiple flows to get
// mapped to the same IP:PORT and consequently some suffer packet
// drops.
HasRandomFully() bool
// Present checks if the kernel supports the iptable interface
Present() error
@@ -164,78 +155,25 @@ const FlushTables FlushFlag = true
// NoFlushTables a boolean false constant for option flag FlushFlag
const NoFlushTables FlushFlag = false
// MinCheckVersion minimum version to be checked
// Versions of iptables less than this do not support the -C / --check flag
// (test whether a rule exists).
var MinCheckVersion = utilversion.MustParseGeneric("1.4.11")
// RandomFullyMinVersion is the minimum version from which the --random-fully flag is supported,
// used for port mapping to be fully randomized
var RandomFullyMinVersion = utilversion.MustParseGeneric("1.6.2")
// WaitMinVersion a minimum iptables versions supporting the -w and -w<seconds> flags
var WaitMinVersion = utilversion.MustParseGeneric("1.4.20")
// WaitSecondsMinVersion a minimum iptables versions supporting the wait seconds
var WaitSecondsMinVersion = utilversion.MustParseGeneric("1.4.22")
// WaitRestoreMinVersion a minimum iptables versions supporting the wait restore seconds
var WaitRestoreMinVersion = utilversion.MustParseGeneric("1.6.2")
// WaitString a constant for specifying the wait flag
const WaitString = "-w"
// WaitSecondsValue a constant for specifying the default wait seconds
const WaitSecondsValue = "5"
// LockfilePath16x is the iptables 1.6.x lock file acquired by any process that's making any change in the iptable rule
const LockfilePath16x = "/run/xtables.lock"
// LockfilePath14x is the iptables 1.4.x lock file acquired by any process that's making any change in the iptable rule
const LockfilePath14x = "@xtables"
// runner implements Interface in terms of exec("iptables").
type runner struct {
mu sync.Mutex
exec utilexec.Interface
protocol Protocol
hasCheck bool
hasRandomFully bool
waitFlag []string
restoreWaitFlag []string
lockfilePath14x string
lockfilePath16x string
exec utilexec.Interface
protocol Protocol
}
// newInternal returns a new Interface which will exec iptables, and allows the
// caller to change the iptables-restore lockfile path
func newInternal(exec utilexec.Interface, protocol Protocol, lockfilePath14x, lockfilePath16x string) Interface {
if lockfilePath16x == "" {
lockfilePath16x = LockfilePath16x
}
if lockfilePath14x == "" {
lockfilePath14x = LockfilePath14x
}
// newInternal returns a new Interface which will exec iptables
func newInternal(exec utilexec.Interface, protocol Protocol) Interface {
runner := &runner{
exec: exec,
protocol: protocol,
lockfilePath14x: lockfilePath14x,
lockfilePath16x: lockfilePath16x,
exec: exec,
protocol: protocol,
}
version, err := getIPTablesVersion(exec, protocol)
if err != nil {
// The only likely error is "no such file or directory", in which case any
// further commands will fail the same way, so we don't need to do
// anything special here.
return runner
}
runner.hasCheck = version.AtLeast(MinCheckVersion)
runner.hasRandomFully = version.AtLeast(RandomFullyMinVersion)
runner.waitFlag = getIPTablesWaitFlag(version)
runner.restoreWaitFlag = getIPTablesRestoreWaitFlag(version, exec, protocol)
return runner
}
@@ -243,16 +181,16 @@ func newInternal(exec utilexec.Interface, protocol Protocol, lockfilePath14x, lo
// Note that this function will return a single iptables Interface *and* an error, if only
// a single family is supported.
func New(protocol Protocol) Interface {
return newInternal(utilexec.New(), protocol, "", "")
return newInternal(utilexec.New(), protocol)
}
func newDualStackInternal(exec utilexec.Interface) map[v1.IPFamily]Interface {
interfaces := map[v1.IPFamily]Interface{}
iptv4 := newInternal(exec, ProtocolIPv4, "", "")
iptv4 := newInternal(exec, ProtocolIPv4)
if presentErr := iptv4.Present(); presentErr == nil {
interfaces[v1.IPv4Protocol] = iptv4
}
iptv6 := newInternal(exec, ProtocolIPv6, "", "")
iptv6 := newInternal(exec, ProtocolIPv6)
if presentErr := iptv6.Present(); presentErr == nil {
interfaces[v1.IPv6Protocol] = iptv6
}
@@ -272,9 +210,6 @@ func NewBestEffort() map[v1.IPFamily]Interface {
func (runner *runner) EnsureChain(table Table, chain Chain) (bool, error) {
fullArgs := makeFullArgs(table, chain)
runner.mu.Lock()
defer runner.mu.Unlock()
out, err := runner.run(opCreateChain, fullArgs)
if err != nil {
if ee, ok := err.(utilexec.ExitError); ok {
@@ -291,9 +226,6 @@ func (runner *runner) EnsureChain(table Table, chain Chain) (bool, error) {
func (runner *runner) FlushChain(table Table, chain Chain) error {
fullArgs := makeFullArgs(table, chain)
runner.mu.Lock()
defer runner.mu.Unlock()
out, err := runner.run(opFlushChain, fullArgs)
if err != nil {
return fmt.Errorf("error flushing chain %q: %v: %s", chain, err, out)
@@ -305,9 +237,6 @@ func (runner *runner) FlushChain(table Table, chain Chain) error {
func (runner *runner) DeleteChain(table Table, chain Chain) error {
fullArgs := makeFullArgs(table, chain)
runner.mu.Lock()
defer runner.mu.Unlock()
out, err := runner.run(opDeleteChain, fullArgs)
if err != nil {
return fmt.Errorf("error deleting chain %q: %v: %s", chain, err, out)
@@ -319,10 +248,7 @@ func (runner *runner) DeleteChain(table Table, chain Chain) error {
func (runner *runner) EnsureRule(position RulePosition, table Table, chain Chain, args ...string) (bool, error) {
fullArgs := makeFullArgs(table, chain, args...)
runner.mu.Lock()
defer runner.mu.Unlock()
exists, err := runner.checkRule(table, chain, args...)
exists, err := runner.checkRule(fullArgs)
if err != nil {
return false, err
}
@@ -340,10 +266,7 @@ func (runner *runner) EnsureRule(position RulePosition, table Table, chain Chain
func (runner *runner) DeleteRule(table Table, chain Chain, args ...string) error {
fullArgs := makeFullArgs(table, chain, args...)
runner.mu.Lock()
defer runner.mu.Unlock()
exists, err := runner.checkRule(table, chain, args...)
exists, err := runner.checkRule(fullArgs)
if err != nil {
return err
}
@@ -367,9 +290,6 @@ func (runner *runner) Protocol() Protocol {
// SaveInto is part of Interface.
func (runner *runner) SaveInto(table Table, buffer *bytes.Buffer) error {
runner.mu.Lock()
defer runner.mu.Unlock()
trace := utiltrace.New("iptables save")
defer trace.LogIfLong(2 * time.Second)
@@ -403,15 +323,8 @@ func (runner *runner) RestoreAll(data []byte, flush FlushFlag, counters RestoreC
return runner.restoreInternal(args, data, flush, counters)
}
type iptablesLocker interface {
Close() error
}
// restoreInternal is the shared part of Restore/RestoreAll
func (runner *runner) restoreInternal(args []string, data []byte, flush FlushFlag, counters RestoreCountersFlag) error {
runner.mu.Lock()
defer runner.mu.Unlock()
trace := utiltrace.New("iptables restore")
defer trace.LogIfLong(2 * time.Second)
@@ -422,24 +335,8 @@ func (runner *runner) restoreInternal(args []string, data []byte, flush FlushFla
args = append(args, "--counters")
}
// Grab the iptables lock to prevent iptables-restore and iptables
// from stepping on each other. iptables-restore 1.6.2 will have
// a --wait option like iptables itself, but that's not widely deployed.
if len(runner.restoreWaitFlag) == 0 {
locker, err := grabIptablesLocks(runner.lockfilePath14x, runner.lockfilePath16x)
if err != nil {
return err
}
trace.Step("Locks grabbed")
defer func(locker iptablesLocker) {
if err := locker.Close(); err != nil {
klog.ErrorS(err, "Failed to close iptables locks")
}
}(locker)
}
// run the command and return the output or an error including the output and error
fullArgs := append(runner.restoreWaitFlag, args...)
fullArgs := append([]string{WaitString, WaitSecondsValue}, args...)
iptablesRestoreCmd := iptablesRestoreCommand(runner.protocol)
klog.V(4).InfoS("Running", "command", iptablesRestoreCmd, "arguments", fullArgs)
cmd := runner.exec.Command(iptablesRestoreCmd, fullArgs...)
@@ -482,8 +379,7 @@ func (runner *runner) run(op operation, args []string) ([]byte, error) {
func (runner *runner) runContext(ctx context.Context, op operation, args []string) ([]byte, error) {
iptablesCmd := iptablesCommand(runner.protocol)
fullArgs := append(runner.waitFlag, string(op))
fullArgs = append(fullArgs, args...)
fullArgs := append([]string{WaitString, WaitSecondsValue, string(op)}, args...)
klog.V(5).InfoS("Running", "command", iptablesCmd, "arguments", fullArgs)
if ctx == nil {
return runner.exec.Command(iptablesCmd, fullArgs...).CombinedOutput()
@@ -494,71 +390,7 @@ func (runner *runner) runContext(ctx context.Context, op operation, args []strin
// Returns (bool, nil) if it was able to check the existence of the rule, or
// (<undefined>, error) if the process of checking failed.
func (runner *runner) checkRule(table Table, chain Chain, args ...string) (bool, error) {
if runner.hasCheck {
return runner.checkRuleUsingCheck(makeFullArgs(table, chain, args...))
}
return runner.checkRuleWithoutCheck(table, chain, args...)
}
var hexnumRE = regexp.MustCompile("0x0+([0-9])")
func trimhex(s string) string {
return hexnumRE.ReplaceAllString(s, "0x$1")
}
// Executes the rule check without using the "-C" flag, instead parsing iptables-save.
// Present for compatibility with <1.4.11 versions of iptables. This is full
// of hack and half-measures. We should nix this ASAP.
func (runner *runner) checkRuleWithoutCheck(table Table, chain Chain, args ...string) (bool, error) {
iptablesSaveCmd := iptablesSaveCommand(runner.protocol)
klog.V(1).InfoS("Running", "command", iptablesSaveCmd, "table", string(table))
out, err := runner.exec.Command(iptablesSaveCmd, "-t", string(table)).CombinedOutput()
if err != nil {
return false, fmt.Errorf("error checking rule: %v", err)
}
// Sadly, iptables has inconsistent quoting rules for comments. Just remove all quotes.
// Also, quoted multi-word comments (which are counted as a single arg)
// will be unpacked into multiple args,
// in order to compare against iptables-save output (which will be split at whitespace boundary)
// e.g. a single arg('"this must be before the NodePort rules"') will be unquoted and unpacked into 7 args.
var argsCopy []string
for i := range args {
tmpField := strings.Trim(args[i], "\"")
tmpField = trimhex(tmpField)
argsCopy = append(argsCopy, strings.Fields(tmpField)...)
}
argset := sets.New(argsCopy...)
for _, line := range strings.Split(string(out), "\n") {
fields := strings.Fields(line)
// Check that this is a rule for the correct chain, and that it has
// the correct number of argument (+2 for "-A <chain name>")
if !strings.HasPrefix(line, fmt.Sprintf("-A %s", string(chain))) || len(fields) != len(argsCopy)+2 {
continue
}
// Sadly, iptables has inconsistent quoting rules for comments.
// Just remove all quotes.
for i := range fields {
fields[i] = strings.Trim(fields[i], "\"")
fields[i] = trimhex(fields[i])
}
// TODO: This misses reorderings e.g. "-x foo ! -y bar" will match "! -x foo -y bar"
if sets.New(fields...).IsSuperset(argset) {
return true, nil
}
klog.V(5).InfoS("DBG: fields is not a superset of args", "fields", fields, "arguments", args)
}
return false, nil
}
// Executes the rule check using the "-C" flag
func (runner *runner) checkRuleUsingCheck(args []string) (bool, error) {
func (runner *runner) checkRule(args []string) (bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
@@ -640,9 +472,6 @@ func (runner *runner) Monitor(canary Chain, tables []Table, reloadFunc func(), i
func (runner *runner) ChainExists(table Table, chain Chain) (bool, error) {
fullArgs := makeFullArgs(table, chain)
runner.mu.Lock()
defer runner.mu.Unlock()
trace := utiltrace.New("iptables ChainExists")
defer trace.LogIfLong(2 * time.Second)
@@ -668,88 +497,6 @@ func makeFullArgs(table Table, chain Chain, args ...string) []string {
return append([]string{string(chain), "-t", string(table)}, args...)
}
const iptablesVersionPattern = `v([0-9]+(\.[0-9]+)+)`
// getIPTablesVersion runs "iptables --version" and parses the returned version
func getIPTablesVersion(exec utilexec.Interface, protocol Protocol) (*utilversion.Version, error) {
// this doesn't access mutable state so we don't need to use the interface / runner
iptablesCmd := iptablesCommand(protocol)
bytes, err := exec.Command(iptablesCmd, "--version").CombinedOutput()
if err != nil {
return nil, err
}
versionMatcher := regexp.MustCompile(iptablesVersionPattern)
match := versionMatcher.FindStringSubmatch(string(bytes))
if match == nil {
return nil, fmt.Errorf("no iptables version found in string: %s", bytes)
}
version, err := utilversion.ParseGeneric(match[1])
if err != nil {
return nil, fmt.Errorf("iptables version %q is not a valid version string: %v", match[1], err)
}
return version, nil
}
// Checks if iptables version has a "wait" flag
func getIPTablesWaitFlag(version *utilversion.Version) []string {
switch {
case version.AtLeast(WaitSecondsMinVersion):
return []string{WaitString, WaitSecondsValue}
case version.AtLeast(WaitMinVersion):
return []string{WaitString}
default:
return nil
}
}
// Checks if iptables-restore has a "wait" flag
func getIPTablesRestoreWaitFlag(version *utilversion.Version, exec utilexec.Interface, protocol Protocol) []string {
if version.AtLeast(WaitRestoreMinVersion) {
return []string{WaitString, WaitSecondsValue}
}
// Older versions may have backported features; if iptables-restore supports
// --version, assume it also supports --wait
vstring, err := getIPTablesRestoreVersionString(exec, protocol)
if err != nil || vstring == "" {
klog.V(3).InfoS("Couldn't get iptables-restore version; assuming it doesn't support --wait")
return nil
}
if _, err := utilversion.ParseGeneric(vstring); err != nil {
klog.V(3).InfoS("Couldn't parse iptables-restore version; assuming it doesn't support --wait")
return nil
}
return []string{WaitString}
}
// getIPTablesRestoreVersionString runs "iptables-restore --version" to get the version string
// in the form "X.X.X"
func getIPTablesRestoreVersionString(exec utilexec.Interface, protocol Protocol) (string, error) {
// this doesn't access mutable state so we don't need to use the interface / runner
// iptables-restore hasn't always had --version, and worse complains
// about unrecognized commands but doesn't exit when it gets them.
// Work around that by setting stdin to nothing so it exits immediately.
iptablesRestoreCmd := iptablesRestoreCommand(protocol)
cmd := exec.Command(iptablesRestoreCmd, "--version")
cmd.SetStdin(bytes.NewReader([]byte{}))
bytes, err := cmd.CombinedOutput()
if err != nil {
return "", err
}
versionMatcher := regexp.MustCompile(iptablesVersionPattern)
match := versionMatcher.FindStringSubmatch(string(bytes))
if match == nil {
return "", fmt.Errorf("no iptables version found in string: %s", bytes)
}
return match[1], nil
}
func (runner *runner) HasRandomFully() bool {
return runner.hasRandomFully
}
// Present tests if iptable is supported on current kernel by checking the existence
// of default table and chain
func (runner *runner) Present() error {

View File

@@ -1,100 +0,0 @@
//go:build linux
/*
Copyright 2017 The Kubernetes 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 iptables
import (
"fmt"
"net"
"os"
"time"
"golang.org/x/sys/unix"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/wait"
)
type locker struct {
lock16 *os.File
lock14 *net.UnixListener
}
func (l *locker) Close() error {
errList := []error{}
if l.lock16 != nil {
if err := l.lock16.Close(); err != nil {
errList = append(errList, err)
}
}
if l.lock14 != nil {
if err := l.lock14.Close(); err != nil {
errList = append(errList, err)
}
}
return utilerrors.NewAggregate(errList)
}
func grabIptablesLocks(lockfilePath14x, lockfilePath16x string) (iptablesLocker, error) {
var err error
var success bool
l := &locker{}
defer func(l *locker) {
// Clean up immediately on failure
if !success {
l.Close()
}
}(l)
// Grab both 1.6.x and 1.4.x-style locks; we don't know what the
// iptables-restore version is if it doesn't support --wait, so we
// can't assume which lock method it'll use.
// Roughly duplicate iptables 1.6.x xtables_lock() function.
l.lock16, err = os.OpenFile(lockfilePath16x, os.O_CREATE, 0600)
if err != nil {
return nil, fmt.Errorf("failed to open iptables lock %s: %v", lockfilePath16x, err)
}
if err := wait.PollImmediate(200*time.Millisecond, 2*time.Second, func() (bool, error) {
if err := grabIptablesFileLock(l.lock16); err != nil {
return false, nil
}
return true, nil
}); err != nil {
return nil, fmt.Errorf("failed to acquire new iptables lock: %v", err)
}
// Roughly duplicate iptables 1.4.x xtables_lock() function.
if err := wait.PollImmediate(200*time.Millisecond, 2*time.Second, func() (bool, error) {
l.lock14, err = net.ListenUnix("unix", &net.UnixAddr{Name: lockfilePath14x, Net: "unix"})
if err != nil {
return false, nil
}
return true, nil
}); err != nil {
return nil, fmt.Errorf("failed to acquire old iptables lock: %v", err)
}
success = true
return l, nil
}
func grabIptablesFileLock(f *os.File) error {
return unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB)
}

File diff suppressed because it is too large Load Diff

View File

@@ -190,7 +190,7 @@ func (mfc *monitorFakeCmd) Stop() {
func TestIPTablesMonitor(t *testing.T) {
mfe := newMonitorFakeExec()
ipt := newInternal(mfe, ProtocolIPv4, "", "")
ipt := newInternal(mfe, ProtocolIPv4)
var reloads uint32
stopCh := make(chan struct{})

View File

@@ -30,8 +30,7 @@ import (
// FakeIPTables is no-op implementation of iptables Interface.
type FakeIPTables struct {
hasRandomFully bool
protocol iptables.Protocol
protocol iptables.Protocol
Dump *IPTablesDump
}
@@ -77,12 +76,6 @@ func NewIPv6Fake() *FakeIPTables {
return f
}
// SetHasRandomFully sets f's return value for HasRandomFully()
func (f *FakeIPTables) SetHasRandomFully(can bool) *FakeIPTables {
f.hasRandomFully = can
return f
}
// EnsureChain is part of iptables.Interface
func (f *FakeIPTables) EnsureChain(table iptables.Table, chain iptables.Chain) (bool, error) {
t, err := f.Dump.GetTable(table)
@@ -321,11 +314,6 @@ func (f *FakeIPTables) RestoreAll(data []byte, flush iptables.FlushFlag, counter
func (f *FakeIPTables) Monitor(canary iptables.Chain, tables []iptables.Table, reloadFunc func(), interval time.Duration, stopCh <-chan struct{}) {
}
// HasRandomFully is part of iptables.Interface
func (f *FakeIPTables) HasRandomFully() bool {
return f.hasRandomFully
}
func (f *FakeIPTables) Present() error {
return nil
}