1 package netlink
2
3 import (
4 "bytes"
5 "io/ioutil"
6 "net"
7 "testing"
8
9 "github.com/vishvananda/netlink/nl"
10 "golang.org/x/sys/unix"
11 )
12
13 func TestParseIpsetProtocolResult(t *testing.T) {
14 msgBytes, err := ioutil.ReadFile("testdata/ipset_protocol_result")
15 if err != nil {
16 t.Fatalf("reading test fixture failed: %v", err)
17 }
18
19 msg := ipsetUnserialize([][]byte{msgBytes})
20 if msg.Protocol != 6 {
21 t.Errorf("expected msg.Protocol to equal 6, got %d", msg.Protocol)
22 }
23 }
24
25 func TestParseIpsetListResult(t *testing.T) {
26 msgBytes, err := ioutil.ReadFile("testdata/ipset_list_result")
27 if err != nil {
28 t.Fatalf("reading test fixture failed: %v", err)
29 }
30
31 msg := ipsetUnserialize([][]byte{msgBytes})
32 if msg.SetName != "clients" {
33 t.Errorf(`expected SetName to equal "clients", got %q`, msg.SetName)
34 }
35 if msg.TypeName != "hash:mac" {
36 t.Errorf(`expected TypeName to equal "hash:mac", got %q`, msg.TypeName)
37 }
38 if msg.Protocol != 6 {
39 t.Errorf("expected Protocol to equal 6, got %d", msg.Protocol)
40 }
41 if msg.References != 0 {
42 t.Errorf("expected References to equal 0, got %d", msg.References)
43 }
44 if msg.NumEntries != 2 {
45 t.Errorf("expected NumEntries to equal 2, got %d", msg.NumEntries)
46 }
47 if msg.HashSize != 1024 {
48 t.Errorf("expected HashSize to equal 1024, got %d", msg.HashSize)
49 }
50 if *msg.Timeout != 3600 {
51 t.Errorf("expected Timeout to equal 3600, got %d", *msg.Timeout)
52 }
53 if msg.MaxElements != 65536 {
54 t.Errorf("expected MaxElements to equal 65536, got %d", msg.MaxElements)
55 }
56 if msg.CadtFlags != nl.IPSET_FLAG_WITH_COMMENT|nl.IPSET_FLAG_WITH_COUNTERS {
57 t.Error("expected CadtFlags to be IPSET_FLAG_WITH_COMMENT and IPSET_FLAG_WITH_COUNTERS")
58 }
59 if len(msg.Entries) != 2 {
60 t.Fatalf("expected 2 Entries, got %d", len(msg.Entries))
61 }
62
63
64 ent := msg.Entries[0]
65 if int(*ent.Timeout) != 3577 {
66 t.Errorf("expected Timeout for first entry to equal 3577, got %d", *ent.Timeout)
67 }
68 if int(*ent.Bytes) != 4121 {
69 t.Errorf("expected Bytes for first entry to equal 4121, got %d", *ent.Bytes)
70 }
71 if int(*ent.Packets) != 42 {
72 t.Errorf("expected Packets for first entry to equal 42, got %d", *ent.Packets)
73 }
74 if ent.Comment != "foo bar" {
75 t.Errorf("unexpected Comment for first entry: %q", ent.Comment)
76 }
77 expectedMAC := net.HardwareAddr{0xde, 0xad, 0x0, 0x0, 0xbe, 0xef}
78 if !bytes.Equal(ent.MAC, expectedMAC) {
79 t.Errorf("expected MAC for first entry to be %s, got %s", expectedMAC.String(), ent.MAC.String())
80 }
81
82
83 ent = msg.Entries[1]
84 expectedMAC = net.HardwareAddr{0x1, 0x2, 0x3, 0x0, 0x1, 0x2}
85 if !bytes.Equal(ent.MAC, expectedMAC) {
86 t.Errorf("expected MAC for second entry to be %s, got %s", expectedMAC.String(), ent.MAC.String())
87 }
88 }
89
90 func TestIpsetCreateListAddDelDestroy(t *testing.T) {
91 tearDown := setUpNetlinkTest(t)
92 defer tearDown()
93 timeout := uint32(3)
94 err := IpsetCreate("my-test-ipset-1", "hash:ip", IpsetCreateOptions{
95 Replace: true,
96 Timeout: &timeout,
97 Counters: true,
98 Comments: true,
99 Skbinfo: false,
100 })
101 if err != nil {
102 t.Fatal(err)
103 }
104
105 err = IpsetCreate("my-test-ipset-2", "hash:net", IpsetCreateOptions{
106 Replace: true,
107 Timeout: &timeout,
108 Counters: false,
109 Comments: true,
110 Skbinfo: true,
111 })
112 if err != nil {
113 t.Fatal(err)
114 }
115
116 results, err := IpsetListAll()
117
118 if err != nil {
119 t.Fatal(err)
120 }
121
122 if len(results) != 2 {
123 t.Fatalf("expected 2 IPSets to be created, got %d", len(results))
124 }
125
126 if results[0].SetName != "my-test-ipset-1" {
127 t.Errorf("expected name to be 'my-test-ipset-1', but got '%s'", results[0].SetName)
128 }
129
130 if results[1].SetName != "my-test-ipset-2" {
131 t.Errorf("expected name to be 'my-test-ipset-2', but got '%s'", results[1].SetName)
132 }
133
134 if results[0].TypeName != "hash:ip" {
135 t.Errorf("expected type to be 'hash:ip', but got '%s'", results[0].TypeName)
136 }
137
138 if results[1].TypeName != "hash:net" {
139 t.Errorf("expected type to be 'hash:net', but got '%s'", results[1].TypeName)
140 }
141
142 if *results[0].Timeout != 3 {
143 t.Errorf("expected timeout to be 3, but got '%d'", *results[0].Timeout)
144 }
145
146 ip := net.ParseIP("10.99.99.99")
147 exist, err := IpsetTest("my-test-ipset-1", &IPSetEntry{
148 IP: ip,
149 })
150 if err != nil {
151 t.Fatal(err)
152 }
153 if exist {
154 t.Errorf("entry should not exist before being added: %s", ip.String())
155 }
156
157 err = IpsetAdd("my-test-ipset-1", &IPSetEntry{
158 Comment: "test comment",
159 IP: ip,
160 Replace: false,
161 })
162
163 if err != nil {
164 t.Fatal(err)
165 }
166
167 exist, err = IpsetTest("my-test-ipset-1", &IPSetEntry{
168 IP: ip,
169 })
170 if err != nil {
171 t.Fatal(err)
172 }
173 if !exist {
174 t.Errorf("entry should exist after being added: %s", ip.String())
175 }
176
177 result, err := IpsetList("my-test-ipset-1")
178
179 if err != nil {
180 t.Fatal(err)
181 }
182
183 if len(result.Entries) != 1 {
184 t.Fatalf("expected 1 entry be created, got '%d'", len(result.Entries))
185 }
186 if result.Entries[0].IP.String() != "10.99.99.99" {
187 t.Fatalf("expected entry to be '10.99.99.99', got '%s'", result.Entries[0].IP.String())
188 }
189
190 if result.Entries[0].Comment != "test comment" {
191
192 t.Logf("expected comment to be 'test comment', got '%s'", result.Entries[0].Comment)
193 }
194
195 err = IpsetDel("my-test-ipset-1", &IPSetEntry{
196 Comment: "test comment",
197 IP: net.ParseIP("10.99.99.99"),
198 })
199 if err != nil {
200 t.Fatal(err)
201 }
202
203 result, err = IpsetList("my-test-ipset-1")
204 if err != nil {
205 t.Fatal(err)
206 }
207
208 if len(result.Entries) != 0 {
209 t.Fatalf("expected 0 entries to exist, got %d", len(result.Entries))
210 }
211
212 err = IpsetDestroy("my-test-ipset-1")
213 if err != nil {
214 t.Fatal(err)
215 }
216
217 err = IpsetDestroy("my-test-ipset-2")
218 if err != nil {
219 t.Fatal(err)
220 }
221 }
222
223 func TestIpsetCreateListAddDelDestroyWithTestCases(t *testing.T) {
224 timeout := uint32(3)
225 protocalTCP := uint8(unix.IPPROTO_TCP)
226 port := uint16(80)
227
228 testCases := []struct {
229 desc string
230 setname string
231 typename string
232 options IpsetCreateOptions
233 entry *IPSetEntry
234 }{
235 {
236 desc: "Type-hash:ip",
237 setname: "my-test-ipset-1",
238 typename: "hash:ip",
239 options: IpsetCreateOptions{
240 Replace: true,
241 Timeout: &timeout,
242 Counters: true,
243 Comments: true,
244 Skbinfo: false,
245 },
246 entry: &IPSetEntry{
247 Comment: "test comment",
248 IP: net.ParseIP("10.99.99.99"),
249 Replace: false,
250 },
251 },
252 {
253 desc: "Type-hash:net",
254 setname: "my-test-ipset-2",
255 typename: "hash:net",
256 options: IpsetCreateOptions{
257 Replace: true,
258 Timeout: &timeout,
259 Counters: false,
260 Comments: true,
261 Skbinfo: true,
262 },
263 entry: &IPSetEntry{
264 Comment: "test comment",
265 IP: net.ParseIP("10.99.99.0"),
266 CIDR: 24,
267 Replace: false,
268 },
269 },
270 {
271 desc: "Type-hash:net,net",
272 setname: "my-test-ipset-4",
273 typename: "hash:net,net",
274 options: IpsetCreateOptions{
275 Replace: true,
276 Timeout: &timeout,
277 Counters: false,
278 Comments: true,
279 Skbinfo: true,
280 },
281 entry: &IPSetEntry{
282 Comment: "test comment",
283 IP: net.ParseIP("10.99.99.0"),
284 CIDR: 24,
285 IP2: net.ParseIP("10.99.0.0"),
286 CIDR2: 24,
287 Replace: false,
288 },
289 },
290 {
291 desc: "Type-hash:ip,ip",
292 setname: "my-test-ipset-5",
293 typename: "hash:net,net",
294 options: IpsetCreateOptions{
295 Replace: true,
296 Timeout: &timeout,
297 Counters: false,
298 Comments: true,
299 Skbinfo: true,
300 },
301 entry: &IPSetEntry{
302 Comment: "test comment",
303 IP: net.ParseIP("10.99.99.0"),
304 IP2: net.ParseIP("10.99.0.0"),
305 Replace: false,
306 },
307 },
308 {
309 desc: "Type-hash:ip,port",
310 setname: "my-test-ipset-6",
311 typename: "hash:ip,port",
312 options: IpsetCreateOptions{
313 Replace: true,
314 Timeout: &timeout,
315 Counters: false,
316 Comments: true,
317 Skbinfo: true,
318 },
319 entry: &IPSetEntry{
320 Comment: "test comment",
321 IP: net.ParseIP("10.99.99.1"),
322 Protocol: &protocalTCP,
323 Port: &port,
324 Replace: false,
325 },
326 },
327 {
328 desc: "Type-hash:net,port,net",
329 setname: "my-test-ipset-7",
330 typename: "hash:net,port,net",
331 options: IpsetCreateOptions{
332 Replace: true,
333 Timeout: &timeout,
334 Counters: false,
335 Comments: true,
336 Skbinfo: true,
337 },
338 entry: &IPSetEntry{
339 Comment: "test comment",
340 IP: net.ParseIP("10.99.99.0"),
341 CIDR: 24,
342 IP2: net.ParseIP("10.99.0.0"),
343 CIDR2: 24,
344 Protocol: &protocalTCP,
345 Port: &port,
346 Replace: false,
347 },
348 },
349 {
350 desc: "Type-hash:mac",
351 setname: "my-test-ipset-8",
352 typename: "hash:mac",
353 options: IpsetCreateOptions{
354 Replace: true,
355 Timeout: &timeout,
356 Counters: true,
357 Comments: true,
358 Skbinfo: false,
359 },
360 entry: &IPSetEntry{
361 Comment: "test comment",
362 MAC: net.HardwareAddr{0x26, 0x6f, 0x0d, 0x5b, 0xc1, 0x9d},
363 Replace: false,
364 },
365 },
366 {
367 desc: "Type-hash:net,iface",
368 setname: "my-test-ipset-9",
369 typename: "hash:net,iface",
370 options: IpsetCreateOptions{
371 Replace: true,
372 Timeout: &timeout,
373 Counters: true,
374 Comments: true,
375 Skbinfo: false,
376 },
377 entry: &IPSetEntry{
378 Comment: "test comment",
379 IP: net.ParseIP("10.99.99.0"),
380 CIDR: 24,
381 IFace: "eth0",
382 Replace: false,
383 },
384 },
385 {
386 desc: "Type-hash:ip,mark",
387 setname: "my-test-ipset-10",
388 typename: "hash:ip,mark",
389 options: IpsetCreateOptions{
390 Replace: true,
391 Timeout: &timeout,
392 Counters: true,
393 Comments: true,
394 Skbinfo: false,
395 },
396 entry: &IPSetEntry{
397 Comment: "test comment",
398 IP: net.ParseIP("10.99.99.0"),
399 Mark: &timeout,
400 Replace: false,
401 },
402 },
403 {
404 desc: "Type-hash:net6",
405 setname: "my-test-ipset-11",
406 typename: "hash:net",
407 options: IpsetCreateOptions{
408 Replace: true,
409 Timeout: &timeout,
410 Counters: false,
411 Comments: true,
412 Skbinfo: true,
413 Family: unix.AF_INET6,
414 },
415 entry: &IPSetEntry{
416 Comment: "test comment",
417 IP: net.ParseIP("::1"),
418 CIDR: 128,
419 Replace: false,
420 },
421 },
422 {
423 desc: "Type-hash:net6:net6",
424 setname: "my-test-ipset-11",
425 typename: "hash:net,net",
426 options: IpsetCreateOptions{
427 Replace: true,
428 Timeout: &timeout,
429 Counters: false,
430 Comments: true,
431 Skbinfo: true,
432 Family: unix.AF_INET6,
433 },
434 entry: &IPSetEntry{
435 Comment: "test comment",
436 IP: net.ParseIP("::1"),
437 CIDR: 128,
438 IP2: net.ParseIP("::2"),
439 CIDR2: 128,
440 Replace: false,
441 },
442 },
443 }
444
445 for _, tC := range testCases {
446 t.Run(tC.desc, func(t *testing.T) {
447 tearDown := setUpNetlinkTest(t)
448 defer tearDown()
449
450 err := IpsetCreate(tC.setname, tC.typename, tC.options)
451 if err != nil {
452 t.Fatal(err)
453 }
454
455 result, err := IpsetList(tC.setname)
456 if err != nil {
457 t.Fatal(err)
458 }
459
460 if result.SetName != tC.setname {
461 t.Errorf("expected name to be '%s', but got '%s'", tC.setname, result.SetName)
462 }
463
464 if result.TypeName != tC.typename {
465 t.Errorf("expected type to be '%s', but got '%s'", tC.typename, result.TypeName)
466 }
467
468 if *result.Timeout != timeout {
469 t.Errorf("expected timeout to be %d, but got '%d'", timeout, *result.Timeout)
470 }
471
472 err = IpsetAdd(tC.setname, tC.entry)
473
474 if err != nil {
475 t.Error(result.Protocol, result.Family)
476 t.Fatal(err)
477 }
478
479 exist, err := IpsetTest(tC.setname, tC.entry)
480 if err != nil {
481 t.Fatal(err)
482 }
483 if !exist {
484 t.Errorf("entry should exist, but 'test' got false, case: %s", tC.desc)
485 }
486
487 result, err = IpsetList(tC.setname)
488
489 if err != nil {
490 t.Fatal(err)
491 }
492
493 if len(result.Entries) != 1 {
494 t.Fatalf("expected 1 entry be created, got '%d'", len(result.Entries))
495 }
496
497 if tC.entry.IP != nil {
498 if !tC.entry.IP.Equal(result.Entries[0].IP) {
499 t.Fatalf("expected entry to be '%v', got '%v'", tC.entry.IP, result.Entries[0].IP)
500 }
501 }
502
503 if tC.entry.CIDR > 0 {
504 if result.Entries[0].CIDR != tC.entry.CIDR {
505 t.Fatalf("expected cidr to be '%d', got '%d'", tC.entry.CIDR, result.Entries[0].CIDR)
506 }
507 }
508
509 if tC.entry.IP2 != nil {
510 if !tC.entry.IP2.Equal(result.Entries[0].IP2) {
511 t.Fatalf("expected entry.ip2 to be '%v', got '%v'", tC.entry.IP2, result.Entries[0].IP2)
512 }
513 }
514
515 if tC.entry.CIDR2 > 0 {
516 if result.Entries[0].CIDR2 != tC.entry.CIDR2 {
517 t.Fatalf("expected cidr2 to be '%d', got '%d'", tC.entry.CIDR2, result.Entries[0].CIDR2)
518 }
519 }
520
521 if tC.entry.Port != nil {
522 if *result.Entries[0].Protocol != *tC.entry.Protocol {
523 t.Fatalf("expected protocol to be '%d', got '%d'", *tC.entry.Protocol, *result.Entries[0].Protocol)
524 }
525 if *result.Entries[0].Port != *tC.entry.Port {
526 t.Fatalf("expected port to be '%d', got '%d'", *tC.entry.Port, *result.Entries[0].Port)
527 }
528 }
529
530 if tC.entry.MAC != nil {
531 if result.Entries[0].MAC.String() != tC.entry.MAC.String() {
532 t.Fatalf("expected mac to be '%v', got '%v'", tC.entry.MAC, result.Entries[0].MAC)
533 }
534 }
535
536 if tC.entry.IFace != "" {
537 if result.Entries[0].IFace != tC.entry.IFace {
538 t.Fatalf("expected iface to be '%v', got '%v'", tC.entry.IFace, result.Entries[0].IFace)
539 }
540 }
541
542 if tC.entry.Mark != nil {
543 if *result.Entries[0].Mark != *tC.entry.Mark {
544 t.Fatalf("expected mark to be '%v', got '%v'", *tC.entry.Mark, *result.Entries[0].Mark)
545 }
546 }
547
548 if result.Entries[0].Comment != tC.entry.Comment {
549
550 t.Logf("expected comment to be '%s', got '%s'", tC.entry.Comment, result.Entries[0].Comment)
551 }
552
553 err = IpsetDel(tC.setname, tC.entry)
554 if err != nil {
555 t.Fatal(err)
556 }
557
558 exist, err = IpsetTest(tC.setname, tC.entry)
559 if err != nil {
560 t.Fatal(err)
561 }
562 if exist {
563 t.Errorf("entry should be deleted, but 'test' got true, case: %s", tC.desc)
564 }
565
566 result, err = IpsetList(tC.setname)
567 if err != nil {
568 t.Fatal(err)
569 }
570
571 if len(result.Entries) != 0 {
572 t.Fatalf("expected 0 entries to exist, got %d", len(result.Entries))
573 }
574
575 err = IpsetDestroy(tC.setname)
576 if err != nil {
577 t.Fatal(err)
578 }
579 })
580 }
581 }
582
583 func TestIpsetBitmapCreateListWithTestCases(t *testing.T) {
584 timeout := uint32(3)
585
586 testCases := []struct {
587 desc string
588 setname string
589 typename string
590 options IpsetCreateOptions
591 entry *IPSetEntry
592 }{
593 {
594 desc: "Type-bitmap:port",
595 setname: "my-test-ipset-11",
596 typename: "bitmap:port",
597 options: IpsetCreateOptions{
598 Replace: true,
599 Timeout: &timeout,
600 Counters: true,
601 Comments: false,
602 Skbinfo: false,
603 PortFrom: 100,
604 PortTo: 600,
605 },
606 entry: &IPSetEntry{
607 Comment: "test comment",
608 IP: net.ParseIP("10.99.99.0"),
609 CIDR: 26,
610 Mark: &timeout,
611 Replace: false,
612 },
613 },
614 }
615
616 for _, tC := range testCases {
617 t.Run(tC.desc, func(t *testing.T) {
618 tearDown := setUpNetlinkTest(t)
619 defer tearDown()
620
621 err := IpsetCreate(tC.setname, tC.typename, tC.options)
622 if err != nil {
623 t.Fatal(err)
624 }
625
626 result, err := IpsetList(tC.setname)
627 if err != nil {
628 t.Fatal(err)
629 }
630
631 if tC.typename == "bitmap:port" {
632 if result.PortFrom != tC.options.PortFrom || result.PortTo != tC.options.PortTo {
633 t.Fatalf("expected port range %d-%d, got %d-%d", tC.options.PortFrom, tC.options.PortTo, result.PortFrom, result.PortTo)
634 }
635 } else if tC.typename == "bitmap:ip" {
636 if result.IPFrom == nil || result.IPTo == nil || result.IPFrom.Equal(tC.options.IPFrom) || result.IPTo.Equal(tC.options.IPTo) {
637 t.Fatalf("expected ip range %v-%v, got %v-%v", tC.options.IPFrom, tC.options.IPTo, result.IPFrom, result.IPTo)
638 }
639 }
640
641 })
642 }
643 }
644
645 func TestIpsetSwap(t *testing.T) {
646 tearDown := setUpNetlinkTest(t)
647 defer tearDown()
648
649 ipset1 := "my-test-ipset-swap-1"
650 ipset2 := "my-test-ipset-swap-2"
651
652 err := IpsetCreate(ipset1, "hash:ip", IpsetCreateOptions{
653 Replace: true,
654 })
655 if err != nil {
656 t.Fatal(err)
657 }
658 defer func() {
659 _ = IpsetDestroy(ipset1)
660 }()
661
662 err = IpsetCreate(ipset2, "hash:ip", IpsetCreateOptions{
663 Replace: true,
664 })
665 if err != nil {
666 t.Fatal(err)
667 }
668 defer func() {
669 _ = IpsetDestroy(ipset2)
670 }()
671
672 err = IpsetAdd(ipset1, &IPSetEntry{
673 IP: net.ParseIP("10.99.99.99"),
674 })
675 if err != nil {
676 t.Fatal(err)
677 }
678
679 assertHasOneEntry := func(name string) {
680 result, err := IpsetList(name)
681 if err != nil {
682 t.Fatal(err)
683 }
684 if len(result.Entries) != 1 {
685 t.Fatalf("expected 1 entry be created, got '%d'", len(result.Entries))
686 }
687 if result.Entries[0].IP.String() != "10.99.99.99" {
688 t.Fatalf("expected entry to be '10.99.99.99', got '%s'", result.Entries[0].IP.String())
689 }
690 }
691
692 assertIsEmpty := func(name string) {
693 result, err := IpsetList(name)
694 if err != nil {
695 t.Fatal(err)
696 }
697 if len(result.Entries) != 0 {
698 t.Fatalf("expected 0 entry be created, got '%d'", len(result.Entries))
699 }
700 }
701
702 assertHasOneEntry(ipset1)
703 assertIsEmpty(ipset2)
704
705 err = IpsetSwap(ipset1, ipset2)
706 if err != nil {
707 t.Fatal(err)
708 }
709
710 assertIsEmpty(ipset1)
711 assertHasOneEntry(ipset2)
712 }
713
714 func nextIP(ip net.IP) {
715 for j := len(ip) - 1; j >= 0; j-- {
716 ip[j]++
717 if ip[j] > 0 {
718 break
719 }
720 }
721 }
722
723
724
725 func TestIpsetMaxElements(t *testing.T) {
726 tearDown := setUpNetlinkTest(t)
727 defer tearDown()
728
729 ipsetName := "my-test-ipset-max"
730 maxElements := uint32(128 << 10)
731
732 err := IpsetCreate(ipsetName, "hash:ip", IpsetCreateOptions{
733 Replace: true,
734 MaxElements: maxElements,
735 })
736 if err != nil {
737 t.Fatal(err)
738 }
739 defer func() {
740 _ = IpsetDestroy(ipsetName)
741 }()
742
743 ip := net.ParseIP("10.0.0.0")
744 for i := uint32(0); i < maxElements; i++ {
745 err = IpsetAdd(ipsetName, &IPSetEntry{
746 IP: ip,
747 })
748 if err != nil {
749 t.Fatal(err)
750 }
751 nextIP(ip)
752 }
753
754 result, err := IpsetList(ipsetName)
755 if err != nil {
756 t.Fatal(err)
757 }
758 if len(result.Entries) != int(maxElements) {
759 t.Fatalf("expected '%d' entry be created, got '%d'", maxElements, len(result.Entries))
760 }
761 }
762
View as plain text