bonding: support custom interface IDs
[vpp.git] / test / test_reassembly.py
1 #!/usr/bin/env python
2
3 import six
4 import unittest
5 from random import shuffle
6
7 from framework import VppTestCase, VppTestRunner
8
9 from scapy.packet import Raw
10 from scapy.layers.l2 import Ether, GRE
11 from scapy.layers.inet import IP, UDP, ICMP
12 from util import ppp, fragment_rfc791, fragment_rfc8200
13 from scapy.layers.inet6 import IPv6, IPv6ExtHdrFragment, ICMPv6ParamProblem,\
14     ICMPv6TimeExceeded
15 from vpp_gre_interface import VppGreInterface, VppGre6Interface
16 from vpp_ip import DpoProto
17 from vpp_ip_route import VppIpRoute, VppRoutePath
18
19 # 35 is enough to have >257 400-byte fragments
20 test_packet_count = 35
21
22
23 class TestIPv4Reassembly(VppTestCase):
24     """ IPv4 Reassembly """
25
26     @classmethod
27     def setUpClass(cls):
28         super(TestIPv4Reassembly, cls).setUpClass()
29
30         cls.create_pg_interfaces([0, 1])
31         cls.src_if = cls.pg0
32         cls.dst_if = cls.pg1
33
34         # setup all interfaces
35         for i in cls.pg_interfaces:
36             i.admin_up()
37             i.config_ip4()
38             i.resolve_arp()
39
40         # packet sizes
41         cls.packet_sizes = [64, 512, 1518, 9018]
42         cls.padding = " abcdefghijklmn"
43         cls.create_stream(cls.packet_sizes)
44         cls.create_fragments()
45
46     def setUp(self):
47         """ Test setup - force timeout on existing reassemblies """
48         super(TestIPv4Reassembly, self).setUp()
49         self.vapi.ip_reassembly_enable_disable(
50             sw_if_index=self.src_if.sw_if_index, enable_ip4=True)
51         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
52                                     expire_walk_interval_ms=10)
53         self.sleep(.25)
54         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
55                                     expire_walk_interval_ms=10000)
56
57     def tearDown(self):
58         super(TestIPv4Reassembly, self).tearDown()
59         self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
60
61     @classmethod
62     def create_stream(cls, packet_sizes, packet_count=test_packet_count):
63         """Create input packet stream
64
65         :param list packet_sizes: Required packet sizes.
66         """
67         for i in range(0, packet_count):
68             info = cls.create_packet_info(cls.src_if, cls.src_if)
69             payload = cls.info_to_payload(info)
70             p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
71                  IP(id=info.index, src=cls.src_if.remote_ip4,
72                     dst=cls.dst_if.remote_ip4) /
73                  UDP(sport=1234, dport=5678) /
74                  Raw(payload))
75             size = packet_sizes[(i // 2) % len(packet_sizes)]
76             cls.extend_packet(p, size, cls.padding)
77             info.data = p
78
79     @classmethod
80     def create_fragments(cls):
81         infos = cls._packet_infos
82         cls.pkt_infos = []
83         for index, info in six.iteritems(infos):
84             p = info.data
85             # cls.logger.debug(ppp("Packet:", p.__class__(str(p))))
86             fragments_400 = fragment_rfc791(p, 400)
87             fragments_300 = fragment_rfc791(p, 300)
88             fragments_200 = [
89                 x for f in fragments_400 for x in fragment_rfc791(f, 200)]
90             cls.pkt_infos.append(
91                 (index, fragments_400, fragments_300, fragments_200))
92         cls.fragments_400 = [
93             x for (_, frags, _, _) in cls.pkt_infos for x in frags]
94         cls.fragments_300 = [
95             x for (_, _, frags, _) in cls.pkt_infos for x in frags]
96         cls.fragments_200 = [
97             x for (_, _, _, frags) in cls.pkt_infos for x in frags]
98         cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
99                          "%s 300-byte fragments and %s 200-byte fragments" %
100                          (len(infos), len(cls.fragments_400),
101                              len(cls.fragments_300), len(cls.fragments_200)))
102
103     def verify_capture(self, capture, dropped_packet_indexes=[]):
104         """Verify captured packet stream.
105
106         :param list capture: Captured packet stream.
107         """
108         info = None
109         seen = set()
110         for packet in capture:
111             try:
112                 self.logger.debug(ppp("Got packet:", packet))
113                 ip = packet[IP]
114                 udp = packet[UDP]
115                 payload_info = self.payload_to_info(str(packet[Raw]))
116                 packet_index = payload_info.index
117                 self.assertTrue(
118                     packet_index not in dropped_packet_indexes,
119                     ppp("Packet received, but should be dropped:", packet))
120                 if packet_index in seen:
121                     raise Exception(ppp("Duplicate packet received", packet))
122                 seen.add(packet_index)
123                 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
124                 info = self._packet_infos[packet_index]
125                 self.assertTrue(info is not None)
126                 self.assertEqual(packet_index, info.index)
127                 saved_packet = info.data
128                 self.assertEqual(ip.src, saved_packet[IP].src)
129                 self.assertEqual(ip.dst, saved_packet[IP].dst)
130                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
131             except Exception:
132                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
133                 raise
134         for index in self._packet_infos:
135             self.assertTrue(index in seen or index in dropped_packet_indexes,
136                             "Packet with packet_index %d not received" % index)
137
138     def test_reassembly(self):
139         """ basic reassembly """
140
141         self.pg_enable_capture()
142         self.src_if.add_stream(self.fragments_200)
143         self.pg_start()
144
145         packets = self.dst_if.get_capture(len(self.pkt_infos))
146         self.verify_capture(packets)
147         self.src_if.assert_nothing_captured()
148
149         # run it all again to verify correctness
150         self.pg_enable_capture()
151         self.src_if.add_stream(self.fragments_200)
152         self.pg_start()
153
154         packets = self.dst_if.get_capture(len(self.pkt_infos))
155         self.verify_capture(packets)
156         self.src_if.assert_nothing_captured()
157
158     def test_reversed(self):
159         """ reverse order reassembly """
160
161         fragments = list(self.fragments_200)
162         fragments.reverse()
163
164         self.pg_enable_capture()
165         self.src_if.add_stream(fragments)
166         self.pg_start()
167
168         packets = self.dst_if.get_capture(len(self.packet_infos))
169         self.verify_capture(packets)
170         self.src_if.assert_nothing_captured()
171
172         # run it all again to verify correctness
173         self.pg_enable_capture()
174         self.src_if.add_stream(fragments)
175         self.pg_start()
176
177         packets = self.dst_if.get_capture(len(self.packet_infos))
178         self.verify_capture(packets)
179         self.src_if.assert_nothing_captured()
180
181     def test_5737(self):
182         """ fragment length + ip header size > 65535 """
183         self.vapi.cli("clear errors")
184         raw = ('E\x00\x00\x88,\xf8\x1f\xfe@\x01\x98\x00\xc0\xa8\n-\xc0\xa8\n'
185                '\x01\x08\x00\xf0J\xed\xcb\xf1\xf5Test-group: IPv4.IPv4.ipv4-'
186                'message.Ethernet-Payload.IPv4-Packet.IPv4-Header.Fragment-Of'
187                'fset; Test-case: 5737')
188
189         malformed_packet = (Ether(dst=self.src_if.local_mac,
190                                   src=self.src_if.remote_mac) /
191                             IP(raw))
192         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
193              IP(id=1000, src=self.src_if.remote_ip4,
194                 dst=self.dst_if.remote_ip4) /
195              UDP(sport=1234, dport=5678) /
196              Raw("X" * 1000))
197         valid_fragments = fragment_rfc791(p, 400)
198
199         self.pg_enable_capture()
200         self.src_if.add_stream([malformed_packet] + valid_fragments)
201         self.pg_start()
202
203         self.dst_if.get_capture(1)
204         self.assert_packet_counter_equal("ip4-reassembly-feature", 1)
205         # TODO remove above, uncomment below once clearing of counters
206         # is supported
207         # self.assert_packet_counter_equal(
208         #     "/err/ip4-reassembly-feature/malformed packets", 1)
209
210     def test_44924(self):
211         """ compress tiny fragments """
212         packets = [(Ether(dst=self.src_if.local_mac,
213                           src=self.src_if.remote_mac) /
214                     IP(id=24339, flags="MF", frag=0, ttl=64,
215                        src=self.src_if.remote_ip4,
216                        dst=self.dst_if.remote_ip4) /
217                     ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
218                     Raw(load='Test-group: IPv4')),
219                    (Ether(dst=self.src_if.local_mac,
220                           src=self.src_if.remote_mac) /
221                     IP(id=24339, flags="MF", frag=3, ttl=64,
222                        src=self.src_if.remote_ip4,
223                        dst=self.dst_if.remote_ip4) /
224                     ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
225                     Raw(load='.IPv4.Fragmentation.vali')),
226                    (Ether(dst=self.src_if.local_mac,
227                           src=self.src_if.remote_mac) /
228                     IP(id=24339, frag=6, ttl=64,
229                        src=self.src_if.remote_ip4,
230                        dst=self.dst_if.remote_ip4) /
231                     ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
232                     Raw(load='d; Test-case: 44924'))
233                    ]
234
235         self.pg_enable_capture()
236         self.src_if.add_stream(packets)
237         self.pg_start()
238
239         self.dst_if.get_capture(1)
240
241     def test_frag_1(self):
242         """ fragment of size 1 """
243         self.vapi.cli("clear errors")
244         malformed_packets = [(Ether(dst=self.src_if.local_mac,
245                                     src=self.src_if.remote_mac) /
246                               IP(id=7, len=21, flags="MF", frag=0, ttl=64,
247                                  src=self.src_if.remote_ip4,
248                                  dst=self.dst_if.remote_ip4) /
249                               ICMP(type="echo-request")),
250                              (Ether(dst=self.src_if.local_mac,
251                                     src=self.src_if.remote_mac) /
252                               IP(id=7, len=21, frag=1, ttl=64,
253                                  src=self.src_if.remote_ip4,
254                                  dst=self.dst_if.remote_ip4) /
255                               Raw(load='\x08')),
256                              ]
257
258         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
259              IP(id=1000, src=self.src_if.remote_ip4,
260                 dst=self.dst_if.remote_ip4) /
261              UDP(sport=1234, dport=5678) /
262              Raw("X" * 1000))
263         valid_fragments = fragment_rfc791(p, 400)
264
265         self.pg_enable_capture()
266         self.src_if.add_stream(malformed_packets + valid_fragments)
267         self.pg_start()
268
269         self.dst_if.get_capture(1)
270
271         self.assert_packet_counter_equal("ip4-reassembly-feature", 1)
272         # TODO remove above, uncomment below once clearing of counters
273         # is supported
274         # self.assert_packet_counter_equal(
275         #     "/err/ip4-reassembly-feature/malformed packets", 1)
276
277     def test_random(self):
278         """ random order reassembly """
279
280         fragments = list(self.fragments_200)
281         shuffle(fragments)
282
283         self.pg_enable_capture()
284         self.src_if.add_stream(fragments)
285         self.pg_start()
286
287         packets = self.dst_if.get_capture(len(self.packet_infos))
288         self.verify_capture(packets)
289         self.src_if.assert_nothing_captured()
290
291         # run it all again to verify correctness
292         self.pg_enable_capture()
293         self.src_if.add_stream(fragments)
294         self.pg_start()
295
296         packets = self.dst_if.get_capture(len(self.packet_infos))
297         self.verify_capture(packets)
298         self.src_if.assert_nothing_captured()
299
300     def test_duplicates(self):
301         """ duplicate fragments """
302
303         fragments = [
304             x for (_, frags, _, _) in self.pkt_infos
305             for x in frags
306             for _ in range(0, min(2, len(frags)))
307         ]
308
309         self.pg_enable_capture()
310         self.src_if.add_stream(fragments)
311         self.pg_start()
312
313         packets = self.dst_if.get_capture(len(self.pkt_infos))
314         self.verify_capture(packets)
315         self.src_if.assert_nothing_captured()
316
317     def test_overlap1(self):
318         """ overlapping fragments case #1 """
319
320         fragments = []
321         for _, _, frags_300, frags_200 in self.pkt_infos:
322             if len(frags_300) == 1:
323                 fragments.extend(frags_300)
324             else:
325                 for i, j in zip(frags_200, frags_300):
326                     fragments.extend(i)
327                     fragments.extend(j)
328
329         self.pg_enable_capture()
330         self.src_if.add_stream(fragments)
331         self.pg_start()
332
333         packets = self.dst_if.get_capture(len(self.pkt_infos))
334         self.verify_capture(packets)
335         self.src_if.assert_nothing_captured()
336
337         # run it all to verify correctness
338         self.pg_enable_capture()
339         self.src_if.add_stream(fragments)
340         self.pg_start()
341
342         packets = self.dst_if.get_capture(len(self.pkt_infos))
343         self.verify_capture(packets)
344         self.src_if.assert_nothing_captured()
345
346     def test_overlap2(self):
347         """ overlapping fragments case #2 """
348
349         fragments = []
350         for _, _, frags_300, frags_200 in self.pkt_infos:
351             if len(frags_300) == 1:
352                 fragments.extend(frags_300)
353             else:
354                 # care must be taken here so that there are no fragments
355                 # received by vpp after reassembly is finished, otherwise
356                 # new reassemblies will be started and packet generator will
357                 # freak out when it detects unfreed buffers
358                 zipped = zip(frags_300, frags_200)
359                 for i, j in zipped[:-1]:
360                     fragments.extend(i)
361                     fragments.extend(j)
362                 fragments.append(zipped[-1][0])
363
364         self.pg_enable_capture()
365         self.src_if.add_stream(fragments)
366         self.pg_start()
367
368         packets = self.dst_if.get_capture(len(self.pkt_infos))
369         self.verify_capture(packets)
370         self.src_if.assert_nothing_captured()
371
372         # run it all to verify correctness
373         self.pg_enable_capture()
374         self.src_if.add_stream(fragments)
375         self.pg_start()
376
377         packets = self.dst_if.get_capture(len(self.pkt_infos))
378         self.verify_capture(packets)
379         self.src_if.assert_nothing_captured()
380
381     def test_timeout_inline(self):
382         """ timeout (inline) """
383
384         dropped_packet_indexes = set(
385             index for (index, frags, _, _) in self.pkt_infos if len(frags) > 1
386         )
387
388         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
389                                     expire_walk_interval_ms=10000)
390
391         self.pg_enable_capture()
392         self.src_if.add_stream(self.fragments_400)
393         self.pg_start()
394
395         packets = self.dst_if.get_capture(
396             len(self.pkt_infos) - len(dropped_packet_indexes))
397         self.verify_capture(packets, dropped_packet_indexes)
398         self.src_if.assert_nothing_captured()
399
400     def test_timeout_cleanup(self):
401         """ timeout (cleanup) """
402
403         # whole packets + fragmented packets sans last fragment
404         fragments = [
405             x for (_, frags_400, _, _) in self.pkt_infos
406             for x in frags_400[:-1 if len(frags_400) > 1 else None]
407         ]
408
409         # last fragments for fragmented packets
410         fragments2 = [frags_400[-1]
411                       for (_, frags_400, _, _) in self.pkt_infos
412                       if len(frags_400) > 1]
413
414         dropped_packet_indexes = set(
415             index for (index, frags_400, _, _) in self.pkt_infos
416             if len(frags_400) > 1)
417
418         self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
419                                     expire_walk_interval_ms=50)
420
421         self.pg_enable_capture()
422         self.src_if.add_stream(fragments)
423         self.pg_start()
424
425         self.sleep(.25, "wait before sending rest of fragments")
426
427         self.src_if.add_stream(fragments2)
428         self.pg_start()
429
430         packets = self.dst_if.get_capture(
431             len(self.pkt_infos) - len(dropped_packet_indexes))
432         self.verify_capture(packets, dropped_packet_indexes)
433         self.src_if.assert_nothing_captured()
434
435     def test_disabled(self):
436         """ reassembly disabled """
437
438         dropped_packet_indexes = set(
439             index for (index, frags_400, _, _) in self.pkt_infos
440             if len(frags_400) > 1)
441
442         self.vapi.ip_reassembly_set(timeout_ms=1000, max_reassemblies=0,
443                                     expire_walk_interval_ms=10000)
444
445         self.pg_enable_capture()
446         self.src_if.add_stream(self.fragments_400)
447         self.pg_start()
448
449         packets = self.dst_if.get_capture(
450             len(self.pkt_infos) - len(dropped_packet_indexes))
451         self.verify_capture(packets, dropped_packet_indexes)
452         self.src_if.assert_nothing_captured()
453
454
455 class TestIPv6Reassembly(VppTestCase):
456     """ IPv6 Reassembly """
457
458     @classmethod
459     def setUpClass(cls):
460         super(TestIPv6Reassembly, cls).setUpClass()
461
462         cls.create_pg_interfaces([0, 1])
463         cls.src_if = cls.pg0
464         cls.dst_if = cls.pg1
465
466         # setup all interfaces
467         for i in cls.pg_interfaces:
468             i.admin_up()
469             i.config_ip6()
470             i.resolve_ndp()
471
472         # packet sizes
473         cls.packet_sizes = [64, 512, 1518, 9018]
474         cls.padding = " abcdefghijklmn"
475         cls.create_stream(cls.packet_sizes)
476         cls.create_fragments()
477
478     def setUp(self):
479         """ Test setup - force timeout on existing reassemblies """
480         super(TestIPv6Reassembly, self).setUp()
481         self.vapi.ip_reassembly_enable_disable(
482             sw_if_index=self.src_if.sw_if_index, enable_ip6=True)
483         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
484                                     expire_walk_interval_ms=10, is_ip6=1)
485         self.sleep(.25)
486         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
487                                     expire_walk_interval_ms=10000, is_ip6=1)
488         self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
489
490     def tearDown(self):
491         super(TestIPv6Reassembly, self).tearDown()
492         self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
493
494     @classmethod
495     def create_stream(cls, packet_sizes, packet_count=test_packet_count):
496         """Create input packet stream for defined interface.
497
498         :param list packet_sizes: Required packet sizes.
499         """
500         for i in range(0, packet_count):
501             info = cls.create_packet_info(cls.src_if, cls.src_if)
502             payload = cls.info_to_payload(info)
503             p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
504                  IPv6(src=cls.src_if.remote_ip6,
505                       dst=cls.dst_if.remote_ip6) /
506                  UDP(sport=1234, dport=5678) /
507                  Raw(payload))
508             size = packet_sizes[(i // 2) % len(packet_sizes)]
509             cls.extend_packet(p, size, cls.padding)
510             info.data = p
511
512     @classmethod
513     def create_fragments(cls):
514         infos = cls._packet_infos
515         cls.pkt_infos = []
516         for index, info in six.iteritems(infos):
517             p = info.data
518             # cls.logger.debug(ppp("Packet:", p.__class__(str(p))))
519             fragments_400 = fragment_rfc8200(p, info.index, 400)
520             fragments_300 = fragment_rfc8200(p, info.index, 300)
521             cls.pkt_infos.append((index, fragments_400, fragments_300))
522         cls.fragments_400 = [
523             x for _, frags, _ in cls.pkt_infos for x in frags]
524         cls.fragments_300 = [
525             x for _, _, frags in cls.pkt_infos for x in frags]
526         cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
527                          "and %s 300-byte fragments" %
528                          (len(infos), len(cls.fragments_400),
529                              len(cls.fragments_300)))
530
531     def verify_capture(self, capture, dropped_packet_indexes=[]):
532         """Verify captured packet strea .
533
534         :param list capture: Captured packet stream.
535         """
536         info = None
537         seen = set()
538         for packet in capture:
539             try:
540                 self.logger.debug(ppp("Got packet:", packet))
541                 ip = packet[IPv6]
542                 udp = packet[UDP]
543                 payload_info = self.payload_to_info(str(packet[Raw]))
544                 packet_index = payload_info.index
545                 self.assertTrue(
546                     packet_index not in dropped_packet_indexes,
547                     ppp("Packet received, but should be dropped:", packet))
548                 if packet_index in seen:
549                     raise Exception(ppp("Duplicate packet received", packet))
550                 seen.add(packet_index)
551                 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
552                 info = self._packet_infos[packet_index]
553                 self.assertTrue(info is not None)
554                 self.assertEqual(packet_index, info.index)
555                 saved_packet = info.data
556                 self.assertEqual(ip.src, saved_packet[IPv6].src)
557                 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
558                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
559             except Exception:
560                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
561                 raise
562         for index in self._packet_infos:
563             self.assertTrue(index in seen or index in dropped_packet_indexes,
564                             "Packet with packet_index %d not received" % index)
565
566     def test_reassembly(self):
567         """ basic reassembly """
568
569         self.pg_enable_capture()
570         self.src_if.add_stream(self.fragments_400)
571         self.pg_start()
572
573         packets = self.dst_if.get_capture(len(self.pkt_infos))
574         self.verify_capture(packets)
575         self.src_if.assert_nothing_captured()
576
577         # run it all again to verify correctness
578         self.pg_enable_capture()
579         self.src_if.add_stream(self.fragments_400)
580         self.pg_start()
581
582         packets = self.dst_if.get_capture(len(self.pkt_infos))
583         self.verify_capture(packets)
584         self.src_if.assert_nothing_captured()
585
586     def test_reversed(self):
587         """ reverse order reassembly """
588
589         fragments = list(self.fragments_400)
590         fragments.reverse()
591
592         self.pg_enable_capture()
593         self.src_if.add_stream(fragments)
594         self.pg_start()
595
596         packets = self.dst_if.get_capture(len(self.pkt_infos))
597         self.verify_capture(packets)
598         self.src_if.assert_nothing_captured()
599
600         # run it all again to verify correctness
601         self.pg_enable_capture()
602         self.src_if.add_stream(fragments)
603         self.pg_start()
604
605         packets = self.dst_if.get_capture(len(self.pkt_infos))
606         self.verify_capture(packets)
607         self.src_if.assert_nothing_captured()
608
609     def test_random(self):
610         """ random order reassembly """
611
612         fragments = list(self.fragments_400)
613         shuffle(fragments)
614
615         self.pg_enable_capture()
616         self.src_if.add_stream(fragments)
617         self.pg_start()
618
619         packets = self.dst_if.get_capture(len(self.pkt_infos))
620         self.verify_capture(packets)
621         self.src_if.assert_nothing_captured()
622
623         # run it all again to verify correctness
624         self.pg_enable_capture()
625         self.src_if.add_stream(fragments)
626         self.pg_start()
627
628         packets = self.dst_if.get_capture(len(self.pkt_infos))
629         self.verify_capture(packets)
630         self.src_if.assert_nothing_captured()
631
632     def test_duplicates(self):
633         """ duplicate fragments """
634
635         fragments = [
636             x for (_, frags, _) in self.pkt_infos
637             for x in frags
638             for _ in range(0, min(2, len(frags)))
639         ]
640
641         self.pg_enable_capture()
642         self.src_if.add_stream(fragments)
643         self.pg_start()
644
645         packets = self.dst_if.get_capture(len(self.pkt_infos))
646         self.verify_capture(packets)
647         self.src_if.assert_nothing_captured()
648
649     def test_overlap1(self):
650         """ overlapping fragments case #1 """
651
652         fragments = []
653         for _, frags_400, frags_300 in self.pkt_infos:
654             if len(frags_300) == 1:
655                 fragments.extend(frags_400)
656             else:
657                 for i, j in zip(frags_300, frags_400):
658                     fragments.extend(i)
659                     fragments.extend(j)
660
661         dropped_packet_indexes = set(
662             index for (index, _, frags) in self.pkt_infos if len(frags) > 1
663         )
664
665         self.pg_enable_capture()
666         self.src_if.add_stream(fragments)
667         self.pg_start()
668
669         packets = self.dst_if.get_capture(
670             len(self.pkt_infos) - len(dropped_packet_indexes))
671         self.verify_capture(packets, dropped_packet_indexes)
672         self.src_if.assert_nothing_captured()
673
674     def test_overlap2(self):
675         """ overlapping fragments case #2 """
676
677         fragments = []
678         for _, frags_400, frags_300 in self.pkt_infos:
679             if len(frags_400) == 1:
680                 fragments.extend(frags_400)
681             else:
682                 # care must be taken here so that there are no fragments
683                 # received by vpp after reassembly is finished, otherwise
684                 # new reassemblies will be started and packet generator will
685                 # freak out when it detects unfreed buffers
686                 zipped = zip(frags_400, frags_300)
687                 for i, j in zipped[:-1]:
688                     fragments.extend(i)
689                     fragments.extend(j)
690                 fragments.append(zipped[-1][0])
691
692         dropped_packet_indexes = set(
693             index for (index, _, frags) in self.pkt_infos if len(frags) > 1
694         )
695
696         self.pg_enable_capture()
697         self.src_if.add_stream(fragments)
698         self.pg_start()
699
700         packets = self.dst_if.get_capture(
701             len(self.pkt_infos) - len(dropped_packet_indexes))
702         self.verify_capture(packets, dropped_packet_indexes)
703         self.src_if.assert_nothing_captured()
704
705     def test_timeout_inline(self):
706         """ timeout (inline) """
707
708         dropped_packet_indexes = set(
709             index for (index, frags, _) in self.pkt_infos if len(frags) > 1
710         )
711
712         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
713                                     expire_walk_interval_ms=10000, is_ip6=1)
714
715         self.pg_enable_capture()
716         self.src_if.add_stream(self.fragments_400)
717         self.pg_start()
718
719         packets = self.dst_if.get_capture(
720             len(self.pkt_infos) - len(dropped_packet_indexes))
721         self.verify_capture(packets, dropped_packet_indexes)
722         pkts = self.src_if.get_capture(
723             expected_count=len(dropped_packet_indexes))
724         for icmp in pkts:
725             self.assertIn(ICMPv6TimeExceeded, icmp)
726             self.assertIn(IPv6ExtHdrFragment, icmp)
727             self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
728             dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
729
730     def test_timeout_cleanup(self):
731         """ timeout (cleanup) """
732
733         # whole packets + fragmented packets sans last fragment
734         fragments = [
735             x for (_, frags_400, _) in self.pkt_infos
736             for x in frags_400[:-1 if len(frags_400) > 1 else None]
737         ]
738
739         # last fragments for fragmented packets
740         fragments2 = [frags_400[-1]
741                       for (_, frags_400, _) in self.pkt_infos
742                       if len(frags_400) > 1]
743
744         dropped_packet_indexes = set(
745             index for (index, frags_400, _) in self.pkt_infos
746             if len(frags_400) > 1)
747
748         self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
749                                     expire_walk_interval_ms=50)
750
751         self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
752                                     expire_walk_interval_ms=50, is_ip6=1)
753
754         self.pg_enable_capture()
755         self.src_if.add_stream(fragments)
756         self.pg_start()
757
758         self.sleep(.25, "wait before sending rest of fragments")
759
760         self.src_if.add_stream(fragments2)
761         self.pg_start()
762
763         packets = self.dst_if.get_capture(
764             len(self.pkt_infos) - len(dropped_packet_indexes))
765         self.verify_capture(packets, dropped_packet_indexes)
766         pkts = self.src_if.get_capture(
767             expected_count=len(dropped_packet_indexes))
768         for icmp in pkts:
769             self.assertIn(ICMPv6TimeExceeded, icmp)
770             self.assertIn(IPv6ExtHdrFragment, icmp)
771             self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
772             dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
773
774     def test_disabled(self):
775         """ reassembly disabled """
776
777         dropped_packet_indexes = set(
778             index for (index, frags_400, _) in self.pkt_infos
779             if len(frags_400) > 1)
780
781         self.vapi.ip_reassembly_set(timeout_ms=1000, max_reassemblies=0,
782                                     expire_walk_interval_ms=10000, is_ip6=1)
783
784         self.pg_enable_capture()
785         self.src_if.add_stream(self.fragments_400)
786         self.pg_start()
787
788         packets = self.dst_if.get_capture(
789             len(self.pkt_infos) - len(dropped_packet_indexes))
790         self.verify_capture(packets, dropped_packet_indexes)
791         self.src_if.assert_nothing_captured()
792
793     def test_missing_upper(self):
794         """ missing upper layer """
795         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
796              IPv6(src=self.src_if.remote_ip6,
797                   dst=self.src_if.local_ip6) /
798              UDP(sport=1234, dport=5678) /
799              Raw())
800         self.extend_packet(p, 1000, self.padding)
801         fragments = fragment_rfc8200(p, 1, 500)
802         bad_fragment = p.__class__(str(fragments[1]))
803         bad_fragment[IPv6ExtHdrFragment].nh = 59
804         bad_fragment[IPv6ExtHdrFragment].offset = 0
805         self.pg_enable_capture()
806         self.src_if.add_stream([bad_fragment])
807         self.pg_start()
808         pkts = self.src_if.get_capture(expected_count=1)
809         icmp = pkts[0]
810         self.assertIn(ICMPv6ParamProblem, icmp)
811         self.assert_equal(icmp[ICMPv6ParamProblem].code, 3, "ICMP code")
812
813     def test_invalid_frag_size(self):
814         """ fragment size not a multiple of 8 """
815         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
816              IPv6(src=self.src_if.remote_ip6,
817                   dst=self.src_if.local_ip6) /
818              UDP(sport=1234, dport=5678) /
819              Raw())
820         self.extend_packet(p, 1000, self.padding)
821         fragments = fragment_rfc8200(p, 1, 500)
822         bad_fragment = fragments[0]
823         self.extend_packet(bad_fragment, len(bad_fragment) + 5)
824         self.pg_enable_capture()
825         self.src_if.add_stream([bad_fragment])
826         self.pg_start()
827         pkts = self.src_if.get_capture(expected_count=1)
828         icmp = pkts[0]
829         self.assertIn(ICMPv6ParamProblem, icmp)
830         self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
831
832     def test_invalid_packet_size(self):
833         """ total packet size > 65535 """
834         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
835              IPv6(src=self.src_if.remote_ip6,
836                   dst=self.src_if.local_ip6) /
837              UDP(sport=1234, dport=5678) /
838              Raw())
839         self.extend_packet(p, 1000, self.padding)
840         fragments = fragment_rfc8200(p, 1, 500)
841         bad_fragment = fragments[1]
842         bad_fragment[IPv6ExtHdrFragment].offset = 65500
843         self.pg_enable_capture()
844         self.src_if.add_stream([bad_fragment])
845         self.pg_start()
846         pkts = self.src_if.get_capture(expected_count=1)
847         icmp = pkts[0]
848         self.assertIn(ICMPv6ParamProblem, icmp)
849         self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
850
851
852 class TestIPv4ReassemblyLocalNode(VppTestCase):
853     """ IPv4 Reassembly for packets coming to ip4-local node """
854
855     @classmethod
856     def setUpClass(cls):
857         super(TestIPv4ReassemblyLocalNode, cls).setUpClass()
858
859         cls.create_pg_interfaces([0])
860         cls.src_dst_if = cls.pg0
861
862         # setup all interfaces
863         for i in cls.pg_interfaces:
864             i.admin_up()
865             i.config_ip4()
866             i.resolve_arp()
867
868         cls.padding = " abcdefghijklmn"
869         cls.create_stream()
870         cls.create_fragments()
871
872     def setUp(self):
873         """ Test setup - force timeout on existing reassemblies """
874         super(TestIPv4ReassemblyLocalNode, self).setUp()
875         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
876                                     expire_walk_interval_ms=10)
877         self.sleep(.25)
878         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
879                                     expire_walk_interval_ms=10000)
880
881     def tearDown(self):
882         super(TestIPv4ReassemblyLocalNode, self).tearDown()
883         self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
884
885     @classmethod
886     def create_stream(cls, packet_count=test_packet_count):
887         """Create input packet stream for defined interface.
888
889         :param list packet_sizes: Required packet sizes.
890         """
891         for i in range(0, packet_count):
892             info = cls.create_packet_info(cls.src_dst_if, cls.src_dst_if)
893             payload = cls.info_to_payload(info)
894             p = (Ether(dst=cls.src_dst_if.local_mac,
895                        src=cls.src_dst_if.remote_mac) /
896                  IP(id=info.index, src=cls.src_dst_if.remote_ip4,
897                     dst=cls.src_dst_if.local_ip4) /
898                  ICMP(type='echo-request', id=1234) /
899                  Raw(payload))
900             cls.extend_packet(p, 1518, cls.padding)
901             info.data = p
902
903     @classmethod
904     def create_fragments(cls):
905         infos = cls._packet_infos
906         cls.pkt_infos = []
907         for index, info in six.iteritems(infos):
908             p = info.data
909             # cls.logger.debug(ppp("Packet:", p.__class__(str(p))))
910             fragments_300 = fragment_rfc791(p, 300)
911             cls.pkt_infos.append((index, fragments_300))
912         cls.fragments_300 = [x for (_, frags) in cls.pkt_infos for x in frags]
913         cls.logger.debug("Fragmented %s packets into %s 300-byte fragments" %
914                          (len(infos), len(cls.fragments_300)))
915
916     def verify_capture(self, capture):
917         """Verify captured packet stream.
918
919         :param list capture: Captured packet stream.
920         """
921         info = None
922         seen = set()
923         for packet in capture:
924             try:
925                 self.logger.debug(ppp("Got packet:", packet))
926                 ip = packet[IP]
927                 icmp = packet[ICMP]
928                 payload_info = self.payload_to_info(str(packet[Raw]))
929                 packet_index = payload_info.index
930                 if packet_index in seen:
931                     raise Exception(ppp("Duplicate packet received", packet))
932                 seen.add(packet_index)
933                 self.assertEqual(payload_info.dst, self.src_dst_if.sw_if_index)
934                 info = self._packet_infos[packet_index]
935                 self.assertIsNotNone(info)
936                 self.assertEqual(packet_index, info.index)
937                 saved_packet = info.data
938                 self.assertEqual(ip.src, saved_packet[IP].dst)
939                 self.assertEqual(ip.dst, saved_packet[IP].src)
940                 self.assertEqual(icmp.type, 0)  # echo reply
941                 self.assertEqual(icmp.id, saved_packet[ICMP].id)
942                 self.assertEqual(icmp.payload, saved_packet[ICMP].payload)
943             except Exception:
944                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
945                 raise
946         for index in self._packet_infos:
947             self.assertIn(index, seen,
948                           "Packet with packet_index %d not received" % index)
949
950     def test_reassembly(self):
951         """ basic reassembly """
952
953         self.pg_enable_capture()
954         self.src_dst_if.add_stream(self.fragments_300)
955         self.pg_start()
956
957         packets = self.src_dst_if.get_capture(len(self.pkt_infos))
958         self.verify_capture(packets)
959
960         # run it all again to verify correctness
961         self.pg_enable_capture()
962         self.src_dst_if.add_stream(self.fragments_300)
963         self.pg_start()
964
965         packets = self.src_dst_if.get_capture(len(self.pkt_infos))
966         self.verify_capture(packets)
967
968
969 class TestFIFReassembly(VppTestCase):
970     """ Fragments in fragments reassembly """
971
972     @classmethod
973     def setUpClass(cls):
974         super(TestFIFReassembly, cls).setUpClass()
975
976         cls.create_pg_interfaces([0, 1])
977         cls.src_if = cls.pg0
978         cls.dst_if = cls.pg1
979         for i in cls.pg_interfaces:
980             i.admin_up()
981             i.config_ip4()
982             i.resolve_arp()
983             i.config_ip6()
984             i.resolve_ndp()
985
986         cls.packet_sizes = [64, 512, 1518, 9018]
987         cls.padding = " abcdefghijklmn"
988
989     def setUp(self):
990         """ Test setup - force timeout on existing reassemblies """
991         super(TestFIFReassembly, self).setUp()
992         self.vapi.ip_reassembly_enable_disable(
993             sw_if_index=self.src_if.sw_if_index, enable_ip4=True,
994             enable_ip6=True)
995         self.vapi.ip_reassembly_enable_disable(
996             sw_if_index=self.dst_if.sw_if_index, enable_ip4=True,
997             enable_ip6=True)
998         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
999                                     expire_walk_interval_ms=10)
1000         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1001                                     expire_walk_interval_ms=10, is_ip6=1)
1002         self.sleep(.25)
1003         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1004                                     expire_walk_interval_ms=10000)
1005         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1006                                     expire_walk_interval_ms=10000, is_ip6=1)
1007
1008     def tearDown(self):
1009         self.logger.debug(self.vapi.ppcli("show ip4-reassembly details"))
1010         self.logger.debug(self.vapi.ppcli("show ip6-reassembly details"))
1011         super(TestFIFReassembly, self).tearDown()
1012
1013     def verify_capture(self, capture, ip_class, dropped_packet_indexes=[]):
1014         """Verify captured packet stream.
1015
1016         :param list capture: Captured packet stream.
1017         """
1018         info = None
1019         seen = set()
1020         for packet in capture:
1021             try:
1022                 self.logger.debug(ppp("Got packet:", packet))
1023                 ip = packet[ip_class]
1024                 udp = packet[UDP]
1025                 payload_info = self.payload_to_info(str(packet[Raw]))
1026                 packet_index = payload_info.index
1027                 self.assertTrue(
1028                     packet_index not in dropped_packet_indexes,
1029                     ppp("Packet received, but should be dropped:", packet))
1030                 if packet_index in seen:
1031                     raise Exception(ppp("Duplicate packet received", packet))
1032                 seen.add(packet_index)
1033                 self.assertEqual(payload_info.dst, self.dst_if.sw_if_index)
1034                 info = self._packet_infos[packet_index]
1035                 self.assertTrue(info is not None)
1036                 self.assertEqual(packet_index, info.index)
1037                 saved_packet = info.data
1038                 self.assertEqual(ip.src, saved_packet[ip_class].src)
1039                 self.assertEqual(ip.dst, saved_packet[ip_class].dst)
1040                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1041             except Exception:
1042                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1043                 raise
1044         for index in self._packet_infos:
1045             self.assertTrue(index in seen or index in dropped_packet_indexes,
1046                             "Packet with packet_index %d not received" % index)
1047
1048     def test_fif4(self):
1049         """ Fragments in fragments (4o4) """
1050
1051         # TODO this should be ideally in setUpClass, but then we hit a bug
1052         # with VppIpRoute incorrectly reporting it's present when it's not
1053         # so we need to manually remove the vpp config, thus we cannot have
1054         # it shared for multiple test cases
1055         self.tun_ip4 = "1.1.1.2"
1056
1057         self.gre4 = VppGreInterface(self, self.src_if.local_ip4, self.tun_ip4)
1058         self.gre4.add_vpp_config()
1059         self.gre4.admin_up()
1060         self.gre4.config_ip4()
1061
1062         self.vapi.ip_reassembly_enable_disable(
1063             sw_if_index=self.gre4.sw_if_index, enable_ip4=True)
1064
1065         self.route4 = VppIpRoute(self, self.tun_ip4, 32,
1066                                  [VppRoutePath(self.src_if.remote_ip4,
1067                                                self.src_if.sw_if_index)])
1068         self.route4.add_vpp_config()
1069
1070         self.reset_packet_infos()
1071         for i in range(test_packet_count):
1072             info = self.create_packet_info(self.src_if, self.dst_if)
1073             payload = self.info_to_payload(info)
1074             # Ethernet header here is only for size calculation, thus it
1075             # doesn't matter how it's initialized. This is to ensure that
1076             # reassembled packet is not > 9000 bytes, so that it's not dropped
1077             p = (Ether() /
1078                  IP(id=i, src=self.src_if.remote_ip4,
1079                     dst=self.dst_if.remote_ip4) /
1080                  UDP(sport=1234, dport=5678) /
1081                  Raw(payload))
1082             size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
1083             self.extend_packet(p, size, self.padding)
1084             info.data = p[IP]  # use only IP part, without ethernet header
1085
1086         fragments = [x for _, p in six.iteritems(self._packet_infos)
1087                      for x in fragment_rfc791(p.data, 400)]
1088
1089         encapped_fragments = \
1090             [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1091              IP(src=self.tun_ip4, dst=self.src_if.local_ip4) /
1092                 GRE() /
1093                 p
1094                 for p in fragments]
1095
1096         fragmented_encapped_fragments = \
1097             [x for p in encapped_fragments
1098              for x in fragment_rfc791(p, 200)]
1099
1100         self.src_if.add_stream(fragmented_encapped_fragments)
1101
1102         self.pg_enable_capture(self.pg_interfaces)
1103         self.pg_start()
1104
1105         self.src_if.assert_nothing_captured()
1106         packets = self.dst_if.get_capture(len(self._packet_infos))
1107         self.verify_capture(packets, IP)
1108
1109         # TODO remove gre vpp config by hand until VppIpRoute gets fixed
1110         # so that it's query_vpp_config() works as it should
1111         self.gre4.remove_vpp_config()
1112         self.logger.debug(self.vapi.ppcli("show interface"))
1113
1114     def test_fif6(self):
1115         """ Fragments in fragments (6o6) """
1116         # TODO this should be ideally in setUpClass, but then we hit a bug
1117         # with VppIpRoute incorrectly reporting it's present when it's not
1118         # so we need to manually remove the vpp config, thus we cannot have
1119         # it shared for multiple test cases
1120         self.tun_ip6 = "1002::1"
1121
1122         self.gre6 = VppGre6Interface(self, self.src_if.local_ip6, self.tun_ip6)
1123         self.gre6.add_vpp_config()
1124         self.gre6.admin_up()
1125         self.gre6.config_ip6()
1126
1127         self.vapi.ip_reassembly_enable_disable(
1128             sw_if_index=self.gre6.sw_if_index, enable_ip6=True)
1129
1130         self.route6 = VppIpRoute(self, self.tun_ip6, 128,
1131                                  [VppRoutePath(self.src_if.remote_ip6,
1132                                                self.src_if.sw_if_index,
1133                                                proto=DpoProto.DPO_PROTO_IP6)],
1134                                  is_ip6=1)
1135         self.route6.add_vpp_config()
1136
1137         self.reset_packet_infos()
1138         for i in range(test_packet_count):
1139             info = self.create_packet_info(self.src_if, self.dst_if)
1140             payload = self.info_to_payload(info)
1141             # Ethernet header here is only for size calculation, thus it
1142             # doesn't matter how it's initialized. This is to ensure that
1143             # reassembled packet is not > 9000 bytes, so that it's not dropped
1144             p = (Ether() /
1145                  IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1146                  UDP(sport=1234, dport=5678) /
1147                  Raw(payload))
1148             size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
1149             self.extend_packet(p, size, self.padding)
1150             info.data = p[IPv6]  # use only IPv6 part, without ethernet header
1151
1152         fragments = [x for _, i in six.iteritems(self._packet_infos)
1153                      for x in fragment_rfc8200(
1154                          i.data, i.index, 400)]
1155
1156         encapped_fragments = \
1157             [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1158              IPv6(src=self.tun_ip6, dst=self.src_if.local_ip6) /
1159                 GRE() /
1160                 p
1161                 for p in fragments]
1162
1163         fragmented_encapped_fragments = \
1164             [x for p in encapped_fragments for x in (
1165                 fragment_rfc8200(
1166                     p,
1167                     2 * len(self._packet_infos) + p[IPv6ExtHdrFragment].id,
1168                     200)
1169                 if IPv6ExtHdrFragment in p else [p]
1170             )
1171             ]
1172
1173         self.src_if.add_stream(fragmented_encapped_fragments)
1174
1175         self.pg_enable_capture(self.pg_interfaces)
1176         self.pg_start()
1177
1178         self.src_if.assert_nothing_captured()
1179         packets = self.dst_if.get_capture(len(self._packet_infos))
1180         self.verify_capture(packets, IPv6)
1181
1182         # TODO remove gre vpp config by hand until VppIpRoute gets fixed
1183         # so that it's query_vpp_config() works as it should
1184         self.gre6.remove_vpp_config()
1185
1186
1187 if __name__ == '__main__':
1188     unittest.main(testRunner=VppTestRunner)