From 2f8c43ca2010cdf668b29ed7dd258d552223812a Mon Sep 17 00:00:00 2001 From: Nadia Pinaeva Date: Fri, 26 Jan 2024 17:48:25 +0100 Subject: [PATCH] Add ParseDump function to allow using Fake.Dump() output as a test setup. Signed-off-by: Nadia Pinaeva --- fake.go | 75 ++++++++++++++++++++ fake_test.go | 177 ++++++++++++++++++++++++++++++++++++++++++++++ objects.go | 194 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 446 insertions(+) diff --git a/fake.go b/fake.go index 9b3ca71..f90991c 100644 --- a/fake.go +++ b/fake.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "reflect" + "regexp" "sort" "strings" ) @@ -519,6 +520,80 @@ func (fake *Fake) Dump() string { return buf.String() } +var commentRegexp = regexp.MustCompile(`^(.*)comment "([^"]*)"( ;)?([^"]*)$`) + +// splitComment splits line into a required pre-comment and optional trailing comment +// (which is enclosed in quotes but does not contain any quotes). +func splitComment(line string) (string, *string) { + //fmt.Printf("Split comment !%v!\n", line) + // We could perhaps do this more efficiently without using a regexp, but it would + // be more complicated... + match := commentRegexp.FindStringSubmatch(line) + if match != nil { + return strings.TrimSpace(match[1]) + match[4], &match[2] + } + return line, nil +} + +// ParseDump can parse a dump for a given nft instance. +// It expects table name and the same nftables family in all rules based on a given fake.nftContext. +// The best way to verify that everything important was properly parsed is to +// compare given data with nft.Dump() output. +func (fake *Fake) ParseDump(data string) (err error) { + lines := strings.Split(data, "\n") + var i int + var line string + parsingDone := false + defer func() { + if err != nil && !parsingDone { + err = fmt.Errorf("line %v: %w", i+1, err) + } + }() + txn := fake.NewTransaction() + for i, line = range lines { + line = strings.TrimSpace(line) + if line == "" || line[0] == '#' { + continue + } + words := strings.Split(line, " ") + if len(words) < 4 { + return fmt.Errorf("every command should have at least 4 words") + } + if words[0] != "add" { + return fmt.Errorf("only supports add operation, got %s", words[0]) + } + if words[2] != string(fake.nftContext.family) { + return fmt.Errorf("wrong ip family %s, expected %s", words[2], string(fake.nftContext.family)) + } + if words[3] != fake.nftContext.table { + return fmt.Errorf("wrong table name %s, expected %s", words[3], fake.nftContext.table) + } + var obj Object + switch words[1] { + case "table": + obj, err = parseTable(words) + case "chain": + obj, err = parseChain(words) + case "rule": + obj, err = parseRule(words) + case "map": + obj, err = parseMap(words) + case "set": + obj, err = parseSet(words) + case "element": + obj, err = parseElement(words) + default: + return fmt.Errorf("unknown object %s", words[1]) + } + if err != nil { + return err + } + txn.Add(obj) + } + parsingDone = true + return fake.Run(context.TODO(), txn) +} + func sortKeys[K ~string, V any](m map[K]V) []K { keys := make([]K, 0, len(m)) for key := range m { diff --git a/fake_test.go b/fake_test.go index 9cc5877..85dc9b5 100644 --- a/fake_test.go +++ b/fake_test.go @@ -558,3 +558,180 @@ func TestFakeAddInsertReplace(t *testing.T) { assertRules(t, fake, "thirteenth", "sixth", "twelfth", "fifth", "seventh", "ninth", "eighth", "fourth", "third", "eleventh", "tenth") } + +func TestFakeParseDump(t *testing.T) { + + tc1 := strings.TrimPrefix(dedent.Dedent(` + add table ip kube-proxy + add chain ip kube-proxy anotherchain + add chain ip kube-proxy chain { comment "foo" ; } + add map ip kube-proxy map1 { type ipv4_addr . inet_proto . inet_service ; } + add rule ip kube-proxy anotherchain ip saddr 1.2.3.4 drop comment "drop rule" + add rule ip kube-proxy anotherchain ip daddr 5.6.7.8 reject comment "reject rule" + add rule ip kube-proxy chain ip daddr 10.0.0.0/8 drop + add rule ip kube-proxy chain masquerade comment "comment" + add element ip kube-proxy map1 { 192.168.0.1 . tcp . 80 : drop } + add element ip kube-proxy map1 { 192.168.0.2 . tcp . 443 comment "with a comment" : goto anotherchain } + `), "\n") + + tc2 := dedent.Dedent(` + add table ip kube-proxy { comment "rules for kube-proxy" ; } + + add chain ip kube-proxy mark-for-masquerade + add chain ip kube-proxy masquerading + add chain ip kube-proxy services + add chain ip kube-proxy firewall-check + add chain ip kube-proxy endpoints-check + add chain ip kube-proxy filter-prerouting { type filter hook prerouting priority -110 ; } + add chain ip kube-proxy filter-forward { type filter hook forward priority -110 ; } + add chain ip kube-proxy filter-input { type filter hook input priority -110 ; } + add chain ip kube-proxy filter-output { type filter hook output priority -110 ; } + add chain ip kube-proxy nat-output { type nat hook output priority -100 ; } + add chain ip kube-proxy nat-postrouting { type nat hook postrouting priority 100 ; } + add chain ip kube-proxy nat-prerouting { type nat hook prerouting priority -100 ; } + add chain ip kube-proxy reject-chain { comment "helper for @no-endpoint-services / @no-endpoint-nodeports" ; } + add chain ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 + add chain ip kube-proxy endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 + add chain ip kube-proxy service-42NFTM6N-ns2/svc2/tcp/p80 + add chain ip kube-proxy endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 + add chain ip kube-proxy external-42NFTM6N-ns2/svc2/tcp/p80 + add chain ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 + add chain ip kube-proxy endpoint-UEIP74TE-ns3/svc3/tcp/p80__10.180.0.3/80 + add chain ip kube-proxy external-4AT6LBPK-ns3/svc3/tcp/p80 + add chain ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 + add chain ip kube-proxy endpoint-UNZV3OEC-ns4/svc4/tcp/p80__10.180.0.4/80 + add chain ip kube-proxy endpoint-5RFCDDV7-ns4/svc4/tcp/p80__10.180.0.5/80 + add chain ip kube-proxy external-LAUZTJTB-ns4/svc4/tcp/p80 + add chain ip kube-proxy service-HVFWP5L3-ns5/svc5/tcp/p80 + add chain ip kube-proxy external-HVFWP5L3-ns5/svc5/tcp/p80 + add chain ip kube-proxy endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 + add chain ip kube-proxy firewall-HVFWP5L3-ns5/svc5/tcp/p80 + + add rule ip kube-proxy mark-for-masquerade mark set mark or 0x4000 + add rule ip kube-proxy masquerading mark and 0x4000 == 0 return + add rule ip kube-proxy masquerading mark set mark xor 0x4000 + add rule ip kube-proxy masquerading masquerade fully-random + add rule ip kube-proxy filter-prerouting ct state new jump firewall-check + add rule ip kube-proxy filter-forward ct state new jump endpoints-check + add rule ip kube-proxy filter-input ct state new jump endpoints-check + add rule ip kube-proxy filter-output ct state new jump endpoints-check + add rule ip kube-proxy filter-output ct state new jump firewall-check + add rule ip kube-proxy nat-output jump services + add rule ip kube-proxy nat-postrouting jump masquerading + add rule ip kube-proxy nat-prerouting jump services + + add map ip kube-proxy firewall-ips { type ipv4_addr . inet_proto . inet_service : verdict ; comment "destinations that are subject to LoadBalancerSourceRanges" ; } + add rule ip kube-proxy firewall-check ip daddr . meta l4proto . th dport vmap @firewall-ips + + add rule ip kube-proxy reject-chain reject + + add map ip kube-proxy no-endpoint-services { type ipv4_addr . inet_proto . inet_service : verdict ; comment "vmap to drop or reject packets to services with no endpoints" ; } + add map ip kube-proxy no-endpoint-nodeports { type inet_proto . inet_service : verdict ; comment "vmap to drop or reject packets to service nodeports with no endpoints" ; } + + add rule ip kube-proxy endpoints-check ip daddr . meta l4proto . th dport vmap @no-endpoint-services + add rule ip kube-proxy endpoints-check fib daddr type local ip daddr != 127.0.0.0/8 meta l4proto . th dport vmap @no-endpoint-nodeports + + add map ip kube-proxy service-ips { type ipv4_addr . inet_proto . inet_service : verdict ; comment "ClusterIP, ExternalIP and LoadBalancer IP traffic" ; } + add map ip kube-proxy service-nodeports { type inet_proto . inet_service : verdict ; comment "NodePort traffic" ; } + add rule ip kube-proxy services ip daddr . meta l4proto . th dport vmap @service-ips + add rule ip kube-proxy services fib daddr type local ip daddr != 127.0.0.0/8 meta l4proto . th dport vmap @service-nodeports + + # svc1 + add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 ip daddr 172.30.0.41 tcp dport 80 ip saddr != 10.0.0.0/8 jump mark-for-masquerade + add rule ip kube-proxy service-ULMVA6XW-ns1/svc1/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 } + + add rule ip kube-proxy endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 ip saddr 10.180.0.1 jump mark-for-masquerade + add rule ip kube-proxy endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 meta l4proto tcp dnat to 10.180.0.1:80 + + add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } + + # svc2 + add rule ip kube-proxy service-42NFTM6N-ns2/svc2/tcp/p80 ip daddr 172.30.0.42 tcp dport 80 ip saddr != 10.0.0.0/8 jump mark-for-masquerade + add rule ip kube-proxy service-42NFTM6N-ns2/svc2/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 } + add rule ip kube-proxy external-42NFTM6N-ns2/svc2/tcp/p80 ip saddr 10.0.0.0/8 goto service-42NFTM6N-ns2/svc2/tcp/p80 comment "short-circuit pod traffic" + add rule ip kube-proxy external-42NFTM6N-ns2/svc2/tcp/p80 fib saddr type local jump mark-for-masquerade comment "masquerade local traffic" + add rule ip kube-proxy external-42NFTM6N-ns2/svc2/tcp/p80 fib saddr type local goto service-42NFTM6N-ns2/svc2/tcp/p80 comment "short-circuit local traffic" + add rule ip kube-proxy endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 ip saddr 10.180.0.2 jump mark-for-masquerade + add rule ip kube-proxy endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 meta l4proto tcp dnat to 10.180.0.2:80 + + add element ip kube-proxy service-ips { 172.30.0.42 . tcp . 80 : goto service-42NFTM6N-ns2/svc2/tcp/p80 } + add element ip kube-proxy service-ips { 192.168.99.22 . tcp . 80 : goto external-42NFTM6N-ns2/svc2/tcp/p80 } + add element ip kube-proxy service-ips { 1.2.3.4 . tcp . 80 : goto external-42NFTM6N-ns2/svc2/tcp/p80 } + add element ip kube-proxy service-nodeports { tcp . 3001 : goto external-42NFTM6N-ns2/svc2/tcp/p80 } + + add element ip kube-proxy no-endpoint-nodeports { tcp . 3001 comment "ns2/svc2:p80" : drop } + add element ip kube-proxy no-endpoint-services { 1.2.3.4 . tcp . 80 comment "ns2/svc2:p80" : drop } + add element ip kube-proxy no-endpoint-services { 192.168.99.22 . tcp . 80 comment "ns2/svc2:p80" : drop } + + # svc3 + add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 ip daddr 172.30.0.43 tcp dport 80 ip saddr != 10.0.0.0/8 jump mark-for-masquerade + add rule ip kube-proxy service-4AT6LBPK-ns3/svc3/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-UEIP74TE-ns3/svc3/tcp/p80__10.180.0.3/80 } + add rule ip kube-proxy external-4AT6LBPK-ns3/svc3/tcp/p80 jump mark-for-masquerade + add rule ip kube-proxy external-4AT6LBPK-ns3/svc3/tcp/p80 goto service-4AT6LBPK-ns3/svc3/tcp/p80 + add rule ip kube-proxy endpoint-UEIP74TE-ns3/svc3/tcp/p80__10.180.0.3/80 ip saddr 10.180.0.3 jump mark-for-masquerade + add rule ip kube-proxy endpoint-UEIP74TE-ns3/svc3/tcp/p80__10.180.0.3/80 meta l4proto tcp dnat to 10.180.0.3:80 + + add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 } + add element ip kube-proxy service-nodeports { tcp . 3003 : goto external-4AT6LBPK-ns3/svc3/tcp/p80 } + + # svc4 + add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 ip daddr 172.30.0.44 tcp dport 80 ip saddr != 10.0.0.0/8 jump mark-for-masquerade + add rule ip kube-proxy service-LAUZTJTB-ns4/svc4/tcp/p80 numgen random mod 2 vmap { 0 : goto endpoint-UNZV3OEC-ns4/svc4/tcp/p80__10.180.0.4/80 , 1 : goto endpoint-5RFCDDV7-ns4/svc4/tcp/p80__10.180.0.5/80 } + add rule ip kube-proxy external-LAUZTJTB-ns4/svc4/tcp/p80 jump mark-for-masquerade + add rule ip kube-proxy external-LAUZTJTB-ns4/svc4/tcp/p80 goto service-LAUZTJTB-ns4/svc4/tcp/p80 + add rule ip kube-proxy endpoint-5RFCDDV7-ns4/svc4/tcp/p80__10.180.0.5/80 ip saddr 10.180.0.5 jump mark-for-masquerade + add rule ip kube-proxy endpoint-5RFCDDV7-ns4/svc4/tcp/p80__10.180.0.5/80 meta l4proto tcp dnat to 10.180.0.5:80 + add rule ip kube-proxy endpoint-UNZV3OEC-ns4/svc4/tcp/p80__10.180.0.4/80 ip saddr 10.180.0.4 jump mark-for-masquerade + add rule ip kube-proxy endpoint-UNZV3OEC-ns4/svc4/tcp/p80__10.180.0.4/80 meta l4proto tcp dnat to 10.180.0.4:80 + + add element ip kube-proxy service-ips { 172.30.0.44 . tcp . 80 : goto service-LAUZTJTB-ns4/svc4/tcp/p80 } + add element ip kube-proxy service-ips { 192.168.99.33 . tcp . 80 : goto external-LAUZTJTB-ns4/svc4/tcp/p80 } + + # svc5 + add set ip kube-proxy affinity-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 { type ipv4_addr ; flags dynamic,timeout ; timeout 10800s ; } + add rule ip kube-proxy service-HVFWP5L3-ns5/svc5/tcp/p80 ip daddr 172.30.0.45 tcp dport 80 ip saddr != 10.0.0.0/8 jump mark-for-masquerade + add rule ip kube-proxy service-HVFWP5L3-ns5/svc5/tcp/p80 ip saddr @affinity-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 goto endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 + add rule ip kube-proxy service-HVFWP5L3-ns5/svc5/tcp/p80 numgen random mod 1 vmap { 0 : goto endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 } + add rule ip kube-proxy external-HVFWP5L3-ns5/svc5/tcp/p80 jump mark-for-masquerade + add rule ip kube-proxy external-HVFWP5L3-ns5/svc5/tcp/p80 goto service-HVFWP5L3-ns5/svc5/tcp/p80 + + add rule ip kube-proxy endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 ip saddr 10.180.0.3 jump mark-for-masquerade + add rule ip kube-proxy endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 update @affinity-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 { ip saddr } + add rule ip kube-proxy endpoint-GTK6MW7G-ns5/svc5/tcp/p80__10.180.0.3/80 meta l4proto tcp dnat to 10.180.0.3:80 + + add rule ip kube-proxy firewall-HVFWP5L3-ns5/svc5/tcp/p80 ip saddr != { 203.0.113.0/25 } drop + + add element ip kube-proxy service-ips { 172.30.0.45 . tcp . 80 : goto service-HVFWP5L3-ns5/svc5/tcp/p80 } + add element ip kube-proxy service-ips { 5.6.7.8 . tcp . 80 : goto external-HVFWP5L3-ns5/svc5/tcp/p80 } + add element ip kube-proxy service-nodeports { tcp . 3002 : goto external-HVFWP5L3-ns5/svc5/tcp/p80 } + add element ip kube-proxy firewall-ips { 5.6.7.8 . tcp . 80 comment "ns5/svc5:p80" : goto firewall-HVFWP5L3-ns5/svc5/tcp/p80 } + + # svc6 + add element ip kube-proxy no-endpoint-services { 172.30.0.46 . tcp . 80 comment "ns6/svc6:p80" : goto reject-chain } + `) + + for _, rules := range []string{tc1, tc2} { + fake := NewFake(IPv4Family, "kube-proxy") + err := fake.ParseDump(rules) + if err != nil { + t.Fatalf("unexpected error from ParseDump: %v", err) + } + + // Dump() had 1 empty line, add to rulesSlice to match + rulesSlice := []string{""} + for _, rule := range strings.Split(rules, "\n") { + if rule == "" || strings.HasPrefix(rule, "#") { + continue + } + rulesSlice = append(rulesSlice, rule) + } + sort.Sort(sort.StringSlice(rulesSlice)) + dumpSlice := strings.Split(fake.Dump(), "\n") + sort.Sort(sort.StringSlice(dumpSlice)) + + diff := cmp.Diff(rulesSlice, dumpSlice) + if diff != "" { + t.Errorf("Dump doesn't match given rules:\n%s", diff) + } + } +} diff --git a/objects.go b/objects.go index 69ed9a8..6a1578f 100644 --- a/objects.go +++ b/objects.go @@ -19,7 +19,9 @@ package knftables import ( "fmt" "io" + "strconv" "strings" + "time" ) // Object implementation for Table @@ -55,6 +57,17 @@ func (table *Table) writeOperation(verb verb, ctx *nftContext, writer io.Writer) fmt.Fprintf(writer, "\n") } +func parseTable(words []string) (*Table, error) { + tableObj := &Table{} + if len(words) > 4 { + _, comment := splitComment(strings.Join(words[4:], " ")) + if comment != nil { + tableObj.Comment = comment + } + } + return tableObj, nil +} + // Object implementation for Chain func (chain *Chain) validate(verb verb) error { if chain.Hook == nil { @@ -128,6 +141,39 @@ func (chain *Chain) writeOperation(verb verb, ctx *nftContext, writer io.Writer) fmt.Fprintf(writer, "\n") } +func parseChain(words []string) (*Chain, error) { + if len(words) < 5 { + return nil, fmt.Errorf("chain command should have at least 5 words") + } + chainObj := &Chain{ + Name: words[4], + } + if len(words) > 6 { + chainStr, comment := splitComment(strings.Join(words[6:len(words)-1], " ")) + if comment != nil { + chainObj.Comment = comment + } + if len(chainStr) > 0 { + chainWords := strings.Split(chainStr, " ") + if len(chainWords) < 6 { + return nil, fmt.Errorf("can't parse chain description: %s", chainStr) + } + chainObj.Type = (*BaseChainType)(&chainWords[1]) + chainObj.Hook = (*BaseChainHook)(&chainWords[3]) + if chainWords[4] == "device" { + chainObj.Device = &chainWords[5] + if len(chainWords) < 8 { + return nil, fmt.Errorf("can't parse chain description: %s", chainStr) + } + chainObj.Priority = (*BaseChainPriority)(&chainWords[7]) + } else { + chainObj.Priority = (*BaseChainPriority)(&chainWords[5]) + } + } + } + return chainObj, nil +} + // Object implementation for Rule func (rule *Rule) validate(verb verb) error { if rule.Chain == "" { @@ -181,6 +227,22 @@ func (rule *Rule) writeOperation(verb verb, ctx *nftContext, writer io.Writer) { fmt.Fprintf(writer, "\n") } +func parseRule(words []string) (*Rule, error) { + if len(words) < 6 { + return nil, fmt.Errorf("rule command should have at least 6 words") + } + chainName := words[4] + rule, comment := splitComment(strings.Join(words[5:], " ")) + ruleObj := &Rule{ + Chain: chainName, + Rule: rule, + } + if comment != nil { + ruleObj.Comment = comment + } + return ruleObj, nil +} + // Object implementation for Set func (set *Set) validate(verb verb) error { switch verb { @@ -261,6 +323,26 @@ func (set *Set) writeOperation(verb verb, ctx *nftContext, writer io.Writer) { fmt.Fprintf(writer, "\n") } +func parseSet(words []string) (*Set, error) { + name, typeProp, typeOf, flags, timeout, gcInterval, size, policy, comment, autoMerge, err := parseMapAndSetProps(words) + if err != nil { + return nil, err + } + + return &Set{ + Name: name, + Type: typeProp, + TypeOf: typeOf, + Flags: flags, + Timeout: timeout, + GCInterval: gcInterval, + Size: size, + Policy: policy, + Comment: comment, + AutoMerge: autoMerge, + }, nil +} + // Object implementation for Map func (mapObj *Map) validate(verb verb) error { switch verb { @@ -338,6 +420,91 @@ func (mapObj *Map) writeOperation(verb verb, ctx *nftContext, writer io.Writer) fmt.Fprintf(writer, "\n") } +func parseMap(words []string) (*Map, error) { + name, typeProp, typeOf, flags, timeout, gcInterval, size, policy, comment, _, err := parseMapAndSetProps(words) + if err != nil { + return nil, err + } + return &Map{ + Name: name, + Type: typeProp, + TypeOf: typeOf, + Flags: flags, + Timeout: timeout, + GCInterval: gcInterval, + Size: size, + Policy: policy, + Comment: comment, + }, nil +} + +func parseMapAndSetProps(words []string) (name string, typeProp string, typeOf string, flags []SetFlag, + timeout *time.Duration, gcInterval *time.Duration, size *uint64, policy *SetPolicy, comment *string, autoMerge *bool, err error) { + if len(words) < 7 { + err = fmt.Errorf("map or set command should have at least 7 words") + return + } + name = words[4] + var objDesc string + objDesc, comment = splitComment(strings.Join(words[6:len(words)-1], " ")) + objProps := strings.Split(objDesc, " ;") + if len(objProps) == 0 { + err = fmt.Errorf("map or set command should have type declaration") + return + } + if strings.HasPrefix(objProps[0], "type ") { + typeProp = strings.TrimPrefix(objProps[0], "type ") + } else { + typeOf = strings.TrimPrefix(objProps[0], "typeof ") + } + // optional fields + for _, mapProp := range objProps[1:] { + if flagsStr := strings.TrimPrefix(mapProp, " flags "); flagsStr != mapProp { + flags = parseSetFlags(flagsStr) + } + if timeoutStr := strings.TrimPrefix(mapProp, " timeout "); timeoutStr != mapProp { + var timeoutObj time.Duration + timeoutObj, err = time.ParseDuration(timeoutStr) + if err != nil { + return + } + timeout = &timeoutObj + } + if gcIntervalStr := strings.TrimPrefix(mapProp, " gc-interval "); gcIntervalStr != mapProp { + var gcIntervalObj time.Duration + gcIntervalObj, err = time.ParseDuration(gcIntervalStr) + if err != nil { + return + } + gcInterval = &gcIntervalObj + } + if sizeStr := strings.TrimPrefix(mapProp, " size "); sizeStr != mapProp { + var sizeObj uint64 + sizeObj, err = strconv.ParseUint(sizeStr, 10, 64) + if err != nil { + return + } + size = &sizeObj + } + if policyStr := strings.TrimPrefix(mapProp, " policy "); policyStr != mapProp { + policy = (*SetPolicy)(&policyStr) + } + if autoMergeStr := strings.TrimPrefix(mapProp, " auto-merge"); autoMergeStr != mapProp { + autoMergeObj := true + autoMerge = &autoMergeObj + } + } + return +} + +func parseSetFlags(s string) []SetFlag { + var res []SetFlag + for _, flag := range strings.Split(s, ",") { + res = append(res, SetFlag(flag)) + } + return res +} + // Object implementation for Element func (element *Element) validate(verb verb) error { if element.Map == "" && element.Set == "" { @@ -387,3 +554,30 @@ func (element *Element) writeOperation(verb verb, ctx *nftContext, writer io.Wri fmt.Fprintf(writer, " }\n") } + +func parseElement(words []string) (*Element, error) { + if len(words) < 5 { + return nil, fmt.Errorf("element command should have at least 5 words") + } + elementObj := &Element{} + mapOrSetName := words[4] + elementStr, comment := splitComment(strings.Join(words[6:len(words)-1], " ")) + if comment != nil { + elementObj.Comment = comment + } + keysAndValues := strings.Split(elementStr, " : ") + if len(keysAndValues) == 1 { + elementObj.Set = mapOrSetName + } else if len(keysAndValues) == 2 { + elementObj.Map = mapOrSetName + for _, value := range strings.Split(keysAndValues[1], " . ") { + elementObj.Value = append(elementObj.Value, value) + } + } else { + return nil, fmt.Errorf("wrong element format") + } + for _, key := range strings.Split(keysAndValues[0], " . ") { + elementObj.Key = append(elementObj.Key, key) + } + return elementObj, nil +}