mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 19:01:49 +00:00
Merge pull request #109844 from danwinship/iptables-tests-new
improve parsing in iptables unit tests
This commit is contained in:
commit
dc4e91a875
File diff suppressed because it is too large
Load Diff
@ -1664,11 +1664,25 @@ func TestMasqueradeRule(t *testing.T) {
|
|||||||
makeServiceMap(fp)
|
makeServiceMap(fp)
|
||||||
fp.syncProxyRules()
|
fp.syncProxyRules()
|
||||||
|
|
||||||
postRoutingRules := ipt.GetRules(string(kubePostroutingChain))
|
buf := bytes.NewBuffer(nil)
|
||||||
if !hasJump(postRoutingRules, "MASQUERADE", "") {
|
_ = ipt.SaveInto(utiliptables.TableNAT, buf)
|
||||||
|
natRules := strings.Split(string(buf.Bytes()), "\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)
|
t.Errorf("Failed to find -j MASQUERADE in %s chain", kubePostroutingChain)
|
||||||
}
|
}
|
||||||
if hasMasqRandomFully(postRoutingRules) != testcase {
|
if hasMasqRandomFully != testcase {
|
||||||
probs := map[bool]string{false: "found", true: "did not find"}
|
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)
|
t.Errorf("%s --random-fully in -j MASQUERADE rule in %s chain for HasRandomFully()=%v", probs[testcase], kubePostroutingChain, testcase)
|
||||||
}
|
}
|
||||||
@ -3817,42 +3831,41 @@ func buildFakeProxier() (*iptablestest.FakeIPTables, *Proxier) {
|
|||||||
return ipt, NewFakeProxier(ipt, ipvs, ipset, nil, nil, v1.IPv4Protocol)
|
return ipt, NewFakeProxier(ipt, ipvs, ipset, nil, nil, v1.IPv4Protocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasJump(rules []iptablestest.Rule, destChain, ipSet string) bool {
|
func getRules(ipt *iptablestest.FakeIPTables, chain utiliptables.Chain) []*iptablestest.Rule {
|
||||||
for _, r := range rules {
|
var rules []*iptablestest.Rule
|
||||||
if r[iptablestest.Jump] == destChain {
|
|
||||||
if ipSet == "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if strings.Contains(r[iptablestest.MatchSet], ipSet) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasMasqRandomFully(rules []iptablestest.Rule) bool {
|
buf := bytes.NewBuffer(nil)
|
||||||
for _, r := range rules {
|
_ = ipt.SaveInto(utiliptables.TableNAT, buf)
|
||||||
if r[iptablestest.Masquerade] == "--random-fully" {
|
_ = ipt.SaveInto(utiliptables.TableFilter, buf)
|
||||||
return true
|
lines := strings.Split(string(buf.Bytes()), "\n")
|
||||||
|
for _, l := range lines {
|
||||||
|
if !strings.HasPrefix(l, "-A ") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rule, _ := iptablestest.ParseRule(l, false)
|
||||||
|
if rule != nil && rule.Chain == chain {
|
||||||
|
rules = append(rules, rule)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return rules
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkIptables to check expected iptables chain and rules. The got rules must have same number and order as the
|
// checkIptables to check expected iptables chain and rules. The got rules must have same number and order as the
|
||||||
// expected rules.
|
// expected rules.
|
||||||
func checkIptables(t *testing.T, ipt *iptablestest.FakeIPTables, epIpt netlinktest.ExpectedIptablesChain) {
|
func checkIptables(t *testing.T, ipt *iptablestest.FakeIPTables, epIpt netlinktest.ExpectedIptablesChain) {
|
||||||
for epChain, epRules := range epIpt {
|
for epChain, epRules := range epIpt {
|
||||||
rules := ipt.GetRules(epChain)
|
rules := getRules(ipt, utiliptables.Chain(epChain))
|
||||||
if len(rules) != len(epRules) {
|
if len(rules) != len(epRules) {
|
||||||
t.Errorf("Expected %d iptables rule in chain %s, got %d", len(epRules), epChain, len(rules))
|
t.Errorf("Expected %d iptables rule in chain %s, got %d", len(epRules), epChain, len(rules))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for i, epRule := range epRules {
|
for i, epRule := range epRules {
|
||||||
rule := rules[i]
|
rule := rules[i]
|
||||||
if rule[iptablestest.Jump] != epRule.JumpChain || !strings.Contains(rule[iptablestest.MatchSet], epRule.MatchSet) {
|
if rule.Jump == nil || rule.Jump.Value != epRule.JumpChain {
|
||||||
t.Errorf("Expected MatchSet=%s JumpChain=%s, got MatchSet=%s JumpChain=%s", epRule.MatchSet, epRule.JumpChain, rule[iptablestest.MatchSet], rule[iptablestest.Jump])
|
t.Errorf("Expected MatchSet=%s JumpChain=%s, got %s", epRule.MatchSet, epRule.JumpChain, rule.Raw)
|
||||||
|
}
|
||||||
|
if (epRule.MatchSet == "" && rule.MatchSet != nil) || (epRule.MatchSet != "" && (rule.MatchSet == nil || rule.MatchSet.Value != epRule.MatchSet)) {
|
||||||
|
t.Errorf("Expected MatchSet=%s JumpChain=%s, got %s", epRule.MatchSet, epRule.JumpChain, rule.Raw)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,86 +25,149 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/util/iptables"
|
"k8s.io/kubernetes/pkg/util/iptables"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// Destination represents the destination address flag
|
|
||||||
Destination = "-d "
|
|
||||||
// Source represents the source address flag
|
|
||||||
Source = "-s "
|
|
||||||
// DPort represents the destination port flag
|
|
||||||
DPort = "--dport "
|
|
||||||
// Protocol represents the protocol flag
|
|
||||||
Protocol = "-p "
|
|
||||||
// Jump represents jump flag specifies the jump target
|
|
||||||
Jump = "-j "
|
|
||||||
// Reject specifies the reject target
|
|
||||||
Reject = "REJECT"
|
|
||||||
// Accept specifies the accept target
|
|
||||||
Accept = "ACCEPT"
|
|
||||||
// ToDest represents the flag used to specify the destination address in DNAT
|
|
||||||
ToDest = "--to-destination "
|
|
||||||
// Recent represents the sub-command recent that allows to dynamically create list of IP address to match against
|
|
||||||
Recent = "recent "
|
|
||||||
// MatchSet represents the flag which match packets against the specified set
|
|
||||||
MatchSet = "--match-set "
|
|
||||||
// SrcType represents the --src-type flag which matches if the source address is of given type
|
|
||||||
SrcType = "--src-type "
|
|
||||||
// Masquerade represents the target that is used in nat table.
|
|
||||||
Masquerade = "MASQUERADE "
|
|
||||||
)
|
|
||||||
|
|
||||||
// Rule holds a map of rules.
|
|
||||||
type Rule map[string]string
|
|
||||||
|
|
||||||
// FakeIPTables is no-op implementation of iptables Interface.
|
// FakeIPTables is no-op implementation of iptables Interface.
|
||||||
type FakeIPTables struct {
|
type FakeIPTables struct {
|
||||||
hasRandomFully bool
|
hasRandomFully bool
|
||||||
Lines []byte
|
|
||||||
protocol iptables.Protocol
|
protocol iptables.Protocol
|
||||||
|
|
||||||
|
Dump *IPTablesDump
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFake returns a no-op iptables.Interface
|
// NewFake returns a no-op iptables.Interface
|
||||||
func NewFake() *FakeIPTables {
|
func NewFake() *FakeIPTables {
|
||||||
return &FakeIPTables{protocol: iptables.ProtocolIPv4}
|
f := &FakeIPTables{
|
||||||
|
protocol: iptables.ProtocolIPv4,
|
||||||
|
Dump: &IPTablesDump{
|
||||||
|
Tables: []Table{
|
||||||
|
{
|
||||||
|
Name: iptables.TableNAT,
|
||||||
|
Chains: []Chain{
|
||||||
|
{Name: iptables.ChainPrerouting},
|
||||||
|
{Name: iptables.ChainInput},
|
||||||
|
{Name: iptables.ChainOutput},
|
||||||
|
{Name: iptables.ChainPostrouting},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: iptables.TableFilter,
|
||||||
|
Chains: []Chain{
|
||||||
|
{Name: iptables.ChainInput},
|
||||||
|
{Name: iptables.ChainForward},
|
||||||
|
{Name: iptables.ChainOutput},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: iptables.TableMangle,
|
||||||
|
Chains: []Chain{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewIPv6Fake returns a no-op iptables.Interface with IsIPv6() == true
|
// NewIPv6Fake returns a no-op iptables.Interface with IsIPv6() == true
|
||||||
func NewIPv6Fake() *FakeIPTables {
|
func NewIPv6Fake() *FakeIPTables {
|
||||||
return &FakeIPTables{protocol: iptables.ProtocolIPv6}
|
f := NewFake()
|
||||||
|
f.protocol = iptables.ProtocolIPv6
|
||||||
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHasRandomFully is part of iptables.Interface
|
// SetHasRandomFully sets f's return value for HasRandomFully()
|
||||||
func (f *FakeIPTables) SetHasRandomFully(can bool) *FakeIPTables {
|
func (f *FakeIPTables) SetHasRandomFully(can bool) *FakeIPTables {
|
||||||
f.hasRandomFully = can
|
f.hasRandomFully = can
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnsureChain is part of iptables.Interface
|
// EnsureChain is part of iptables.Interface
|
||||||
func (*FakeIPTables) EnsureChain(table iptables.Table, chain iptables.Chain) (bool, error) {
|
func (f *FakeIPTables) EnsureChain(table iptables.Table, chain iptables.Chain) (bool, error) {
|
||||||
return true, nil
|
t, err := f.Dump.GetTable(table)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if c, _ := f.Dump.GetChain(table, chain); c != nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
t.Chains = append(t.Chains, Chain{Name: chain})
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlushChain is part of iptables.Interface
|
// FlushChain is part of iptables.Interface
|
||||||
func (*FakeIPTables) FlushChain(table iptables.Table, chain iptables.Chain) error {
|
func (f *FakeIPTables) FlushChain(table iptables.Table, chain iptables.Chain) error {
|
||||||
|
if c, _ := f.Dump.GetChain(table, chain); c != nil {
|
||||||
|
c.Rules = nil
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteChain is part of iptables.Interface
|
// DeleteChain is part of iptables.Interface
|
||||||
func (*FakeIPTables) DeleteChain(table iptables.Table, chain iptables.Chain) error {
|
func (f *FakeIPTables) DeleteChain(table iptables.Table, chain iptables.Chain) error {
|
||||||
|
t, err := f.Dump.GetTable(table)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := range t.Chains {
|
||||||
|
if t.Chains[i].Name == chain {
|
||||||
|
t.Chains = append(t.Chains[:i], t.Chains[i+1:]...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChainExists is part of iptables.Interface
|
// ChainExists is part of iptables.Interface
|
||||||
func (*FakeIPTables) ChainExists(table iptables.Table, chain iptables.Chain) (bool, error) {
|
func (f *FakeIPTables) ChainExists(table iptables.Table, chain iptables.Chain) (bool, error) {
|
||||||
return true, nil
|
if _, err := f.Dump.GetTable(table); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if c, _ := f.Dump.GetChain(table, chain); c != nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnsureRule is part of iptables.Interface
|
// EnsureRule is part of iptables.Interface
|
||||||
func (*FakeIPTables) EnsureRule(position iptables.RulePosition, table iptables.Table, chain iptables.Chain, args ...string) (bool, error) {
|
func (f *FakeIPTables) EnsureRule(position iptables.RulePosition, table iptables.Table, chain iptables.Chain, args ...string) (bool, error) {
|
||||||
return true, nil
|
c, err := f.Dump.GetChain(table, chain)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rule := "-A " + string(chain) + " " + strings.Join(args, " ")
|
||||||
|
for _, r := range c.Rules {
|
||||||
|
if r.Raw == rule {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := ParseRule(rule, false)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if position == iptables.Append {
|
||||||
|
c.Rules = append(c.Rules, parsed)
|
||||||
|
} else {
|
||||||
|
c.Rules = append([]*Rule{parsed}, c.Rules...)
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteRule is part of iptables.Interface
|
// DeleteRule is part of iptables.Interface
|
||||||
func (*FakeIPTables) DeleteRule(table iptables.Table, chain iptables.Chain, args ...string) error {
|
func (f *FakeIPTables) DeleteRule(table iptables.Table, chain iptables.Chain, args ...string) error {
|
||||||
|
c, err := f.Dump.GetChain(table, chain)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rule := "-A " + string(chain) + " " + strings.Join(args, " ")
|
||||||
|
for i, r := range c.Rules {
|
||||||
|
if r.Raw == rule {
|
||||||
|
c.Rules = append(c.Rules[:i], c.Rules[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,27 +181,102 @@ func (f *FakeIPTables) Protocol() iptables.Protocol {
|
|||||||
return f.protocol
|
return f.protocol
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save is part of iptables.Interface
|
func (f *FakeIPTables) saveTable(table iptables.Table, buffer *bytes.Buffer) error {
|
||||||
func (f *FakeIPTables) Save(table iptables.Table) ([]byte, error) {
|
t, err := f.Dump.GetTable(table)
|
||||||
lines := make([]byte, len(f.Lines))
|
if err != nil {
|
||||||
copy(lines, f.Lines)
|
return err
|
||||||
return lines, nil
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(buffer, "*%s\n", table)
|
||||||
|
for _, c := range t.Chains {
|
||||||
|
fmt.Fprintf(buffer, ":%s - [%d:%d]\n", c.Name, c.Packets, c.Bytes)
|
||||||
|
}
|
||||||
|
for _, c := range t.Chains {
|
||||||
|
for _, r := range c.Rules {
|
||||||
|
fmt.Fprintf(buffer, "%s\n", r.Raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(buffer, "COMMIT\n")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveInto is part of iptables.Interface
|
// SaveInto is part of iptables.Interface
|
||||||
func (f *FakeIPTables) SaveInto(table iptables.Table, buffer *bytes.Buffer) error {
|
func (f *FakeIPTables) SaveInto(table iptables.Table, buffer *bytes.Buffer) error {
|
||||||
buffer.Write(f.Lines)
|
if table == "" {
|
||||||
|
// As a secret extension to the API, FakeIPTables treats table="" as
|
||||||
|
// meaning "all tables"
|
||||||
|
for i := range f.Dump.Tables {
|
||||||
|
err := f.saveTable(f.Dump.Tables[i].Name, buffer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.saveTable(table, buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeIPTables) restoreTable(newTable *Table, flush iptables.FlushFlag, counters iptables.RestoreCountersFlag) error {
|
||||||
|
oldTable, err := f.Dump.GetTable(newTable.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if flush == iptables.FlushTables {
|
||||||
|
oldTable.Chains = make([]Chain, 0, len(newTable.Chains))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, newChain := range newTable.Chains {
|
||||||
|
oldChain, _ := f.Dump.GetChain(newTable.Name, newChain.Name)
|
||||||
|
switch {
|
||||||
|
case oldChain == nil && newChain.Deleted:
|
||||||
|
// no-op
|
||||||
|
case oldChain == nil && !newChain.Deleted:
|
||||||
|
oldTable.Chains = append(oldTable.Chains, newChain)
|
||||||
|
case oldChain != nil && newChain.Deleted:
|
||||||
|
// FIXME: should make sure chain is not referenced from other jumps
|
||||||
|
_ = f.DeleteChain(newTable.Name, newChain.Name)
|
||||||
|
case oldChain != nil && !newChain.Deleted:
|
||||||
|
// replace old data with new
|
||||||
|
oldChain.Rules = newChain.Rules
|
||||||
|
if counters == iptables.RestoreCounters {
|
||||||
|
oldChain.Packets = newChain.Packets
|
||||||
|
oldChain.Bytes = newChain.Bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore is part of iptables.Interface
|
// Restore is part of iptables.Interface
|
||||||
func (*FakeIPTables) Restore(table iptables.Table, data []byte, flush iptables.FlushFlag, counters iptables.RestoreCountersFlag) error {
|
func (f *FakeIPTables) Restore(table iptables.Table, data []byte, flush iptables.FlushFlag, counters iptables.RestoreCountersFlag) error {
|
||||||
return nil
|
dump, err := ParseIPTablesDump(string(data))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newTable, err := dump.GetTable(table)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.restoreTable(newTable, flush, counters)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestoreAll is part of iptables.Interface
|
// RestoreAll is part of iptables.Interface
|
||||||
func (f *FakeIPTables) RestoreAll(data []byte, flush iptables.FlushFlag, counters iptables.RestoreCountersFlag) error {
|
func (f *FakeIPTables) RestoreAll(data []byte, flush iptables.FlushFlag, counters iptables.RestoreCountersFlag) error {
|
||||||
f.Lines = data
|
dump, err := ParseIPTablesDump(string(data))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range dump.Tables {
|
||||||
|
err = f.restoreTable(&dump.Tables[i], flush, counters)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,31 +284,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{}) {
|
func (f *FakeIPTables) Monitor(canary iptables.Chain, tables []iptables.Table, reloadFunc func(), interval time.Duration, stopCh <-chan struct{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getToken(line, separator string) string {
|
|
||||||
tokens := strings.Split(line, separator)
|
|
||||||
if len(tokens) == 2 {
|
|
||||||
return strings.Split(tokens[1], " ")[0]
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRules is part of iptables.Interface
|
|
||||||
func (f *FakeIPTables) GetRules(chainName string) (rules []Rule) {
|
|
||||||
for _, l := range strings.Split(string(f.Lines), "\n") {
|
|
||||||
if strings.Contains(l, fmt.Sprintf("-A %v", chainName)) {
|
|
||||||
newRule := Rule(map[string]string{})
|
|
||||||
for _, arg := range []string{Destination, Source, DPort, Protocol, Jump, ToDest, Recent, MatchSet, SrcType, Masquerade} {
|
|
||||||
tok := getToken(l, arg)
|
|
||||||
if tok != "" {
|
|
||||||
newRule[arg] = tok
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rules = append(rules, newRule)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasRandomFully is part of iptables.Interface
|
// HasRandomFully is part of iptables.Interface
|
||||||
func (f *FakeIPTables) HasRandomFully() bool {
|
func (f *FakeIPTables) HasRandomFully() bool {
|
||||||
return f.hasRandomFully
|
return f.hasRandomFully
|
||||||
|
315
pkg/util/iptables/testing/fake_test.go
Normal file
315
pkg/util/iptables/testing/fake_test.go
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 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 testing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/lithammer/dedent"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/util/iptables"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFakeIPTables(t *testing.T) {
|
||||||
|
fake := NewFake()
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
err := fake.SaveInto("", buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error from SaveInto: %v", err)
|
||||||
|
}
|
||||||
|
expected := dedent.Dedent(strings.Trim(`
|
||||||
|
*nat
|
||||||
|
:PREROUTING - [0:0]
|
||||||
|
:INPUT - [0:0]
|
||||||
|
:OUTPUT - [0:0]
|
||||||
|
:POSTROUTING - [0:0]
|
||||||
|
COMMIT
|
||||||
|
*filter
|
||||||
|
:INPUT - [0:0]
|
||||||
|
:FORWARD - [0:0]
|
||||||
|
:OUTPUT - [0:0]
|
||||||
|
COMMIT
|
||||||
|
*mangle
|
||||||
|
COMMIT
|
||||||
|
`, "\n"))
|
||||||
|
if string(buf.Bytes()) != expected {
|
||||||
|
t.Fatalf("bad initial dump. expected:\n%s\n\ngot:\n%s\n", expected, buf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnsureChain
|
||||||
|
existed, err := fake.EnsureChain(iptables.Table("blah"), iptables.Chain("KUBE-TEST"))
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("did not get expected error creating chain in non-existent table")
|
||||||
|
} else if existed {
|
||||||
|
t.Errorf("wrong return value from EnsureChain with non-existent table")
|
||||||
|
}
|
||||||
|
existed, err = fake.EnsureChain(iptables.TableNAT, iptables.Chain("KUBE-TEST"))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error creating chain: %v", err)
|
||||||
|
} else if existed {
|
||||||
|
t.Errorf("wrong return value from EnsureChain with non-existent chain")
|
||||||
|
}
|
||||||
|
existed, err = fake.EnsureChain(iptables.TableNAT, iptables.Chain("KUBE-TEST"))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error creating chain: %v", err)
|
||||||
|
} else if !existed {
|
||||||
|
t.Errorf("wrong return value from EnsureChain with existing chain")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainExists
|
||||||
|
exists, err := fake.ChainExists(iptables.TableNAT, iptables.Chain("KUBE-TEST"))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error checking chain: %v", err)
|
||||||
|
} else if !exists {
|
||||||
|
t.Errorf("wrong return value from ChainExists with existing chain")
|
||||||
|
}
|
||||||
|
exists, err = fake.ChainExists(iptables.TableNAT, iptables.Chain("KUBE-TEST-NOT"))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error checking chain: %v", err)
|
||||||
|
} else if exists {
|
||||||
|
t.Errorf("wrong return value from ChainExists with non-existent chain")
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnsureRule
|
||||||
|
existed, err = fake.EnsureRule(iptables.Append, iptables.Table("blah"), iptables.Chain("KUBE-TEST"), "-j", "ACCEPT")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("did not get expected error creating rule in non-existent table")
|
||||||
|
} else if existed {
|
||||||
|
t.Errorf("wrong return value from EnsureRule with non-existent table")
|
||||||
|
}
|
||||||
|
existed, err = fake.EnsureRule(iptables.Append, iptables.TableNAT, iptables.Chain("KUBE-TEST-NOT"), "-j", "ACCEPT")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("did not get expected error creating rule in non-existent chain")
|
||||||
|
} else if existed {
|
||||||
|
t.Errorf("wrong return value from EnsureRule with non-existent chain")
|
||||||
|
}
|
||||||
|
existed, err = fake.EnsureRule(iptables.Append, iptables.TableNAT, iptables.Chain("KUBE-TEST"), "-j", "ACCEPT")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error creating rule: %v", err)
|
||||||
|
} else if existed {
|
||||||
|
t.Errorf("wrong return value from EnsureRule with non-existent rule")
|
||||||
|
}
|
||||||
|
existed, err = fake.EnsureRule(iptables.Prepend, iptables.TableNAT, iptables.Chain("KUBE-TEST"), "-j", "DROP")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error creating rule: %v", err)
|
||||||
|
} else if existed {
|
||||||
|
t.Errorf("wrong return value from EnsureRule with non-existent rule")
|
||||||
|
}
|
||||||
|
existed, err = fake.EnsureRule(iptables.Append, iptables.TableNAT, iptables.Chain("KUBE-TEST"), "-j", "DROP")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error creating rule: %v", err)
|
||||||
|
} else if !existed {
|
||||||
|
t.Errorf("wrong return value from EnsureRule with already-existing rule")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity-check...
|
||||||
|
buf.Reset()
|
||||||
|
err = fake.SaveInto("", buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error from SaveInto: %v", err)
|
||||||
|
}
|
||||||
|
expected = dedent.Dedent(strings.Trim(`
|
||||||
|
*nat
|
||||||
|
:PREROUTING - [0:0]
|
||||||
|
:INPUT - [0:0]
|
||||||
|
:OUTPUT - [0:0]
|
||||||
|
:POSTROUTING - [0:0]
|
||||||
|
:KUBE-TEST - [0:0]
|
||||||
|
-A KUBE-TEST -j DROP
|
||||||
|
-A KUBE-TEST -j ACCEPT
|
||||||
|
COMMIT
|
||||||
|
*filter
|
||||||
|
:INPUT - [0:0]
|
||||||
|
:FORWARD - [0:0]
|
||||||
|
:OUTPUT - [0:0]
|
||||||
|
COMMIT
|
||||||
|
*mangle
|
||||||
|
COMMIT
|
||||||
|
`, "\n"))
|
||||||
|
if string(buf.Bytes()) != expected {
|
||||||
|
t.Fatalf("bad sanity-check dump. expected:\n%s\n\ngot:\n%s\n", expected, buf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRule
|
||||||
|
err = fake.DeleteRule(iptables.Table("blah"), iptables.Chain("KUBE-TEST"), "-j", "DROP")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("did not get expected error deleting rule in non-existent table")
|
||||||
|
}
|
||||||
|
err = fake.DeleteRule(iptables.TableNAT, iptables.Chain("KUBE-TEST-NOT"), "-j", "DROP")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("did not get expected error deleting rule in non-existent chain")
|
||||||
|
}
|
||||||
|
err = fake.DeleteRule(iptables.TableNAT, iptables.Chain("KUBE-TEST"), "-j", "DROPLET")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error deleting non-existent rule: %v", err)
|
||||||
|
}
|
||||||
|
err = fake.DeleteRule(iptables.TableNAT, iptables.Chain("KUBE-TEST"), "-j", "DROP")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error deleting rule: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore
|
||||||
|
rules := dedent.Dedent(strings.Trim(`
|
||||||
|
*nat
|
||||||
|
:KUBE-RESTORED - [0:0]
|
||||||
|
:KUBE-MISC-CHAIN - [0:0]
|
||||||
|
:KUBE-EMPTY - [0:0]
|
||||||
|
-A KUBE-RESTORED -m comment --comment "restored chain" -j ACCEPT
|
||||||
|
-A KUBE-MISC-CHAIN -s 1.2.3.4 -j DROP
|
||||||
|
-A KUBE-MISC-CHAIN -d 5.6.7.8 -j MASQUERADE
|
||||||
|
COMMIT
|
||||||
|
`, "\n"))
|
||||||
|
err = fake.Restore(iptables.TableNAT, []byte(rules), iptables.NoFlushTables, iptables.NoRestoreCounters)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error from Restore: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We used NoFlushTables, so this should leave KUBE-TEST unchanged
|
||||||
|
buf.Reset()
|
||||||
|
err = fake.SaveInto("", buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error from SaveInto: %v", err)
|
||||||
|
}
|
||||||
|
expected = dedent.Dedent(strings.Trim(`
|
||||||
|
*nat
|
||||||
|
:PREROUTING - [0:0]
|
||||||
|
:INPUT - [0:0]
|
||||||
|
:OUTPUT - [0:0]
|
||||||
|
:POSTROUTING - [0:0]
|
||||||
|
:KUBE-TEST - [0:0]
|
||||||
|
:KUBE-RESTORED - [0:0]
|
||||||
|
:KUBE-MISC-CHAIN - [0:0]
|
||||||
|
:KUBE-EMPTY - [0:0]
|
||||||
|
-A KUBE-TEST -j ACCEPT
|
||||||
|
-A KUBE-RESTORED -m comment --comment "restored chain" -j ACCEPT
|
||||||
|
-A KUBE-MISC-CHAIN -s 1.2.3.4 -j DROP
|
||||||
|
-A KUBE-MISC-CHAIN -d 5.6.7.8 -j MASQUERADE
|
||||||
|
COMMIT
|
||||||
|
*filter
|
||||||
|
:INPUT - [0:0]
|
||||||
|
:FORWARD - [0:0]
|
||||||
|
:OUTPUT - [0:0]
|
||||||
|
COMMIT
|
||||||
|
*mangle
|
||||||
|
COMMIT
|
||||||
|
`, "\n"))
|
||||||
|
if string(buf.Bytes()) != expected {
|
||||||
|
t.Fatalf("bad post-restore dump. expected:\n%s\n\ngot:\n%s\n", expected, buf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// more Restore; empty out one chain and delete another, but also update its counters
|
||||||
|
rules = dedent.Dedent(strings.Trim(`
|
||||||
|
*nat
|
||||||
|
:KUBE-RESTORED - [0:0]
|
||||||
|
:KUBE-TEST - [99:9999]
|
||||||
|
-X KUBE-RESTORED
|
||||||
|
COMMIT
|
||||||
|
`, "\n"))
|
||||||
|
err = fake.Restore(iptables.TableNAT, []byte(rules), iptables.NoFlushTables, iptables.RestoreCounters)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error from Restore: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
err = fake.SaveInto("", buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error from SaveInto: %v", err)
|
||||||
|
}
|
||||||
|
expected = dedent.Dedent(strings.Trim(`
|
||||||
|
*nat
|
||||||
|
:PREROUTING - [0:0]
|
||||||
|
:INPUT - [0:0]
|
||||||
|
:OUTPUT - [0:0]
|
||||||
|
:POSTROUTING - [0:0]
|
||||||
|
:KUBE-TEST - [99:9999]
|
||||||
|
:KUBE-MISC-CHAIN - [0:0]
|
||||||
|
:KUBE-EMPTY - [0:0]
|
||||||
|
-A KUBE-MISC-CHAIN -s 1.2.3.4 -j DROP
|
||||||
|
-A KUBE-MISC-CHAIN -d 5.6.7.8 -j MASQUERADE
|
||||||
|
COMMIT
|
||||||
|
*filter
|
||||||
|
:INPUT - [0:0]
|
||||||
|
:FORWARD - [0:0]
|
||||||
|
:OUTPUT - [0:0]
|
||||||
|
COMMIT
|
||||||
|
*mangle
|
||||||
|
COMMIT
|
||||||
|
`, "\n"))
|
||||||
|
if string(buf.Bytes()) != expected {
|
||||||
|
t.Fatalf("bad post-second-restore dump. expected:\n%s\n\ngot:\n%s\n", expected, buf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestoreAll, FlushTables
|
||||||
|
rules = dedent.Dedent(strings.Trim(`
|
||||||
|
*filter
|
||||||
|
:INPUT - [0:0]
|
||||||
|
:FORWARD - [0:0]
|
||||||
|
:OUTPUT - [0:0]
|
||||||
|
:KUBE-TEST - [0:0]
|
||||||
|
-A KUBE-TEST -m comment --comment "filter table KUBE-TEST" -j ACCEPT
|
||||||
|
COMMIT
|
||||||
|
*nat
|
||||||
|
:PREROUTING - [0:0]
|
||||||
|
:INPUT - [0:0]
|
||||||
|
:OUTPUT - [0:0]
|
||||||
|
:POSTROUTING - [0:0]
|
||||||
|
:KUBE-TEST - [88:8888]
|
||||||
|
:KUBE-NEW-CHAIN - [0:0]
|
||||||
|
-A KUBE-NEW-CHAIN -d 172.30.0.1 -j DNAT --to-destination 10.0.0.1
|
||||||
|
-A KUBE-NEW-CHAIN -d 172.30.0.2 -j DNAT --to-destination 10.0.0.2
|
||||||
|
-A KUBE-NEW-CHAIN -d 172.30.0.3 -j DNAT --to-destination 10.0.0.3
|
||||||
|
COMMIT
|
||||||
|
`, "\n"))
|
||||||
|
err = fake.RestoreAll([]byte(rules), iptables.FlushTables, iptables.NoRestoreCounters)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error from RestoreAll: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
err = fake.SaveInto("", buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error from SaveInto: %v", err)
|
||||||
|
}
|
||||||
|
expected = dedent.Dedent(strings.Trim(`
|
||||||
|
*nat
|
||||||
|
:PREROUTING - [0:0]
|
||||||
|
:INPUT - [0:0]
|
||||||
|
:OUTPUT - [0:0]
|
||||||
|
:POSTROUTING - [0:0]
|
||||||
|
:KUBE-TEST - [88:8888]
|
||||||
|
:KUBE-NEW-CHAIN - [0:0]
|
||||||
|
-A KUBE-NEW-CHAIN -d 172.30.0.1 -j DNAT --to-destination 10.0.0.1
|
||||||
|
-A KUBE-NEW-CHAIN -d 172.30.0.2 -j DNAT --to-destination 10.0.0.2
|
||||||
|
-A KUBE-NEW-CHAIN -d 172.30.0.3 -j DNAT --to-destination 10.0.0.3
|
||||||
|
COMMIT
|
||||||
|
*filter
|
||||||
|
:INPUT - [0:0]
|
||||||
|
:FORWARD - [0:0]
|
||||||
|
:OUTPUT - [0:0]
|
||||||
|
:KUBE-TEST - [0:0]
|
||||||
|
-A KUBE-TEST -m comment --comment "filter table KUBE-TEST" -j ACCEPT
|
||||||
|
COMMIT
|
||||||
|
*mangle
|
||||||
|
COMMIT
|
||||||
|
`, "\n"))
|
||||||
|
if string(buf.Bytes()) != expected {
|
||||||
|
t.Fatalf("bad post-restore-all dump. expected:\n%s\n\ngot:\n%s\n", expected, buf.Bytes())
|
||||||
|
}
|
||||||
|
}
|
375
pkg/util/iptables/testing/parse.go
Normal file
375
pkg/util/iptables/testing/parse.go
Normal file
@ -0,0 +1,375 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 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 testing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/util/iptables"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IPTablesDump represents a parsed IPTables rules dump (ie, the output of
|
||||||
|
// "iptables-save" or input to "iptables-restore")
|
||||||
|
type IPTablesDump struct {
|
||||||
|
Tables []Table
|
||||||
|
}
|
||||||
|
|
||||||
|
// Table represents an IPTables table
|
||||||
|
type Table struct {
|
||||||
|
Name iptables.Table
|
||||||
|
Chains []Chain
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chain represents an IPTables chain
|
||||||
|
type Chain struct {
|
||||||
|
Name iptables.Chain
|
||||||
|
Packets uint64
|
||||||
|
Bytes uint64
|
||||||
|
Rules []*Rule
|
||||||
|
|
||||||
|
// Deleted is set if the input contained a "-X Name" line; this would never
|
||||||
|
// appear in iptables-save output but it could appear in iptables-restore *input*.
|
||||||
|
Deleted bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var declareTableRegex = regexp.MustCompile(`^\*(.*)$`)
|
||||||
|
var declareChainRegex = regexp.MustCompile(`^:([^ ]*) - \[([0-9]*):([0-9]*)\]$`)
|
||||||
|
var addRuleRegex = regexp.MustCompile(`^-A ([^ ]*) (.*)$`)
|
||||||
|
var deleteChainRegex = regexp.MustCompile(`^-X (.*)$`)
|
||||||
|
|
||||||
|
type parseState int
|
||||||
|
|
||||||
|
const (
|
||||||
|
parseTableDeclaration parseState = iota
|
||||||
|
parseChainDeclarations
|
||||||
|
parseChains
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseIPTablesDump parses an IPTables rules dump. Note: this may ignore some bad data.
|
||||||
|
func ParseIPTablesDump(data string) (*IPTablesDump, error) {
|
||||||
|
dump := &IPTablesDump{}
|
||||||
|
state := parseTableDeclaration
|
||||||
|
lines := strings.Split(strings.Trim(data, "\n"), "\n")
|
||||||
|
var t *Table
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
retry:
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line == "" || line[0] == '#' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch state {
|
||||||
|
case parseTableDeclaration:
|
||||||
|
// Parse table declaration line ("*filter").
|
||||||
|
match := declareTableRegex.FindStringSubmatch(line)
|
||||||
|
if match == nil {
|
||||||
|
return nil, fmt.Errorf("could not parse iptables data (table %d starts with %q not a table name)", len(dump.Tables)+1, line)
|
||||||
|
}
|
||||||
|
dump.Tables = append(dump.Tables, Table{Name: iptables.Table(match[1])})
|
||||||
|
t = &dump.Tables[len(dump.Tables)-1]
|
||||||
|
state = parseChainDeclarations
|
||||||
|
|
||||||
|
case parseChainDeclarations:
|
||||||
|
match := declareChainRegex.FindStringSubmatch(line)
|
||||||
|
if match == nil {
|
||||||
|
state = parseChains
|
||||||
|
goto retry
|
||||||
|
}
|
||||||
|
|
||||||
|
chain := iptables.Chain(match[1])
|
||||||
|
packets, _ := strconv.ParseUint(match[2], 10, 64)
|
||||||
|
bytes, _ := strconv.ParseUint(match[3], 10, 64)
|
||||||
|
|
||||||
|
t.Chains = append(t.Chains,
|
||||||
|
Chain{
|
||||||
|
Name: chain,
|
||||||
|
Packets: packets,
|
||||||
|
Bytes: bytes,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
case parseChains:
|
||||||
|
if match := addRuleRegex.FindStringSubmatch(line); match != nil {
|
||||||
|
chain := iptables.Chain(match[1])
|
||||||
|
|
||||||
|
c, err := dump.GetChain(t.Name, chain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing rule %q: %v", line, err)
|
||||||
|
}
|
||||||
|
if c.Deleted {
|
||||||
|
return nil, fmt.Errorf("cannot add rules to deleted chain %q", chain)
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := ParseRule(line, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.Rules = append(c.Rules, rule)
|
||||||
|
} else if match := deleteChainRegex.FindStringSubmatch(line); match != nil {
|
||||||
|
chain := iptables.Chain(match[1])
|
||||||
|
|
||||||
|
c, err := dump.GetChain(t.Name, chain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing rule %q: %v", line, err)
|
||||||
|
}
|
||||||
|
if len(c.Rules) != 0 {
|
||||||
|
return nil, fmt.Errorf("cannot delete chain %q after adding rules", chain)
|
||||||
|
}
|
||||||
|
c.Deleted = true
|
||||||
|
} else if line == "COMMIT" {
|
||||||
|
state = parseTableDeclaration
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("error parsing rule %q", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if state != parseTableDeclaration {
|
||||||
|
return nil, fmt.Errorf("could not parse iptables data (no COMMIT line?)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return dump, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dump *IPTablesDump) String() string {
|
||||||
|
buffer := &strings.Builder{}
|
||||||
|
for _, t := range dump.Tables {
|
||||||
|
fmt.Fprintf(buffer, "*%s\n", t.Name)
|
||||||
|
for _, c := range t.Chains {
|
||||||
|
fmt.Fprintf(buffer, ":%s - [%d:%d]\n", c.Name, c.Packets, c.Bytes)
|
||||||
|
}
|
||||||
|
for _, c := range t.Chains {
|
||||||
|
for _, r := range c.Rules {
|
||||||
|
fmt.Fprintf(buffer, "%s\n", r.Raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, c := range t.Chains {
|
||||||
|
if c.Deleted {
|
||||||
|
fmt.Fprintf(buffer, "-X %s\n", c.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(buffer, "COMMIT\n")
|
||||||
|
}
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dump *IPTablesDump) GetTable(table iptables.Table) (*Table, error) {
|
||||||
|
for i := range dump.Tables {
|
||||||
|
if dump.Tables[i].Name == table {
|
||||||
|
return &dump.Tables[i], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no such table %q", table)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dump *IPTablesDump) GetChain(table iptables.Table, chain iptables.Chain) (*Chain, error) {
|
||||||
|
t, err := dump.GetTable(table)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i := range t.Chains {
|
||||||
|
if t.Chains[i].Name == chain {
|
||||||
|
return &t.Chains[i], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no such chain %q", chain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule represents a single parsed IPTables rule. (This currently covers all of the rule
|
||||||
|
// types that we actually use in pkg/proxy/iptables or pkg/proxy/ipvs.)
|
||||||
|
//
|
||||||
|
// The parsing is mostly-automated based on type reflection. The `param` tag on a field
|
||||||
|
// indicates the parameter whose value will be placed into that field. (The code assumes
|
||||||
|
// that we don't use both the short and long forms of any parameter names (eg, "-s" vs
|
||||||
|
// "--source"), which is currently true, but it could be extended if necessary.) The
|
||||||
|
// `negatable` tag indicates if a parameter is allowed to be preceded by "!".
|
||||||
|
//
|
||||||
|
// Parameters that take a value are stored as type `*IPTablesValue`, which encapsulates a
|
||||||
|
// string value and whether the rule was negated (ie, whether the rule requires that we
|
||||||
|
// *match* or *don't match* that value). But string-valued parameters that can't be
|
||||||
|
// negated use `IPTablesValue` rather than `string` too, just for API consistency.
|
||||||
|
//
|
||||||
|
// Parameters that don't take a value are stored as `*bool`, where the value is `nil` if
|
||||||
|
// the parameter was not present, `&true` if the parameter was present, or `&false` if the
|
||||||
|
// parameter was present but negated.
|
||||||
|
//
|
||||||
|
// Parsing skips over "-m MODULE" parameters because most parameters have unique names
|
||||||
|
// anyway even ignoring the module name, and in the cases where they don't (eg "-m tcp
|
||||||
|
// --sport" vs "-m udp --sport") the parameters are mutually-exclusive and it's more
|
||||||
|
// convenient to store them in the same struct field anyway.
|
||||||
|
type Rule struct {
|
||||||
|
// Raw contains the original raw rule string
|
||||||
|
Raw string
|
||||||
|
|
||||||
|
Chain iptables.Chain `param:"-A"`
|
||||||
|
Comment *IPTablesValue `param:"--comment"`
|
||||||
|
|
||||||
|
Protocol *IPTablesValue `param:"-p" negatable:"true"`
|
||||||
|
|
||||||
|
SourceAddress *IPTablesValue `param:"-s" negatable:"true"`
|
||||||
|
SourceType *IPTablesValue `param:"--src-type" negatable:"true"`
|
||||||
|
SourcePort *IPTablesValue `param:"--sport" negatable:"true"`
|
||||||
|
|
||||||
|
DestinationAddress *IPTablesValue `param:"-d" negatable:"true"`
|
||||||
|
DestinationType *IPTablesValue `param:"--dst-type" negatable:"true"`
|
||||||
|
DestinationPort *IPTablesValue `param:"--dport" negatable:"true"`
|
||||||
|
|
||||||
|
MatchSet *IPTablesValue `param:"--match-set" negatable:"true"`
|
||||||
|
|
||||||
|
Jump *IPTablesValue `param:"-j"`
|
||||||
|
RandomFully *bool `param:"--random-fully"`
|
||||||
|
Probability *IPTablesValue `param:"--probability"`
|
||||||
|
DNATDestination *IPTablesValue `param:"--to-destination"`
|
||||||
|
|
||||||
|
// We don't actually use the values of these, but we care if they are present
|
||||||
|
AffinityCheck *bool `param:"--rcheck" negatable:"true"`
|
||||||
|
MarkCheck *IPTablesValue `param:"--mark" negatable:"true"`
|
||||||
|
CTStateCheck *IPTablesValue `param:"--ctstate" negatable:"true"`
|
||||||
|
|
||||||
|
// We don't currently care about any of these in the unit tests, but we expect
|
||||||
|
// them to be present in some rules that we parse, so we define how to parse them.
|
||||||
|
AffinityName *IPTablesValue `param:"--name"`
|
||||||
|
AffinitySeconds *IPTablesValue `param:"--seconds"`
|
||||||
|
AffinitySet *bool `param:"--set" negatable:"true"`
|
||||||
|
AffinityReap *bool `param:"--reap"`
|
||||||
|
StatisticMode *IPTablesValue `param:"--mode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPTablesValue is a value of a parameter in an Rule, where the parameter is
|
||||||
|
// possibly negated.
|
||||||
|
type IPTablesValue struct {
|
||||||
|
Negated bool
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
// for debugging; otherwise %v will just print the pointer value
|
||||||
|
func (v *IPTablesValue) String() string {
|
||||||
|
if v.Negated {
|
||||||
|
return fmt.Sprintf("NOT %q", v.Value)
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("%q", v.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches returns true if cmp equals / doesn't equal v.Value (depending on
|
||||||
|
// v.Negated).
|
||||||
|
func (v *IPTablesValue) Matches(cmp string) bool {
|
||||||
|
if v.Negated {
|
||||||
|
return v.Value != cmp
|
||||||
|
} else {
|
||||||
|
return v.Value == cmp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// findParamField finds a field in value with the struct tag "param:${param}" and if found,
|
||||||
|
// returns a pointer to the Value of that field, and the value of its "negatable" tag.
|
||||||
|
func findParamField(value reflect.Value, param string) (*reflect.Value, bool) {
|
||||||
|
typ := value.Type()
|
||||||
|
for i := 0; i < typ.NumField(); i++ {
|
||||||
|
field := typ.Field(i)
|
||||||
|
if field.Tag.Get("param") == param {
|
||||||
|
fValue := value.Field(i)
|
||||||
|
return &fValue, field.Tag.Get("negatable") == "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// wordRegex matches a single word or a quoted string (at the start of the string, or
|
||||||
|
// preceded by whitespace)
|
||||||
|
var wordRegex = regexp.MustCompile(`(?:^|\s)("[^"]*"|[^"]\S*)`)
|
||||||
|
|
||||||
|
// Used by ParseRule
|
||||||
|
var boolPtrType = reflect.PtrTo(reflect.TypeOf(true))
|
||||||
|
var ipTablesValuePtrType = reflect.TypeOf((*IPTablesValue)(nil))
|
||||||
|
|
||||||
|
// ParseRule parses rule. If strict is false, it will parse the recognized
|
||||||
|
// parameters and ignore unrecognized ones. If it is true, parsing will fail if there are
|
||||||
|
// unrecognized parameters.
|
||||||
|
func ParseRule(rule string, strict bool) (*Rule, error) {
|
||||||
|
parsed := &Rule{Raw: rule}
|
||||||
|
|
||||||
|
// Split rule into "words" (where a quoted string is a single word).
|
||||||
|
var words []string
|
||||||
|
for _, match := range wordRegex.FindAllStringSubmatch(rule, -1) {
|
||||||
|
words = append(words, strings.Trim(match[1], `"`))
|
||||||
|
}
|
||||||
|
|
||||||
|
// The chain name must come first (and can't be the only thing there)
|
||||||
|
if len(words) < 2 || words[0] != "-A" {
|
||||||
|
return nil, fmt.Errorf(`bad iptables rule (does not start with "-A CHAIN")`)
|
||||||
|
} else if len(words) < 3 {
|
||||||
|
return nil, fmt.Errorf("bad iptables rule (no match rules)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each word, see if it is a known iptables parameter, based on the struct
|
||||||
|
// field tags in Rule. Note that in the non-strict case we implicitly assume that
|
||||||
|
// no unrecognized parameter will take an argument that could be mistaken for
|
||||||
|
// another parameter.
|
||||||
|
v := reflect.ValueOf(parsed).Elem()
|
||||||
|
negated := false
|
||||||
|
for w := 0; w < len(words); {
|
||||||
|
if words[w] == "-m" && w < len(words)-1 {
|
||||||
|
// Skip "-m MODULE"; we don't pay attention to that since the
|
||||||
|
// parameter names are unique enough anyway.
|
||||||
|
w += 2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if words[w] == "!" {
|
||||||
|
negated = true
|
||||||
|
w++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For everything else, see if it corresponds to a field of Rule
|
||||||
|
if field, negatable := findParamField(v, words[w]); field != nil {
|
||||||
|
if negated && !negatable {
|
||||||
|
return nil, fmt.Errorf("cannot negate parameter %q", words[w])
|
||||||
|
}
|
||||||
|
if field.Type() != boolPtrType && w == len(words)-1 {
|
||||||
|
return nil, fmt.Errorf("parameter %q requires an argument", words[w])
|
||||||
|
}
|
||||||
|
switch field.Type() {
|
||||||
|
case boolPtrType:
|
||||||
|
boolVal := !negated
|
||||||
|
field.Set(reflect.ValueOf(&boolVal))
|
||||||
|
w++
|
||||||
|
case ipTablesValuePtrType:
|
||||||
|
field.Set(reflect.ValueOf(&IPTablesValue{Negated: negated, Value: words[w+1]}))
|
||||||
|
w += 2
|
||||||
|
default:
|
||||||
|
field.SetString(words[w+1])
|
||||||
|
w += 2
|
||||||
|
}
|
||||||
|
} else if strict {
|
||||||
|
return nil, fmt.Errorf("unrecognized parameter %q", words[w])
|
||||||
|
} else {
|
||||||
|
// skip
|
||||||
|
w++
|
||||||
|
}
|
||||||
|
|
||||||
|
negated = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed, nil
|
||||||
|
}
|
595
pkg/util/iptables/testing/parse_test.go
Normal file
595
pkg/util/iptables/testing/parse_test.go
Normal file
@ -0,0 +1,595 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 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 testing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/lithammer/dedent"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/util/iptables"
|
||||||
|
utilpointer "k8s.io/utils/pointer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseRule(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
rule string
|
||||||
|
parsed *Rule
|
||||||
|
nonStrict bool
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic rule",
|
||||||
|
rule: `-A KUBE-NODEPORTS -m comment --comment "ns2/svc2:p80 health check node port" -m tcp -p tcp --dport 30000 -j ACCEPT`,
|
||||||
|
parsed: &Rule{
|
||||||
|
Raw: `-A KUBE-NODEPORTS -m comment --comment "ns2/svc2:p80 health check node port" -m tcp -p tcp --dport 30000 -j ACCEPT`,
|
||||||
|
Chain: iptables.Chain("KUBE-NODEPORTS"),
|
||||||
|
Comment: &IPTablesValue{Value: "ns2/svc2:p80 health check node port"},
|
||||||
|
Protocol: &IPTablesValue{Value: "tcp"},
|
||||||
|
DestinationPort: &IPTablesValue{Value: "30000"},
|
||||||
|
Jump: &IPTablesValue{Value: "ACCEPT"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "addRuleToChainRegex requires an actual rule, not just a chain name",
|
||||||
|
rule: `-A KUBE-NODEPORTS`,
|
||||||
|
err: `(no match rules)`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ParseRule only parses adds",
|
||||||
|
rule: `-D KUBE-NODEPORTS -m comment --comment "ns2/svc2:p80 health check node port" -m tcp -p tcp --dport 30000 -j ACCEPT`,
|
||||||
|
err: `(does not start with "-A CHAIN")`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unquoted comment",
|
||||||
|
rule: `-A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment ns1/svc1:p80 -j KUBE-SEP-SXIVWICOYRO3J4NJ`,
|
||||||
|
parsed: &Rule{
|
||||||
|
Raw: `-A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment ns1/svc1:p80 -j KUBE-SEP-SXIVWICOYRO3J4NJ`,
|
||||||
|
Chain: iptables.Chain("KUBE-SVC-XPGD46QRK7WJZT7O"),
|
||||||
|
Comment: &IPTablesValue{Value: "ns1/svc1:p80"},
|
||||||
|
Jump: &IPTablesValue{Value: "KUBE-SEP-SXIVWICOYRO3J4NJ"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "local source",
|
||||||
|
rule: `-A KUBE-XLB-GNZBNJ2PO5MGZ6GT -m comment --comment "masquerade LOCAL traffic for ns2/svc2:p80 LB IP" -m addrtype --src-type LOCAL -j KUBE-MARK-MASQ`,
|
||||||
|
parsed: &Rule{
|
||||||
|
Raw: `-A KUBE-XLB-GNZBNJ2PO5MGZ6GT -m comment --comment "masquerade LOCAL traffic for ns2/svc2:p80 LB IP" -m addrtype --src-type LOCAL -j KUBE-MARK-MASQ`,
|
||||||
|
Chain: iptables.Chain("KUBE-XLB-GNZBNJ2PO5MGZ6GT"),
|
||||||
|
Comment: &IPTablesValue{Value: "masquerade LOCAL traffic for ns2/svc2:p80 LB IP"},
|
||||||
|
SourceType: &IPTablesValue{Value: "LOCAL"},
|
||||||
|
Jump: &IPTablesValue{Value: "KUBE-MARK-MASQ"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not local destination",
|
||||||
|
rule: `-A RULE-TYPE-NOT-CURRENTLY-USED-BY-KUBE-PROXY -m addrtype ! --dst-type LOCAL -j KUBE-MARK-MASQ`,
|
||||||
|
parsed: &Rule{
|
||||||
|
Raw: `-A RULE-TYPE-NOT-CURRENTLY-USED-BY-KUBE-PROXY -m addrtype ! --dst-type LOCAL -j KUBE-MARK-MASQ`,
|
||||||
|
Chain: iptables.Chain("RULE-TYPE-NOT-CURRENTLY-USED-BY-KUBE-PROXY"),
|
||||||
|
DestinationType: &IPTablesValue{Negated: true, Value: "LOCAL"},
|
||||||
|
Jump: &IPTablesValue{Value: "KUBE-MARK-MASQ"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "destination IP/port",
|
||||||
|
rule: `-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`,
|
||||||
|
parsed: &Rule{
|
||||||
|
Raw: `-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`,
|
||||||
|
Chain: iptables.Chain("KUBE-SERVICES"),
|
||||||
|
Comment: &IPTablesValue{Value: "ns1/svc1:p80 cluster IP"},
|
||||||
|
Protocol: &IPTablesValue{Value: "tcp"},
|
||||||
|
DestinationAddress: &IPTablesValue{Value: "172.30.0.41"},
|
||||||
|
DestinationPort: &IPTablesValue{Value: "80"},
|
||||||
|
Jump: &IPTablesValue{Value: "KUBE-SVC-XPGD46QRK7WJZT7O"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "source IP",
|
||||||
|
rule: `-A KUBE-SEP-SXIVWICOYRO3J4NJ -m comment --comment ns1/svc1:p80 -s 10.180.0.1 -j KUBE-MARK-MASQ`,
|
||||||
|
parsed: &Rule{
|
||||||
|
Raw: `-A KUBE-SEP-SXIVWICOYRO3J4NJ -m comment --comment ns1/svc1:p80 -s 10.180.0.1 -j KUBE-MARK-MASQ`,
|
||||||
|
Chain: iptables.Chain("KUBE-SEP-SXIVWICOYRO3J4NJ"),
|
||||||
|
Comment: &IPTablesValue{Value: "ns1/svc1:p80"},
|
||||||
|
SourceAddress: &IPTablesValue{Value: "10.180.0.1"},
|
||||||
|
Jump: &IPTablesValue{Value: "KUBE-MARK-MASQ"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not source IP",
|
||||||
|
rule: `-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`,
|
||||||
|
parsed: &Rule{
|
||||||
|
Raw: `-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`,
|
||||||
|
Chain: iptables.Chain("KUBE-SVC-XPGD46QRK7WJZT7O"),
|
||||||
|
Comment: &IPTablesValue{Value: "ns1/svc1:p80 cluster IP"},
|
||||||
|
Protocol: &IPTablesValue{Value: "tcp"},
|
||||||
|
DestinationAddress: &IPTablesValue{Value: "172.30.0.41"},
|
||||||
|
DestinationPort: &IPTablesValue{Value: "80"},
|
||||||
|
SourceAddress: &IPTablesValue{Negated: true, Value: "10.0.0.0/8"},
|
||||||
|
Jump: &IPTablesValue{Value: "KUBE-MARK-MASQ"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "affinity",
|
||||||
|
rule: `-A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment ns1/svc1:p80 -m recent --name KUBE-SEP-SXIVWICOYRO3J4NJ --rcheck --seconds 10800 --reap -j KUBE-SEP-SXIVWICOYRO3J4NJ`,
|
||||||
|
parsed: &Rule{
|
||||||
|
Raw: `-A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment ns1/svc1:p80 -m recent --name KUBE-SEP-SXIVWICOYRO3J4NJ --rcheck --seconds 10800 --reap -j KUBE-SEP-SXIVWICOYRO3J4NJ`,
|
||||||
|
Chain: iptables.Chain("KUBE-SVC-XPGD46QRK7WJZT7O"),
|
||||||
|
Comment: &IPTablesValue{Value: "ns1/svc1:p80"},
|
||||||
|
AffinityName: &IPTablesValue{Value: "KUBE-SEP-SXIVWICOYRO3J4NJ"},
|
||||||
|
AffinitySeconds: &IPTablesValue{Value: "10800"},
|
||||||
|
AffinityCheck: utilpointer.Bool(true),
|
||||||
|
AffinityReap: utilpointer.Bool(true),
|
||||||
|
Jump: &IPTablesValue{Value: "KUBE-SEP-SXIVWICOYRO3J4NJ"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "jump to DNAT",
|
||||||
|
rule: `-A KUBE-SEP-SXIVWICOYRO3J4NJ -m comment --comment ns1/svc1:p80 -m tcp -p tcp -j DNAT --to-destination 10.180.0.1:80`,
|
||||||
|
parsed: &Rule{
|
||||||
|
Raw: `-A KUBE-SEP-SXIVWICOYRO3J4NJ -m comment --comment ns1/svc1:p80 -m tcp -p tcp -j DNAT --to-destination 10.180.0.1:80`,
|
||||||
|
Chain: iptables.Chain("KUBE-SEP-SXIVWICOYRO3J4NJ"),
|
||||||
|
Comment: &IPTablesValue{Value: "ns1/svc1:p80"},
|
||||||
|
Protocol: &IPTablesValue{Value: "tcp"},
|
||||||
|
Jump: &IPTablesValue{Value: "DNAT"},
|
||||||
|
DNATDestination: &IPTablesValue{Value: "10.180.0.1:80"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "jump to endpoint",
|
||||||
|
rule: `-A KUBE-SVC-4SW47YFZTEDKD3PK -m comment --comment ns4/svc4:p80 -m statistic --mode random --probability 0.5000000000 -j KUBE-SEP-UKSFD7AGPMPPLUHC`,
|
||||||
|
parsed: &Rule{
|
||||||
|
Raw: `-A KUBE-SVC-4SW47YFZTEDKD3PK -m comment --comment ns4/svc4:p80 -m statistic --mode random --probability 0.5000000000 -j KUBE-SEP-UKSFD7AGPMPPLUHC`,
|
||||||
|
Chain: iptables.Chain("KUBE-SVC-4SW47YFZTEDKD3PK"),
|
||||||
|
Comment: &IPTablesValue{Value: "ns4/svc4:p80"},
|
||||||
|
Probability: &IPTablesValue{Value: "0.5000000000"},
|
||||||
|
StatisticMode: &IPTablesValue{Value: "random"},
|
||||||
|
Jump: &IPTablesValue{Value: "KUBE-SEP-UKSFD7AGPMPPLUHC"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unrecognized arguments",
|
||||||
|
rule: `-A KUBE-SVC-4SW47YFZTEDKD3PK -m comment --comment ns4/svc4:p80 -i eth0 -j KUBE-SEP-UKSFD7AGPMPPLUHC`,
|
||||||
|
err: `unrecognized parameter "-i"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unrecognized arguments with strict=false",
|
||||||
|
rule: `-A KUBE-SVC-4SW47YFZTEDKD3PK -m comment --comment ns4/svc4:p80 -i eth0 -j KUBE-SEP-UKSFD7AGPMPPLUHC`,
|
||||||
|
nonStrict: true,
|
||||||
|
parsed: &Rule{
|
||||||
|
Raw: `-A KUBE-SVC-4SW47YFZTEDKD3PK -m comment --comment ns4/svc4:p80 -i eth0 -j KUBE-SEP-UKSFD7AGPMPPLUHC`,
|
||||||
|
Chain: iptables.Chain("KUBE-SVC-4SW47YFZTEDKD3PK"),
|
||||||
|
Comment: &IPTablesValue{Value: "ns4/svc4:p80"},
|
||||||
|
Jump: &IPTablesValue{Value: "KUBE-SEP-UKSFD7AGPMPPLUHC"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad use of !",
|
||||||
|
rule: `-A KUBE-SVC-4SW47YFZTEDKD3PK -m comment --comment ns4/svc4:p80 ! -j KUBE-SEP-UKSFD7AGPMPPLUHC`,
|
||||||
|
err: `cannot negate parameter "-j"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing argument",
|
||||||
|
rule: `-A KUBE-SVC-4SW47YFZTEDKD3PK -m comment --comment ns4/svc4:p80 -j`,
|
||||||
|
err: `parameter "-j" requires an argument`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negated bool arg",
|
||||||
|
rule: `-A TEST -m recent ! --rcheck -j KUBE-SEP-SXIVWICOYRO3J4NJ`,
|
||||||
|
parsed: &Rule{
|
||||||
|
Raw: `-A TEST -m recent ! --rcheck -j KUBE-SEP-SXIVWICOYRO3J4NJ`,
|
||||||
|
Chain: iptables.Chain("TEST"),
|
||||||
|
AffinityCheck: utilpointer.Bool(false),
|
||||||
|
Jump: &IPTablesValue{Value: "KUBE-SEP-SXIVWICOYRO3J4NJ"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
rule, err := ParseRule(testCase.rule, !testCase.nonStrict)
|
||||||
|
if err != nil {
|
||||||
|
if testCase.err == "" {
|
||||||
|
t.Errorf("expected %+v, got error %q", testCase.parsed, err)
|
||||||
|
} else if !strings.Contains(err.Error(), testCase.err) {
|
||||||
|
t.Errorf("wrong error, expected %q got %q", testCase.err, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if testCase.err != "" {
|
||||||
|
t.Errorf("expected error %q, got %+v", testCase.err, rule)
|
||||||
|
} else if !reflect.DeepEqual(rule, testCase.parsed) {
|
||||||
|
t.Errorf("bad match: expected\n%+v\ngot\n%+v", testCase.parsed, rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper for TestParseIPTablesDump. Obviously it should not be used in TestParseRule...
|
||||||
|
func mustParseRule(rule string) *Rule {
|
||||||
|
parsed, err := ParseRule(rule, false)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to parse test case rule %q: %v", rule, err))
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseIPTablesDump(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
output *IPTablesDump
|
||||||
|
error string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic test",
|
||||||
|
input: dedent.Dedent(`
|
||||||
|
*filter
|
||||||
|
:KUBE-SERVICES - [0:0]
|
||||||
|
:KUBE-EXTERNAL-SERVICES - [0:0]
|
||||||
|
:KUBE-FORWARD - [0:0]
|
||||||
|
:KUBE-NODEPORTS - [0:0]
|
||||||
|
-A KUBE-NODEPORTS -m comment --comment "ns2/svc2:p80 health check node port" -m tcp -p tcp --dport 30000 -j ACCEPT
|
||||||
|
-A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP
|
||||||
|
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT
|
||||||
|
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
COMMIT
|
||||||
|
*nat
|
||||||
|
:KUBE-SERVICES - [0:0]
|
||||||
|
:KUBE-NODEPORTS - [0:0]
|
||||||
|
:KUBE-POSTROUTING - [0:0]
|
||||||
|
:KUBE-MARK-MASQ - [0:0]
|
||||||
|
:KUBE-SVC-XPGD46QRK7WJZT7O - [0:0]
|
||||||
|
: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-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
|
||||||
|
-A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment ns1/svc1:p80 -j KUBE-SEP-SXIVWICOYRO3J4NJ
|
||||||
|
-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-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
|
||||||
|
COMMIT
|
||||||
|
`),
|
||||||
|
output: &IPTablesDump{
|
||||||
|
Tables: []Table{{
|
||||||
|
Name: iptables.TableFilter,
|
||||||
|
Chains: []Chain{{
|
||||||
|
Name: iptables.Chain("KUBE-SERVICES"),
|
||||||
|
}, {
|
||||||
|
Name: iptables.Chain("KUBE-EXTERNAL-SERVICES"),
|
||||||
|
}, {
|
||||||
|
Name: iptables.Chain("KUBE-FORWARD"),
|
||||||
|
Rules: []*Rule{
|
||||||
|
mustParseRule(`-A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP`),
|
||||||
|
mustParseRule(`-A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT`),
|
||||||
|
mustParseRule(`-A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT`),
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Name: iptables.Chain("KUBE-NODEPORTS"),
|
||||||
|
Rules: []*Rule{
|
||||||
|
mustParseRule(`-A KUBE-NODEPORTS -m comment --comment "ns2/svc2:p80 health check node port" -m tcp -p tcp --dport 30000 -j ACCEPT`),
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}, {
|
||||||
|
Name: iptables.TableNAT,
|
||||||
|
Chains: []Chain{{
|
||||||
|
Name: iptables.Chain("KUBE-SERVICES"),
|
||||||
|
Rules: []*Rule{
|
||||||
|
mustParseRule(`-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`),
|
||||||
|
mustParseRule(`-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS`),
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Name: iptables.Chain("KUBE-NODEPORTS"),
|
||||||
|
}, {
|
||||||
|
Name: iptables.Chain("KUBE-POSTROUTING"),
|
||||||
|
Rules: []*Rule{
|
||||||
|
mustParseRule(`-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN`),
|
||||||
|
mustParseRule(`-A KUBE-POSTROUTING -j MARK --xor-mark 0x4000`),
|
||||||
|
mustParseRule(`-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE`),
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Name: iptables.Chain("KUBE-MARK-MASQ"),
|
||||||
|
Rules: []*Rule{
|
||||||
|
mustParseRule(`-A KUBE-MARK-MASQ -j MARK --or-mark 0x4000`),
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Name: iptables.Chain("KUBE-SVC-XPGD46QRK7WJZT7O"),
|
||||||
|
Rules: []*Rule{
|
||||||
|
mustParseRule(`-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`),
|
||||||
|
mustParseRule(`-A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment ns1/svc1:p80 -j KUBE-SEP-SXIVWICOYRO3J4NJ`),
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Name: iptables.Chain("KUBE-SEP-SXIVWICOYRO3J4NJ"),
|
||||||
|
Rules: []*Rule{
|
||||||
|
mustParseRule(`-A KUBE-SEP-SXIVWICOYRO3J4NJ -m comment --comment ns1/svc1:p80 -s 10.180.0.1 -j KUBE-MARK-MASQ`),
|
||||||
|
mustParseRule(`-A KUBE-SEP-SXIVWICOYRO3J4NJ -m comment --comment ns1/svc1:p80 -m tcp -p tcp -j DNAT --to-destination 10.180.0.1:80`),
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deletion",
|
||||||
|
input: dedent.Dedent(`
|
||||||
|
*nat
|
||||||
|
:KUBE-SERVICES - [0:0]
|
||||||
|
:KUBE-SVC-XPGD46QRK7WJZT7O - [0:0]
|
||||||
|
:KUBE-SEP-SXIVWICOYRO3J4NJ - [0:0]
|
||||||
|
-X KUBE-SVC-XPGD46QRK7WJZT7O
|
||||||
|
-X KUBE-SEP-SXIVWICOYRO3J4NJ
|
||||||
|
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
|
||||||
|
COMMIT
|
||||||
|
`),
|
||||||
|
output: &IPTablesDump{
|
||||||
|
Tables: []Table{{
|
||||||
|
Name: iptables.TableNAT,
|
||||||
|
Chains: []Chain{{
|
||||||
|
Name: iptables.Chain("KUBE-SERVICES"),
|
||||||
|
Rules: []*Rule{
|
||||||
|
mustParseRule(`-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS`),
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Name: iptables.Chain("KUBE-SVC-XPGD46QRK7WJZT7O"),
|
||||||
|
Deleted: true,
|
||||||
|
}, {
|
||||||
|
Name: iptables.Chain("KUBE-SEP-SXIVWICOYRO3J4NJ"),
|
||||||
|
Deleted: true,
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "whitespace and comments",
|
||||||
|
input: dedent.Dedent(`
|
||||||
|
# Generated by iptables-save v1.8.7 on Mon May 9 11:22:21 2022
|
||||||
|
# (not really...)
|
||||||
|
*filter
|
||||||
|
:KUBE-SERVICES - [0:0]
|
||||||
|
:KUBE-EXTERNAL-SERVICES - [0:0]
|
||||||
|
|
||||||
|
:KUBE-FORWARD - [0:0]
|
||||||
|
:KUBE-NODEPORTS - [0:0]
|
||||||
|
-A KUBE-NODEPORTS -m comment --comment "ns2/svc2:p80 health check node port" -m tcp -p tcp --dport 30000 -j ACCEPT
|
||||||
|
-A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP
|
||||||
|
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT
|
||||||
|
# This rule does a thing
|
||||||
|
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
COMMIT
|
||||||
|
# Completed on Mon May 9 11:22:21 2022
|
||||||
|
`),
|
||||||
|
output: &IPTablesDump{
|
||||||
|
Tables: []Table{{
|
||||||
|
Name: iptables.TableFilter,
|
||||||
|
Chains: []Chain{{
|
||||||
|
Name: iptables.Chain("KUBE-SERVICES"),
|
||||||
|
}, {
|
||||||
|
Name: iptables.Chain("KUBE-EXTERNAL-SERVICES"),
|
||||||
|
}, {
|
||||||
|
Name: iptables.Chain("KUBE-FORWARD"),
|
||||||
|
Rules: []*Rule{
|
||||||
|
mustParseRule(`-A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP`),
|
||||||
|
mustParseRule(`-A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT`),
|
||||||
|
mustParseRule(`-A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT`),
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Name: iptables.Chain("KUBE-NODEPORTS"),
|
||||||
|
Rules: []*Rule{
|
||||||
|
mustParseRule(`-A KUBE-NODEPORTS -m comment --comment "ns2/svc2:p80 health check node port" -m tcp -p tcp --dport 30000 -j ACCEPT`),
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no COMMIT line",
|
||||||
|
input: dedent.Dedent(`
|
||||||
|
*filter
|
||||||
|
:KUBE-SERVICES - [0:0]
|
||||||
|
:KUBE-EXTERNAL-SERVICES - [0:0]
|
||||||
|
:KUBE-FORWARD - [0:0]
|
||||||
|
:KUBE-NODEPORTS - [0:0]
|
||||||
|
-A KUBE-NODEPORTS -m comment --comment "ns2/svc2:p80 health check node port" -m tcp -p tcp --dport 30000 -j ACCEPT
|
||||||
|
-A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP
|
||||||
|
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT
|
||||||
|
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
`),
|
||||||
|
error: "no COMMIT line?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "two tables, no second COMMIT line",
|
||||||
|
input: dedent.Dedent(`
|
||||||
|
*filter
|
||||||
|
:KUBE-SERVICES - [0:0]
|
||||||
|
:KUBE-EXTERNAL-SERVICES - [0:0]
|
||||||
|
:KUBE-FORWARD - [0:0]
|
||||||
|
:KUBE-NODEPORTS - [0:0]
|
||||||
|
-A KUBE-NODEPORTS -m comment --comment "ns2/svc2:p80 health check node port" -m tcp -p tcp --dport 30000 -j ACCEPT
|
||||||
|
-A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP
|
||||||
|
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT
|
||||||
|
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
COMMIT
|
||||||
|
*nat
|
||||||
|
:KUBE-SERVICES - [0:0]
|
||||||
|
:KUBE-NODEPORTS - [0:0]
|
||||||
|
:KUBE-POSTROUTING - [0:0]
|
||||||
|
:KUBE-MARK-MASQ - [0:0]
|
||||||
|
:KUBE-SVC-XPGD46QRK7WJZT7O - [0:0]
|
||||||
|
: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-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
|
||||||
|
-A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment ns1/svc1:p80 -j KUBE-SEP-SXIVWICOYRO3J4NJ
|
||||||
|
-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-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
|
||||||
|
`),
|
||||||
|
error: "no COMMIT line?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "two tables, no second header line",
|
||||||
|
input: dedent.Dedent(`
|
||||||
|
*filter
|
||||||
|
:KUBE-SERVICES - [0:0]
|
||||||
|
:KUBE-EXTERNAL-SERVICES - [0:0]
|
||||||
|
:KUBE-FORWARD - [0:0]
|
||||||
|
:KUBE-NODEPORTS - [0:0]
|
||||||
|
-A KUBE-NODEPORTS -m comment --comment "ns2/svc2:p80 health check node port" -m tcp -p tcp --dport 30000 -j ACCEPT
|
||||||
|
-A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP
|
||||||
|
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT
|
||||||
|
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
COMMIT
|
||||||
|
:KUBE-SERVICES - [0:0]
|
||||||
|
:KUBE-NODEPORTS - [0:0]
|
||||||
|
:KUBE-POSTROUTING - [0:0]
|
||||||
|
:KUBE-MARK-MASQ - [0:0]
|
||||||
|
:KUBE-SVC-XPGD46QRK7WJZT7O - [0:0]
|
||||||
|
: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-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
|
||||||
|
-A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment ns1/svc1:p80 -j KUBE-SEP-SXIVWICOYRO3J4NJ
|
||||||
|
-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-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
|
||||||
|
COMMIT
|
||||||
|
`),
|
||||||
|
error: "not a table name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "trailing junk",
|
||||||
|
input: dedent.Dedent(`
|
||||||
|
*filter
|
||||||
|
:KUBE-SERVICES - [0:0]
|
||||||
|
:KUBE-EXTERNAL-SERVICES - [0:0]
|
||||||
|
:KUBE-FORWARD - [0:0]
|
||||||
|
:KUBE-NODEPORTS - [0:0]
|
||||||
|
-A KUBE-NODEPORTS -m comment --comment "ns2/svc2:p80 health check node port" -m tcp -p tcp --dport 30000 -j ACCEPT
|
||||||
|
-A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP
|
||||||
|
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT
|
||||||
|
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
COMMIT
|
||||||
|
*nat
|
||||||
|
:KUBE-SERVICES - [0:0]
|
||||||
|
:KUBE-EXTERNAL-SERVICES - [0:0]
|
||||||
|
:KUBE-FORWARD - [0:0]
|
||||||
|
:KUBE-NODEPORTS - [0:0]
|
||||||
|
-A KUBE-NODEPORTS -m comment --comment "ns2/svc2:p80 health check node port" -m tcp -p tcp --dport 30000 -j ACCEPT
|
||||||
|
-A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP
|
||||||
|
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT
|
||||||
|
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
COMMIT
|
||||||
|
junk
|
||||||
|
`),
|
||||||
|
error: `table 3 starts with "junk"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "add to missing chain",
|
||||||
|
input: dedent.Dedent(`
|
||||||
|
*filter
|
||||||
|
:KUBE-SERVICES - [0:0]
|
||||||
|
:KUBE-EXTERNAL-SERVICES - [0:0]
|
||||||
|
:KUBE-NODEPORTS - [0:0]
|
||||||
|
-A KUBE-NODEPORTS -m comment --comment "ns2/svc2:p80 health check node port" -m tcp -p tcp --dport 30000 -j ACCEPT
|
||||||
|
-A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP
|
||||||
|
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT
|
||||||
|
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
COMMIT
|
||||||
|
`),
|
||||||
|
error: `no such chain "KUBE-FORWARD"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "add to deleted chain",
|
||||||
|
input: dedent.Dedent(`
|
||||||
|
*filter
|
||||||
|
:KUBE-SERVICES - [0:0]
|
||||||
|
:KUBE-EXTERNAL-SERVICES - [0:0]
|
||||||
|
:KUBE-FORWARD - [0:0]
|
||||||
|
:KUBE-NODEPORTS - [0:0]
|
||||||
|
-A KUBE-NODEPORTS -m comment --comment "ns2/svc2:p80 health check node port" -m tcp -p tcp --dport 30000 -j ACCEPT
|
||||||
|
-X KUBE-FORWARD
|
||||||
|
-A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP
|
||||||
|
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT
|
||||||
|
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
COMMIT
|
||||||
|
`),
|
||||||
|
error: `cannot add rules to deleted chain`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deleted non-empty chain",
|
||||||
|
input: dedent.Dedent(`
|
||||||
|
*filter
|
||||||
|
:KUBE-SERVICES - [0:0]
|
||||||
|
:KUBE-EXTERNAL-SERVICES - [0:0]
|
||||||
|
:KUBE-FORWARD - [0:0]
|
||||||
|
:KUBE-NODEPORTS - [0:0]
|
||||||
|
-A KUBE-NODEPORTS -m comment --comment "ns2/svc2:p80 health check node port" -m tcp -p tcp --dport 30000 -j ACCEPT
|
||||||
|
-A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP
|
||||||
|
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT
|
||||||
|
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
-X KUBE-FORWARD
|
||||||
|
COMMIT
|
||||||
|
`),
|
||||||
|
error: `cannot delete chain "KUBE-FORWARD" after adding rules`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "junk rule",
|
||||||
|
input: dedent.Dedent(`
|
||||||
|
*filter
|
||||||
|
:KUBE-SERVICES - [0:0]
|
||||||
|
:KUBE-EXTERNAL-SERVICES - [0:0]
|
||||||
|
:KUBE-FORWARD - [0:0]
|
||||||
|
:KUBE-NODEPORTS - [0:0]
|
||||||
|
-A KUBE-NODEPORTS -m comment --comment "ns2/svc2:p80 health check node port" -m tcp -p tcp --dport 30000 -j ACCEPT
|
||||||
|
-Q KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP
|
||||||
|
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT
|
||||||
|
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
COMMIT
|
||||||
|
`),
|
||||||
|
error: `"-Q KUBE-FORWARD`,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
dump, err := ParseIPTablesDump(tc.input)
|
||||||
|
if err == nil {
|
||||||
|
if tc.error != "" {
|
||||||
|
t.Errorf("unexpectedly did not get error")
|
||||||
|
} else if !reflect.DeepEqual(tc.output, dump) {
|
||||||
|
t.Errorf("bad output: expected %#v got %#v", tc.output, dump)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if tc.error == "" {
|
||||||
|
t.Errorf("got unexpected error: %v", err)
|
||||||
|
} else if !strings.Contains(err.Error(), tc.error) {
|
||||||
|
t.Errorf("got wrong error: %v (expected %q)", err, tc.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user