1
2
3
4
19
20 package testing
21
22 import (
23 "fmt"
24 "reflect"
25 "regexp"
26 "strconv"
27 "strings"
28
29 "k8s.io/kubernetes/pkg/util/iptables"
30 )
31
32
33
34 type IPTablesDump struct {
35 Tables []Table
36 }
37
38
39 type Table struct {
40 Name iptables.Table
41 Chains []Chain
42 }
43
44
45 type Chain struct {
46 Name iptables.Chain
47 Packets uint64
48 Bytes uint64
49 Rules []*Rule
50
51
52
53 Deleted bool
54 }
55
56 var declareTableRegex = regexp.MustCompile(`^\*(.*)$`)
57 var declareChainRegex = regexp.MustCompile(`^:([^ ]*) - \[([0-9]*):([0-9]*)\]$`)
58 var addRuleRegex = regexp.MustCompile(`^-A ([^ ]*) (.*)$`)
59 var deleteChainRegex = regexp.MustCompile(`^-X (.*)$`)
60
61 type parseState int
62
63 const (
64 parseTableDeclaration parseState = iota
65 parseChainDeclarations
66 parseChains
67 )
68
69
70 func ParseIPTablesDump(data string) (*IPTablesDump, error) {
71 dump := &IPTablesDump{}
72 state := parseTableDeclaration
73 lines := strings.Split(strings.Trim(data, "\n"), "\n")
74 var t *Table
75
76 for _, line := range lines {
77 retry:
78 line = strings.TrimSpace(line)
79 if line == "" || line[0] == '#' {
80 continue
81 }
82
83 switch state {
84 case parseTableDeclaration:
85
86 match := declareTableRegex.FindStringSubmatch(line)
87 if match == nil {
88 return nil, fmt.Errorf("could not parse iptables data (table %d starts with %q not a table name)", len(dump.Tables)+1, line)
89 }
90 dump.Tables = append(dump.Tables, Table{Name: iptables.Table(match[1])})
91 t = &dump.Tables[len(dump.Tables)-1]
92 state = parseChainDeclarations
93
94 case parseChainDeclarations:
95 match := declareChainRegex.FindStringSubmatch(line)
96 if match == nil {
97 state = parseChains
98 goto retry
99 }
100
101 chain := iptables.Chain(match[1])
102 packets, _ := strconv.ParseUint(match[2], 10, 64)
103 bytes, _ := strconv.ParseUint(match[3], 10, 64)
104
105 t.Chains = append(t.Chains,
106 Chain{
107 Name: chain,
108 Packets: packets,
109 Bytes: bytes,
110 },
111 )
112
113 case parseChains:
114 if match := addRuleRegex.FindStringSubmatch(line); match != nil {
115 chain := iptables.Chain(match[1])
116
117 c, err := dump.GetChain(t.Name, chain)
118 if err != nil {
119 return nil, fmt.Errorf("error parsing rule %q: %v", line, err)
120 }
121 if c.Deleted {
122 return nil, fmt.Errorf("cannot add rules to deleted chain %q", chain)
123 }
124
125 rule, err := ParseRule(line, false)
126 if err != nil {
127 return nil, err
128 }
129 c.Rules = append(c.Rules, rule)
130 } else if match := deleteChainRegex.FindStringSubmatch(line); match != nil {
131 chain := iptables.Chain(match[1])
132
133 c, err := dump.GetChain(t.Name, chain)
134 if err != nil {
135 return nil, fmt.Errorf("error parsing rule %q: %v", line, err)
136 }
137 if len(c.Rules) != 0 {
138 return nil, fmt.Errorf("cannot delete chain %q after adding rules", chain)
139 }
140 c.Deleted = true
141 } else if line == "COMMIT" {
142 state = parseTableDeclaration
143 } else {
144 return nil, fmt.Errorf("error parsing rule %q", line)
145 }
146 }
147 }
148
149 if state != parseTableDeclaration {
150 return nil, fmt.Errorf("could not parse iptables data (no COMMIT line?)")
151 }
152
153 return dump, nil
154 }
155
156 func (dump *IPTablesDump) String() string {
157 buffer := &strings.Builder{}
158 for _, t := range dump.Tables {
159 fmt.Fprintf(buffer, "*%s\n", t.Name)
160 for _, c := range t.Chains {
161 fmt.Fprintf(buffer, ":%s - [%d:%d]\n", c.Name, c.Packets, c.Bytes)
162 }
163 for _, c := range t.Chains {
164 for _, r := range c.Rules {
165 fmt.Fprintf(buffer, "%s\n", r.Raw)
166 }
167 }
168 for _, c := range t.Chains {
169 if c.Deleted {
170 fmt.Fprintf(buffer, "-X %s\n", c.Name)
171 }
172 }
173 fmt.Fprintf(buffer, "COMMIT\n")
174 }
175 return buffer.String()
176 }
177
178 func (dump *IPTablesDump) GetTable(table iptables.Table) (*Table, error) {
179 for i := range dump.Tables {
180 if dump.Tables[i].Name == table {
181 return &dump.Tables[i], nil
182 }
183 }
184 return nil, fmt.Errorf("no such table %q", table)
185 }
186
187 func (dump *IPTablesDump) GetChain(table iptables.Table, chain iptables.Chain) (*Chain, error) {
188 t, err := dump.GetTable(table)
189 if err != nil {
190 return nil, err
191 }
192 for i := range t.Chains {
193 if t.Chains[i].Name == chain {
194 return &t.Chains[i], nil
195 }
196 }
197 return nil, fmt.Errorf("no such chain %q", chain)
198 }
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222 type Rule struct {
223
224 Raw string
225
226 Chain iptables.Chain `param:"-A"`
227 Comment *IPTablesValue `param:"--comment"`
228
229 Protocol *IPTablesValue `param:"-p" negatable:"true"`
230
231 SourceAddress *IPTablesValue `param:"-s" negatable:"true"`
232 SourceType *IPTablesValue `param:"--src-type" negatable:"true"`
233 SourcePort *IPTablesValue `param:"--sport" negatable:"true"`
234
235 DestinationAddress *IPTablesValue `param:"-d" negatable:"true"`
236 DestinationType *IPTablesValue `param:"--dst-type" negatable:"true"`
237 DestinationPort *IPTablesValue `param:"--dport" negatable:"true"`
238
239 MatchSet *IPTablesValue `param:"--match-set" negatable:"true"`
240
241 Jump *IPTablesValue `param:"-j"`
242 RandomFully *bool `param:"--random-fully"`
243 Probability *IPTablesValue `param:"--probability"`
244 DNATDestination *IPTablesValue `param:"--to-destination"`
245
246
247 AffinityCheck *bool `param:"--rcheck" negatable:"true"`
248 MarkCheck *IPTablesValue `param:"--mark" negatable:"true"`
249 CTStateCheck *IPTablesValue `param:"--ctstate" negatable:"true"`
250
251
252
253 AffinityName *IPTablesValue `param:"--name"`
254 AffinitySeconds *IPTablesValue `param:"--seconds"`
255 AffinitySet *bool `param:"--set" negatable:"true"`
256 AffinityReap *bool `param:"--reap"`
257 StatisticMode *IPTablesValue `param:"--mode"`
258 }
259
260
261
262 type IPTablesValue struct {
263 Negated bool
264 Value string
265 }
266
267
268 func (v *IPTablesValue) String() string {
269 if v.Negated {
270 return fmt.Sprintf("NOT %q", v.Value)
271 } else {
272 return fmt.Sprintf("%q", v.Value)
273 }
274 }
275
276
277
278 func (v *IPTablesValue) Matches(cmp string) bool {
279 if v.Negated {
280 return v.Value != cmp
281 } else {
282 return v.Value == cmp
283 }
284 }
285
286
287
288 func findParamField(value reflect.Value, param string) (*reflect.Value, bool) {
289 typ := value.Type()
290 for i := 0; i < typ.NumField(); i++ {
291 field := typ.Field(i)
292 if field.Tag.Get("param") == param {
293 fValue := value.Field(i)
294 return &fValue, field.Tag.Get("negatable") == "true"
295 }
296 }
297 return nil, false
298 }
299
300
301
302 var wordRegex = regexp.MustCompile(`(?:^|\s)("[^"]*"|[^"]\S*)`)
303
304
305 var boolPtrType = reflect.PointerTo(reflect.TypeOf(true))
306 var ipTablesValuePtrType = reflect.TypeOf((*IPTablesValue)(nil))
307
308
309
310
311 func ParseRule(rule string, strict bool) (*Rule, error) {
312 parsed := &Rule{Raw: rule}
313
314
315 var words []string
316 for _, match := range wordRegex.FindAllStringSubmatch(rule, -1) {
317 words = append(words, strings.Trim(match[1], `"`))
318 }
319
320
321 if len(words) < 2 || words[0] != "-A" {
322 return nil, fmt.Errorf(`bad iptables rule (does not start with "-A CHAIN")`)
323 } else if len(words) < 3 {
324 return nil, fmt.Errorf("bad iptables rule (no match rules)")
325 }
326
327
328
329
330
331 v := reflect.ValueOf(parsed).Elem()
332 negated := false
333 for w := 0; w < len(words); {
334 if words[w] == "-m" && w < len(words)-1 {
335
336
337 w += 2
338 continue
339 }
340
341 if words[w] == "!" {
342 negated = true
343 w++
344 continue
345 }
346
347
348 if field, negatable := findParamField(v, words[w]); field != nil {
349 if negated && !negatable {
350 return nil, fmt.Errorf("cannot negate parameter %q", words[w])
351 }
352 if field.Type() != boolPtrType && w == len(words)-1 {
353 return nil, fmt.Errorf("parameter %q requires an argument", words[w])
354 }
355 switch field.Type() {
356 case boolPtrType:
357 boolVal := !negated
358 field.Set(reflect.ValueOf(&boolVal))
359 w++
360 case ipTablesValuePtrType:
361 field.Set(reflect.ValueOf(&IPTablesValue{Negated: negated, Value: words[w+1]}))
362 w += 2
363 default:
364 field.SetString(words[w+1])
365 w += 2
366 }
367 } else if strict {
368 return nil, fmt.Errorf("unrecognized parameter %q", words[w])
369 } else {
370
371 w++
372 }
373
374 negated = false
375 }
376
377 return parsed, nil
378 }
379
View as plain text