ip: Router ID included in flow hash
[vpp.git] / test / test_reassembly.py
1 #!/usr/bin/env python3
2
3 import unittest
4 from random import shuffle, choice, randrange
5
6 from framework import VppTestCase, VppTestRunner
7
8 import scapy.compat
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 scapy.layers.inet6 import HBHOptUnknown, ICMPv6ParamProblem,\
13     ICMPv6TimeExceeded, IPv6, IPv6ExtHdrFragment, IPv6ExtHdrHopByHop
14 from framework import VppTestCase, VppTestRunner
15 from util import ppp, ppc, fragment_rfc791, fragment_rfc8200
16 from vpp_gre_interface import VppGreInterface
17 from vpp_ip import DpoProto
18 from vpp_ip_route import VppIpRoute, VppRoutePath, FibPathProto
19 from vpp_papi import VppEnum
20
21 # 35 is enough to have >257 400-byte fragments
22 test_packet_count = 35
23
24 # number of workers used for multi-worker test cases
25 worker_count = 3
26
27
28 class TestIPv4Reassembly(VppTestCase):
29     """ IPv4 Reassembly """
30
31     @classmethod
32     def setUpClass(cls):
33         super(TestIPv4Reassembly, cls).setUpClass()
34
35         cls.create_pg_interfaces([0, 1])
36         cls.src_if = cls.pg0
37         cls.dst_if = cls.pg1
38
39         # setup all interfaces
40         for i in cls.pg_interfaces:
41             i.admin_up()
42             i.config_ip4()
43             i.resolve_arp()
44
45         # packet sizes
46         cls.packet_sizes = [64, 512, 1518, 9018]
47         cls.padding = " abcdefghijklmn"
48         cls.create_stream(cls.packet_sizes)
49         cls.create_fragments()
50
51     @classmethod
52     def tearDownClass(cls):
53         super(TestIPv4Reassembly, cls).tearDownClass()
54
55     def setUp(self):
56         """ Test setup - force timeout on existing reassemblies """
57         super(TestIPv4Reassembly, self).setUp()
58         self.vapi.ip_reassembly_enable_disable(
59             sw_if_index=self.src_if.sw_if_index, enable_ip4=True)
60         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
61                                     max_reassembly_length=1000,
62                                     expire_walk_interval_ms=10)
63         self.sleep(.25)
64         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
65                                     max_reassembly_length=1000,
66                                     expire_walk_interval_ms=10000)
67
68     def tearDown(self):
69         super(TestIPv4Reassembly, self).tearDown()
70
71     def show_commands_at_teardown(self):
72         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
73         self.logger.debug(self.vapi.ppcli("show buffers"))
74
75     @classmethod
76     def create_stream(cls, packet_sizes, packet_count=test_packet_count):
77         """Create input packet stream
78
79         :param list packet_sizes: Required packet sizes.
80         """
81         for i in range(0, packet_count):
82             info = cls.create_packet_info(cls.src_if, cls.src_if)
83             payload = cls.info_to_payload(info)
84             p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
85                  IP(id=info.index, src=cls.src_if.remote_ip4,
86                     dst=cls.dst_if.remote_ip4) /
87                  UDP(sport=1234, dport=5678) /
88                  Raw(payload))
89             size = packet_sizes[(i // 2) % len(packet_sizes)]
90             cls.extend_packet(p, size, cls.padding)
91             info.data = p
92
93     @classmethod
94     def create_fragments(cls):
95         infos = cls._packet_infos
96         cls.pkt_infos = []
97         for index, info in infos.items():
98             p = info.data
99             # cls.logger.debug(ppp("Packet:",
100             #                      p.__class__(scapy.compat.raw(p))))
101             fragments_400 = fragment_rfc791(p, 400)
102             fragments_300 = fragment_rfc791(p, 300)
103             fragments_200 = [
104                 x for f in fragments_400 for x in fragment_rfc791(f, 200)]
105             cls.pkt_infos.append(
106                 (index, fragments_400, fragments_300, fragments_200))
107         cls.fragments_400 = [
108             x for (_, frags, _, _) in cls.pkt_infos for x in frags]
109         cls.fragments_300 = [
110             x for (_, _, frags, _) in cls.pkt_infos for x in frags]
111         cls.fragments_200 = [
112             x for (_, _, _, frags) in cls.pkt_infos for x in frags]
113         cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
114                          "%s 300-byte fragments and %s 200-byte fragments" %
115                          (len(infos), len(cls.fragments_400),
116                              len(cls.fragments_300), len(cls.fragments_200)))
117
118     def verify_capture(self, capture, dropped_packet_indexes=[]):
119         """Verify captured packet stream.
120
121         :param list capture: Captured packet stream.
122         """
123         info = None
124         seen = set()
125         for packet in capture:
126             try:
127                 self.logger.debug(ppp("Got packet:", packet))
128                 ip = packet[IP]
129                 udp = packet[UDP]
130                 payload_info = self.payload_to_info(packet[Raw])
131                 packet_index = payload_info.index
132                 self.assertTrue(
133                     packet_index not in dropped_packet_indexes,
134                     ppp("Packet received, but should be dropped:", packet))
135                 if packet_index in seen:
136                     raise Exception(ppp("Duplicate packet received", packet))
137                 seen.add(packet_index)
138                 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
139                 info = self._packet_infos[packet_index]
140                 self.assertTrue(info is not None)
141                 self.assertEqual(packet_index, info.index)
142                 saved_packet = info.data
143                 self.assertEqual(ip.src, saved_packet[IP].src)
144                 self.assertEqual(ip.dst, saved_packet[IP].dst)
145                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
146             except Exception:
147                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
148                 raise
149         for index in self._packet_infos:
150             self.assertTrue(index in seen or index in dropped_packet_indexes,
151                             "Packet with packet_index %d not received" % index)
152
153     def test_reassembly(self):
154         """ basic reassembly """
155
156         self.pg_enable_capture()
157         self.src_if.add_stream(self.fragments_200)
158         self.pg_start()
159
160         packets = self.dst_if.get_capture(len(self.pkt_infos))
161         self.verify_capture(packets)
162         self.src_if.assert_nothing_captured()
163
164         # run it all again to verify correctness
165         self.pg_enable_capture()
166         self.src_if.add_stream(self.fragments_200)
167         self.pg_start()
168
169         packets = self.dst_if.get_capture(len(self.pkt_infos))
170         self.verify_capture(packets)
171         self.src_if.assert_nothing_captured()
172
173     def test_verify_clear_trace_mid_reassembly(self):
174         """ verify clear trace works mid-reassembly """
175
176         self.pg_enable_capture()
177         self.src_if.add_stream(self.fragments_200[0:-1])
178         self.pg_start()
179
180         self.logger.debug(self.vapi.cli("show trace"))
181         self.vapi.cli("clear trace")
182
183         self.src_if.add_stream(self.fragments_200[-1])
184         self.pg_start()
185         packets = self.dst_if.get_capture(len(self.pkt_infos))
186         self.verify_capture(packets)
187
188     def test_reversed(self):
189         """ reverse order reassembly """
190
191         fragments = list(self.fragments_200)
192         fragments.reverse()
193
194         self.pg_enable_capture()
195         self.src_if.add_stream(fragments)
196         self.pg_start()
197
198         packets = self.dst_if.get_capture(len(self.packet_infos))
199         self.verify_capture(packets)
200         self.src_if.assert_nothing_captured()
201
202         # run it all again to verify correctness
203         self.pg_enable_capture()
204         self.src_if.add_stream(fragments)
205         self.pg_start()
206
207         packets = self.dst_if.get_capture(len(self.packet_infos))
208         self.verify_capture(packets)
209         self.src_if.assert_nothing_captured()
210
211     def test_long_fragment_chain(self):
212         """ long fragment chain """
213
214         error_cnt_str = \
215             "/err/ip4-full-reassembly-feature/fragment chain too long (drop)"
216
217         error_cnt = self.statistics.get_err_counter(error_cnt_str)
218
219         self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
220                                     max_reassembly_length=3,
221                                     expire_walk_interval_ms=50)
222
223         p1 = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
224               IP(id=1000, src=self.src_if.remote_ip4,
225                  dst=self.dst_if.remote_ip4) /
226               UDP(sport=1234, dport=5678) /
227               Raw(b"X" * 1000))
228         p2 = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
229               IP(id=1001, src=self.src_if.remote_ip4,
230                  dst=self.dst_if.remote_ip4) /
231               UDP(sport=1234, dport=5678) /
232               Raw(b"X" * 1000))
233         frags = fragment_rfc791(p1, 200) + fragment_rfc791(p2, 500)
234
235         self.pg_enable_capture()
236         self.src_if.add_stream(frags)
237         self.pg_start()
238
239         self.dst_if.get_capture(1)
240         self.assert_error_counter_equal(error_cnt_str, error_cnt + 1)
241
242     def test_5737(self):
243         """ fragment length + ip header size > 65535 """
244         self.vapi.cli("clear errors")
245         raw = b'''E\x00\x00\x88,\xf8\x1f\xfe@\x01\x98\x00\xc0\xa8\n-\xc0\xa8\n\
246 \x01\x08\x00\xf0J\xed\xcb\xf1\xf5Test-group: IPv4.IPv4.ipv4-message.\
247 Ethernet-Payload.IPv4-Packet.IPv4-Header.Fragment-Offset; Test-case: 5737'''
248         malformed_packet = (Ether(dst=self.src_if.local_mac,
249                                   src=self.src_if.remote_mac) /
250                             IP(raw))
251         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
252              IP(id=1000, src=self.src_if.remote_ip4,
253                 dst=self.dst_if.remote_ip4) /
254              UDP(sport=1234, dport=5678) /
255              Raw(b"X" * 1000))
256         valid_fragments = fragment_rfc791(p, 400)
257
258         counter = "/err/ip4-full-reassembly-feature/malformed packets"
259         error_counter = self.statistics.get_err_counter(counter)
260         self.pg_enable_capture()
261         self.src_if.add_stream([malformed_packet] + valid_fragments)
262         self.pg_start()
263
264         self.dst_if.get_capture(1)
265         self.logger.debug(self.vapi.ppcli("show error"))
266         self.assertEqual(self.statistics.get_err_counter(counter),
267                          error_counter + 1)
268
269     def test_44924(self):
270         """ compress tiny fragments """
271         packets = [(Ether(dst=self.src_if.local_mac,
272                           src=self.src_if.remote_mac) /
273                     IP(id=24339, flags="MF", frag=0, ttl=64,
274                        src=self.src_if.remote_ip4,
275                        dst=self.dst_if.remote_ip4) /
276                     ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
277                     Raw(load='Test-group: IPv4')),
278                    (Ether(dst=self.src_if.local_mac,
279                           src=self.src_if.remote_mac) /
280                     IP(id=24339, flags="MF", frag=3, ttl=64,
281                        src=self.src_if.remote_ip4,
282                        dst=self.dst_if.remote_ip4) /
283                     ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
284                     Raw(load='.IPv4.Fragmentation.vali')),
285                    (Ether(dst=self.src_if.local_mac,
286                           src=self.src_if.remote_mac) /
287                     IP(id=24339, frag=6, ttl=64,
288                        src=self.src_if.remote_ip4,
289                        dst=self.dst_if.remote_ip4) /
290                     ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
291                     Raw(load='d; Test-case: 44924'))
292                    ]
293
294         self.pg_enable_capture()
295         self.src_if.add_stream(packets)
296         self.pg_start()
297
298         self.dst_if.get_capture(1)
299
300     def test_frag_1(self):
301         """ fragment of size 1 """
302         self.vapi.cli("clear errors")
303         malformed_packets = [(Ether(dst=self.src_if.local_mac,
304                                     src=self.src_if.remote_mac) /
305                               IP(id=7, len=21, flags="MF", frag=0, ttl=64,
306                                  src=self.src_if.remote_ip4,
307                                  dst=self.dst_if.remote_ip4) /
308                               ICMP(type="echo-request")),
309                              (Ether(dst=self.src_if.local_mac,
310                                     src=self.src_if.remote_mac) /
311                               IP(id=7, len=21, frag=1, ttl=64,
312                                  src=self.src_if.remote_ip4,
313                                  dst=self.dst_if.remote_ip4) /
314                               Raw(load=b'\x08')),
315                              ]
316
317         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
318              IP(id=1000, src=self.src_if.remote_ip4,
319                 dst=self.dst_if.remote_ip4) /
320              UDP(sport=1234, dport=5678) /
321              Raw(b"X" * 1000))
322         valid_fragments = fragment_rfc791(p, 400)
323
324         self.pg_enable_capture()
325         self.src_if.add_stream(malformed_packets + valid_fragments)
326         self.pg_start()
327
328         self.dst_if.get_capture(1)
329
330         self.assert_packet_counter_equal("ip4-full-reassembly-feature", 1)
331         # TODO remove above, uncomment below once clearing of counters
332         # is supported
333         # self.assert_packet_counter_equal(
334         #     "/err/ip4-full-reassembly-feature/malformed packets", 1)
335
336     def test_random(self):
337         """ random order reassembly """
338
339         fragments = list(self.fragments_200)
340         shuffle(fragments)
341
342         self.pg_enable_capture()
343         self.src_if.add_stream(fragments)
344         self.pg_start()
345
346         packets = self.dst_if.get_capture(len(self.packet_infos))
347         self.verify_capture(packets)
348         self.src_if.assert_nothing_captured()
349
350         # run it all again to verify correctness
351         self.pg_enable_capture()
352         self.src_if.add_stream(fragments)
353         self.pg_start()
354
355         packets = self.dst_if.get_capture(len(self.packet_infos))
356         self.verify_capture(packets)
357         self.src_if.assert_nothing_captured()
358
359     def test_duplicates(self):
360         """ duplicate fragments """
361
362         fragments = [
363             x for (_, frags, _, _) in self.pkt_infos
364             for x in frags
365             for _ in range(0, min(2, len(frags)))
366         ]
367
368         self.pg_enable_capture()
369         self.src_if.add_stream(fragments)
370         self.pg_start()
371
372         packets = self.dst_if.get_capture(len(self.pkt_infos))
373         self.verify_capture(packets)
374         self.src_if.assert_nothing_captured()
375
376     def test_overlap1(self):
377         """ overlapping fragments case #1 """
378
379         fragments = []
380         for _, _, frags_300, frags_200 in self.pkt_infos:
381             if len(frags_300) == 1:
382                 fragments.extend(frags_300)
383             else:
384                 for i, j in zip(frags_200, frags_300):
385                     fragments.extend(i)
386                     fragments.extend(j)
387
388         self.pg_enable_capture()
389         self.src_if.add_stream(fragments)
390         self.pg_start()
391
392         packets = self.dst_if.get_capture(len(self.pkt_infos))
393         self.verify_capture(packets)
394         self.src_if.assert_nothing_captured()
395
396         # run it all to verify correctness
397         self.pg_enable_capture()
398         self.src_if.add_stream(fragments)
399         self.pg_start()
400
401         packets = self.dst_if.get_capture(len(self.pkt_infos))
402         self.verify_capture(packets)
403         self.src_if.assert_nothing_captured()
404
405     def test_overlap2(self):
406         """ overlapping fragments case #2 """
407
408         fragments = []
409         for _, _, frags_300, frags_200 in self.pkt_infos:
410             if len(frags_300) == 1:
411                 fragments.extend(frags_300)
412             else:
413                 # care must be taken here so that there are no fragments
414                 # received by vpp after reassembly is finished, otherwise
415                 # new reassemblies will be started and packet generator will
416                 # freak out when it detects unfreed buffers
417                 zipped = zip(frags_300, frags_200)
418                 for i, j in zipped:
419                     fragments.extend(i)
420                     fragments.extend(j)
421                 fragments.pop()
422
423         self.pg_enable_capture()
424         self.src_if.add_stream(fragments)
425         self.pg_start()
426
427         packets = self.dst_if.get_capture(len(self.pkt_infos))
428         self.verify_capture(packets)
429         self.src_if.assert_nothing_captured()
430
431         # run it all to verify correctness
432         self.pg_enable_capture()
433         self.src_if.add_stream(fragments)
434         self.pg_start()
435
436         packets = self.dst_if.get_capture(len(self.pkt_infos))
437         self.verify_capture(packets)
438         self.src_if.assert_nothing_captured()
439
440     def test_timeout_inline(self):
441         """ timeout (inline) """
442
443         dropped_packet_indexes = set(
444             index for (index, frags, _, _) in self.pkt_infos if len(frags) > 1
445         )
446
447         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
448                                     max_reassembly_length=3,
449                                     expire_walk_interval_ms=10000)
450
451         self.pg_enable_capture()
452         self.src_if.add_stream(self.fragments_400)
453         self.pg_start()
454
455         packets = self.dst_if.get_capture(
456             len(self.pkt_infos) - len(dropped_packet_indexes))
457         self.verify_capture(packets, dropped_packet_indexes)
458         self.src_if.assert_nothing_captured()
459
460     def test_timeout_cleanup(self):
461         """ timeout (cleanup) """
462
463         # whole packets + fragmented packets sans last fragment
464         fragments = [
465             x for (_, frags_400, _, _) in self.pkt_infos
466             for x in frags_400[:-1 if len(frags_400) > 1 else None]
467         ]
468
469         # last fragments for fragmented packets
470         fragments2 = [frags_400[-1]
471                       for (_, frags_400, _, _) in self.pkt_infos
472                       if len(frags_400) > 1]
473
474         dropped_packet_indexes = set(
475             index for (index, frags_400, _, _) in self.pkt_infos
476             if len(frags_400) > 1)
477
478         self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
479                                     max_reassembly_length=1000,
480                                     expire_walk_interval_ms=50)
481
482         self.pg_enable_capture()
483         self.src_if.add_stream(fragments)
484         self.pg_start()
485
486         self.sleep(.25, "wait before sending rest of fragments")
487
488         self.src_if.add_stream(fragments2)
489         self.pg_start()
490
491         packets = self.dst_if.get_capture(
492             len(self.pkt_infos) - len(dropped_packet_indexes))
493         self.verify_capture(packets, dropped_packet_indexes)
494         self.src_if.assert_nothing_captured()
495
496     def test_disabled(self):
497         """ reassembly disabled """
498
499         dropped_packet_indexes = set(
500             index for (index, frags_400, _, _) in self.pkt_infos
501             if len(frags_400) > 1)
502
503         self.vapi.ip_reassembly_set(timeout_ms=1000, max_reassemblies=0,
504                                     max_reassembly_length=3,
505                                     expire_walk_interval_ms=10000)
506
507         self.pg_enable_capture()
508         self.src_if.add_stream(self.fragments_400)
509         self.pg_start()
510
511         packets = self.dst_if.get_capture(
512             len(self.pkt_infos) - len(dropped_packet_indexes))
513         self.verify_capture(packets, dropped_packet_indexes)
514         self.src_if.assert_nothing_captured()
515
516
517 class TestIPv4SVReassembly(VppTestCase):
518     """ IPv4 Shallow Virtual Reassembly """
519
520     @classmethod
521     def setUpClass(cls):
522         super(TestIPv4SVReassembly, cls).setUpClass()
523
524         cls.create_pg_interfaces([0, 1])
525         cls.src_if = cls.pg0
526         cls.dst_if = cls.pg1
527
528         # setup all interfaces
529         for i in cls.pg_interfaces:
530             i.admin_up()
531             i.config_ip4()
532             i.resolve_arp()
533
534     def setUp(self):
535         """ Test setup - force timeout on existing reassemblies """
536         super(TestIPv4SVReassembly, self).setUp()
537         self.vapi.ip_reassembly_enable_disable(
538             sw_if_index=self.src_if.sw_if_index, enable_ip4=True,
539             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL)
540         self.vapi.ip_reassembly_set(
541             timeout_ms=0, max_reassemblies=1000,
542             max_reassembly_length=1000,
543             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
544             expire_walk_interval_ms=10)
545         self.sleep(.25)
546         self.vapi.ip_reassembly_set(
547             timeout_ms=1000000, max_reassemblies=1000,
548             max_reassembly_length=1000,
549             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
550             expire_walk_interval_ms=10000)
551
552     def tearDown(self):
553         super(TestIPv4SVReassembly, self).tearDown()
554         self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
555         self.logger.debug(self.vapi.ppcli("show buffers"))
556
557     def test_basic(self):
558         """ basic reassembly """
559         payload_len = 1000
560         payload = ""
561         counter = 0
562         while len(payload) < payload_len:
563             payload += "%u " % counter
564             counter += 1
565
566         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
567              IP(id=1, src=self.src_if.remote_ip4,
568                 dst=self.dst_if.remote_ip4) /
569              UDP(sport=1234, dport=5678) /
570              Raw(payload))
571         fragments = fragment_rfc791(p, payload_len/4)
572
573         # send fragment #2 - should be cached inside reassembly
574         self.pg_enable_capture()
575         self.src_if.add_stream(fragments[1])
576         self.pg_start()
577         self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
578         self.logger.debug(self.vapi.ppcli("show buffers"))
579         self.logger.debug(self.vapi.ppcli("show trace"))
580         self.dst_if.assert_nothing_captured()
581
582         # send fragment #1 - reassembly is finished now and both fragments
583         # forwarded
584         self.pg_enable_capture()
585         self.src_if.add_stream(fragments[0])
586         self.pg_start()
587         self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
588         self.logger.debug(self.vapi.ppcli("show buffers"))
589         self.logger.debug(self.vapi.ppcli("show trace"))
590         c = self.dst_if.get_capture(2)
591         for sent, recvd in zip([fragments[1], fragments[0]], c):
592             self.assertEqual(sent[IP].src, recvd[IP].src)
593             self.assertEqual(sent[IP].dst, recvd[IP].dst)
594             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
595
596         # send rest of fragments - should be immediately forwarded
597         self.pg_enable_capture()
598         self.src_if.add_stream(fragments[2:])
599         self.pg_start()
600         c = self.dst_if.get_capture(len(fragments[2:]))
601         for sent, recvd in zip(fragments[2:], c):
602             self.assertEqual(sent[IP].src, recvd[IP].src)
603             self.assertEqual(sent[IP].dst, recvd[IP].dst)
604             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
605
606     def test_verify_clear_trace_mid_reassembly(self):
607         """ verify clear trace works mid-reassembly """
608         payload_len = 1000
609         payload = ""
610         counter = 0
611         while len(payload) < payload_len:
612             payload += "%u " % counter
613             counter += 1
614
615         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
616              IP(id=1, src=self.src_if.remote_ip4,
617                 dst=self.dst_if.remote_ip4) /
618              UDP(sport=1234, dport=5678) /
619              Raw(payload))
620         fragments = fragment_rfc791(p, payload_len/4)
621
622         self.pg_enable_capture()
623         self.src_if.add_stream(fragments[1])
624         self.pg_start()
625
626         self.logger.debug(self.vapi.cli("show trace"))
627         self.vapi.cli("clear trace")
628
629         self.pg_enable_capture()
630         self.src_if.add_stream(fragments[0])
631         self.pg_start()
632         self.dst_if.get_capture(2)
633
634         self.logger.debug(self.vapi.cli("show trace"))
635         self.vapi.cli("clear trace")
636
637         self.pg_enable_capture()
638         self.src_if.add_stream(fragments[2:])
639         self.pg_start()
640         self.dst_if.get_capture(len(fragments[2:]))
641
642     def test_timeout(self):
643         """ reassembly timeout """
644         payload_len = 1000
645         payload = ""
646         counter = 0
647         while len(payload) < payload_len:
648             payload += "%u " % counter
649             counter += 1
650
651         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
652              IP(id=1, src=self.src_if.remote_ip4,
653                 dst=self.dst_if.remote_ip4) /
654              UDP(sport=1234, dport=5678) /
655              Raw(payload))
656         fragments = fragment_rfc791(p, payload_len/4)
657
658         self.vapi.ip_reassembly_set(
659             timeout_ms=100, max_reassemblies=1000,
660             max_reassembly_length=1000,
661             expire_walk_interval_ms=50,
662             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL)
663
664         # send fragments #2 and #1 - should be forwarded
665         self.pg_enable_capture()
666         self.src_if.add_stream(fragments[0:2])
667         self.pg_start()
668         self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
669         self.logger.debug(self.vapi.ppcli("show buffers"))
670         self.logger.debug(self.vapi.ppcli("show trace"))
671         c = self.dst_if.get_capture(2)
672         for sent, recvd in zip([fragments[1], fragments[0]], c):
673             self.assertEqual(sent[IP].src, recvd[IP].src)
674             self.assertEqual(sent[IP].dst, recvd[IP].dst)
675             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
676
677         # wait for cleanup
678         self.sleep(.25, "wait before sending rest of fragments")
679
680         # send rest of fragments - shouldn't be forwarded
681         self.pg_enable_capture()
682         self.src_if.add_stream(fragments[2:])
683         self.pg_start()
684         self.dst_if.assert_nothing_captured()
685
686     def test_lru(self):
687         """ reassembly reuses LRU element """
688
689         self.vapi.ip_reassembly_set(
690             timeout_ms=1000000, max_reassemblies=1,
691             max_reassembly_length=1000,
692             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
693             expire_walk_interval_ms=10000)
694
695         payload_len = 1000
696         payload = ""
697         counter = 0
698         while len(payload) < payload_len:
699             payload += "%u " % counter
700             counter += 1
701
702         packet_count = 10
703
704         fragments = [f
705                      for i in range(packet_count)
706                      for p in (Ether(dst=self.src_if.local_mac,
707                                      src=self.src_if.remote_mac) /
708                                IP(id=i, src=self.src_if.remote_ip4,
709                                    dst=self.dst_if.remote_ip4) /
710                                UDP(sport=1234, dport=5678) /
711                                Raw(payload))
712                      for f in fragment_rfc791(p, payload_len/4)]
713
714         self.pg_enable_capture()
715         self.src_if.add_stream(fragments)
716         self.pg_start()
717         c = self.dst_if.get_capture(len(fragments))
718         for sent, recvd in zip(fragments, c):
719             self.assertEqual(sent[IP].src, recvd[IP].src)
720             self.assertEqual(sent[IP].dst, recvd[IP].dst)
721             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
722
723     def send_mixed_and_verify_capture(self, traffic):
724         stream = []
725         for t in traffic:
726             for c in range(t['count']):
727                 stream.append(
728                     (Ether(dst=self.src_if.local_mac,
729                            src=self.src_if.remote_mac) /
730                      IP(id=self.counter,
731                         flags=t['flags'],
732                         src=self.src_if.remote_ip4,
733                         dst=self.dst_if.remote_ip4) /
734                      UDP(sport=1234, dport=5678) /
735                      Raw("abcdef")))
736                 self.counter = self.counter + 1
737
738         self.pg_enable_capture()
739         self.src_if.add_stream(stream)
740         self.pg_start()
741         self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
742         self.logger.debug(self.vapi.ppcli("show buffers"))
743         self.logger.debug(self.vapi.ppcli("show trace"))
744         self.dst_if.get_capture(len(stream))
745
746     def test_mixed(self):
747         """ mixed traffic correctly passes through SVR """
748         self.counter = 1
749
750         self.send_mixed_and_verify_capture([{'count': 1, 'flags': ''}])
751         self.send_mixed_and_verify_capture([{'count': 2, 'flags': ''}])
752         self.send_mixed_and_verify_capture([{'count': 3, 'flags': ''}])
753         self.send_mixed_and_verify_capture([{'count': 8, 'flags': ''}])
754         self.send_mixed_and_verify_capture([{'count': 257, 'flags': ''}])
755
756         self.send_mixed_and_verify_capture([{'count': 1, 'flags': 'MF'}])
757         self.send_mixed_and_verify_capture([{'count': 2, 'flags': 'MF'}])
758         self.send_mixed_and_verify_capture([{'count': 3, 'flags': 'MF'}])
759         self.send_mixed_and_verify_capture([{'count': 8, 'flags': 'MF'}])
760         self.send_mixed_and_verify_capture([{'count': 257, 'flags': 'MF'}])
761
762         self.send_mixed_and_verify_capture(
763             [{'count': 1, 'flags': ''}, {'count': 1, 'flags': 'MF'}])
764         self.send_mixed_and_verify_capture(
765             [{'count': 2, 'flags': ''}, {'count': 2, 'flags': 'MF'}])
766         self.send_mixed_and_verify_capture(
767             [{'count': 3, 'flags': ''}, {'count': 3, 'flags': 'MF'}])
768         self.send_mixed_and_verify_capture(
769             [{'count': 8, 'flags': ''}, {'count': 8, 'flags': 'MF'}])
770         self.send_mixed_and_verify_capture(
771             [{'count': 129, 'flags': ''}, {'count': 129, 'flags': 'MF'}])
772
773         self.send_mixed_and_verify_capture(
774             [{'count': 1, 'flags': ''}, {'count': 1, 'flags': 'MF'},
775              {'count': 1, 'flags': ''}, {'count': 1, 'flags': 'MF'}])
776         self.send_mixed_and_verify_capture(
777             [{'count': 2, 'flags': ''}, {'count': 2, 'flags': 'MF'},
778              {'count': 2, 'flags': ''}, {'count': 2, 'flags': 'MF'}])
779         self.send_mixed_and_verify_capture(
780             [{'count': 3, 'flags': ''}, {'count': 3, 'flags': 'MF'},
781              {'count': 3, 'flags': ''}, {'count': 3, 'flags': 'MF'}])
782         self.send_mixed_and_verify_capture(
783             [{'count': 8, 'flags': ''}, {'count': 8, 'flags': 'MF'},
784              {'count': 8, 'flags': ''}, {'count': 8, 'flags': 'MF'}])
785         self.send_mixed_and_verify_capture(
786             [{'count': 65, 'flags': ''}, {'count': 65, 'flags': 'MF'},
787              {'count': 65, 'flags': ''}, {'count': 65, 'flags': 'MF'}])
788
789
790 class TestIPv4MWReassembly(VppTestCase):
791     """ IPv4 Reassembly (multiple workers) """
792     worker_config = "workers %d" % worker_count
793
794     @classmethod
795     def setUpClass(cls):
796         super(TestIPv4MWReassembly, cls).setUpClass()
797
798         cls.create_pg_interfaces(range(worker_count+1))
799         cls.src_if = cls.pg0
800         cls.send_ifs = cls.pg_interfaces[:-1]
801         cls.dst_if = cls.pg_interfaces[-1]
802
803         # setup all interfaces
804         for i in cls.pg_interfaces:
805             i.admin_up()
806             i.config_ip4()
807             i.resolve_arp()
808
809         # packets sizes reduced here because we are generating packets without
810         # Ethernet headers, which are added later (diff fragments go via
811         # different interfaces)
812         cls.packet_sizes = [64-len(Ether()), 512-len(Ether()),
813                             1518-len(Ether()), 9018-len(Ether())]
814         cls.padding = " abcdefghijklmn"
815         cls.create_stream(cls.packet_sizes)
816         cls.create_fragments()
817
818     @classmethod
819     def tearDownClass(cls):
820         super(TestIPv4MWReassembly, cls).tearDownClass()
821
822     def setUp(self):
823         """ Test setup - force timeout on existing reassemblies """
824         super(TestIPv4MWReassembly, self).setUp()
825         for intf in self.send_ifs:
826             self.vapi.ip_reassembly_enable_disable(
827                 sw_if_index=intf.sw_if_index, enable_ip4=True)
828         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
829                                     max_reassembly_length=1000,
830                                     expire_walk_interval_ms=10)
831         self.sleep(.25)
832         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
833                                     max_reassembly_length=1000,
834                                     expire_walk_interval_ms=10000)
835
836     def tearDown(self):
837         super(TestIPv4MWReassembly, self).tearDown()
838
839     def show_commands_at_teardown(self):
840         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
841         self.logger.debug(self.vapi.ppcli("show buffers"))
842
843     @classmethod
844     def create_stream(cls, packet_sizes, packet_count=test_packet_count):
845         """Create input packet stream
846
847         :param list packet_sizes: Required packet sizes.
848         """
849         for i in range(0, packet_count):
850             info = cls.create_packet_info(cls.src_if, cls.src_if)
851             payload = cls.info_to_payload(info)
852             p = (IP(id=info.index, src=cls.src_if.remote_ip4,
853                     dst=cls.dst_if.remote_ip4) /
854                  UDP(sport=1234, dport=5678) /
855                  Raw(payload))
856             size = packet_sizes[(i // 2) % len(packet_sizes)]
857             cls.extend_packet(p, size, cls.padding)
858             info.data = p
859
860     @classmethod
861     def create_fragments(cls):
862         infos = cls._packet_infos
863         cls.pkt_infos = []
864         for index, info in infos.items():
865             p = info.data
866             # cls.logger.debug(ppp("Packet:",
867             #                      p.__class__(scapy.compat.raw(p))))
868             fragments_400 = fragment_rfc791(p, 400)
869             cls.pkt_infos.append((index, fragments_400))
870         cls.fragments_400 = [
871             x for (_, frags) in cls.pkt_infos for x in frags]
872         cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, " %
873                          (len(infos), len(cls.fragments_400)))
874
875     def verify_capture(self, capture, dropped_packet_indexes=[]):
876         """Verify captured packet stream.
877
878         :param list capture: Captured packet stream.
879         """
880         info = None
881         seen = set()
882         for packet in capture:
883             try:
884                 self.logger.debug(ppp("Got packet:", packet))
885                 ip = packet[IP]
886                 udp = packet[UDP]
887                 payload_info = self.payload_to_info(packet[Raw])
888                 packet_index = payload_info.index
889                 self.assertTrue(
890                     packet_index not in dropped_packet_indexes,
891                     ppp("Packet received, but should be dropped:", packet))
892                 if packet_index in seen:
893                     raise Exception(ppp("Duplicate packet received", packet))
894                 seen.add(packet_index)
895                 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
896                 info = self._packet_infos[packet_index]
897                 self.assertTrue(info is not None)
898                 self.assertEqual(packet_index, info.index)
899                 saved_packet = info.data
900                 self.assertEqual(ip.src, saved_packet[IP].src)
901                 self.assertEqual(ip.dst, saved_packet[IP].dst)
902                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
903             except Exception:
904                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
905                 raise
906         for index in self._packet_infos:
907             self.assertTrue(index in seen or index in dropped_packet_indexes,
908                             "Packet with packet_index %d not received" % index)
909
910     def send_packets(self, packets):
911         for counter in range(worker_count):
912             if 0 == len(packets[counter]):
913                 continue
914             send_if = self.send_ifs[counter]
915             send_if.add_stream(
916                 (Ether(dst=send_if.local_mac, src=send_if.remote_mac) / x
917                  for x in packets[counter]),
918                 worker=counter)
919         self.pg_start()
920
921     def test_worker_conflict(self):
922         """ 1st and FO=0 fragments on different workers """
923
924         # in first wave we send fragments which don't start at offset 0
925         # then we send fragments with offset 0 on a different thread
926         # then the rest of packets on a random thread
927         first_packets = [[] for n in range(worker_count)]
928         second_packets = [[] for n in range(worker_count)]
929         rest_of_packets = [[] for n in range(worker_count)]
930         for (_, p) in self.pkt_infos:
931             wi = randrange(worker_count)
932             second_packets[wi].append(p[0])
933             if len(p) <= 1:
934                 continue
935             wi2 = wi
936             while wi2 == wi:
937                 wi2 = randrange(worker_count)
938             first_packets[wi2].append(p[1])
939             wi3 = randrange(worker_count)
940             rest_of_packets[wi3].extend(p[2:])
941
942         self.pg_enable_capture()
943         self.send_packets(first_packets)
944         self.send_packets(second_packets)
945         self.send_packets(rest_of_packets)
946
947         packets = self.dst_if.get_capture(len(self.pkt_infos))
948         self.verify_capture(packets)
949         for send_if in self.send_ifs:
950             send_if.assert_nothing_captured()
951
952         self.logger.debug(self.vapi.ppcli("show trace"))
953         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
954         self.logger.debug(self.vapi.ppcli("show buffers"))
955         self.vapi.cli("clear trace")
956
957         self.pg_enable_capture()
958         self.send_packets(first_packets)
959         self.send_packets(second_packets)
960         self.send_packets(rest_of_packets)
961
962         packets = self.dst_if.get_capture(len(self.pkt_infos))
963         self.verify_capture(packets)
964         for send_if in self.send_ifs:
965             send_if.assert_nothing_captured()
966
967
968 class TestIPv6Reassembly(VppTestCase):
969     """ IPv6 Reassembly """
970
971     @classmethod
972     def setUpClass(cls):
973         super(TestIPv6Reassembly, cls).setUpClass()
974
975         cls.create_pg_interfaces([0, 1])
976         cls.src_if = cls.pg0
977         cls.dst_if = cls.pg1
978
979         # setup all interfaces
980         for i in cls.pg_interfaces:
981             i.admin_up()
982             i.config_ip6()
983             i.resolve_ndp()
984
985         # packet sizes
986         cls.packet_sizes = [64, 512, 1518, 9018]
987         cls.padding = " abcdefghijklmn"
988         cls.create_stream(cls.packet_sizes)
989         cls.create_fragments()
990
991     @classmethod
992     def tearDownClass(cls):
993         super(TestIPv6Reassembly, cls).tearDownClass()
994
995     def setUp(self):
996         """ Test setup - force timeout on existing reassemblies """
997         super(TestIPv6Reassembly, self).setUp()
998         self.vapi.ip_reassembly_enable_disable(
999             sw_if_index=self.src_if.sw_if_index, enable_ip6=True)
1000         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1001                                     max_reassembly_length=1000,
1002                                     expire_walk_interval_ms=10, is_ip6=1)
1003         self.sleep(.25)
1004         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1005                                     max_reassembly_length=1000,
1006                                     expire_walk_interval_ms=10000, is_ip6=1)
1007         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1008         self.logger.debug(self.vapi.ppcli("show buffers"))
1009
1010     def tearDown(self):
1011         super(TestIPv6Reassembly, self).tearDown()
1012
1013     def show_commands_at_teardown(self):
1014         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1015         self.logger.debug(self.vapi.ppcli("show buffers"))
1016
1017     @classmethod
1018     def create_stream(cls, packet_sizes, packet_count=test_packet_count):
1019         """Create input packet stream for defined interface.
1020
1021         :param list packet_sizes: Required packet sizes.
1022         """
1023         for i in range(0, packet_count):
1024             info = cls.create_packet_info(cls.src_if, cls.src_if)
1025             payload = cls.info_to_payload(info)
1026             p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
1027                  IPv6(src=cls.src_if.remote_ip6,
1028                       dst=cls.dst_if.remote_ip6) /
1029                  UDP(sport=1234, dport=5678) /
1030                  Raw(payload))
1031             size = packet_sizes[(i // 2) % len(packet_sizes)]
1032             cls.extend_packet(p, size, cls.padding)
1033             info.data = p
1034
1035     @classmethod
1036     def create_fragments(cls):
1037         infos = cls._packet_infos
1038         cls.pkt_infos = []
1039         for index, info in infos.items():
1040             p = info.data
1041             # cls.logger.debug(ppp("Packet:",
1042             #                      p.__class__(scapy.compat.raw(p))))
1043             fragments_400 = fragment_rfc8200(p, info.index, 400)
1044             fragments_300 = fragment_rfc8200(p, info.index, 300)
1045             cls.pkt_infos.append((index, fragments_400, fragments_300))
1046         cls.fragments_400 = [
1047             x for _, frags, _ in cls.pkt_infos for x in frags]
1048         cls.fragments_300 = [
1049             x for _, _, frags in cls.pkt_infos for x in frags]
1050         cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
1051                          "and %s 300-byte fragments" %
1052                          (len(infos), len(cls.fragments_400),
1053                              len(cls.fragments_300)))
1054
1055     def verify_capture(self, capture, dropped_packet_indexes=[]):
1056         """Verify captured packet strea .
1057
1058         :param list capture: Captured packet stream.
1059         """
1060         info = None
1061         seen = set()
1062         for packet in capture:
1063             try:
1064                 self.logger.debug(ppp("Got packet:", packet))
1065                 ip = packet[IPv6]
1066                 udp = packet[UDP]
1067                 payload_info = self.payload_to_info(packet[Raw])
1068                 packet_index = payload_info.index
1069                 self.assertTrue(
1070                     packet_index not in dropped_packet_indexes,
1071                     ppp("Packet received, but should be dropped:", packet))
1072                 if packet_index in seen:
1073                     raise Exception(ppp("Duplicate packet received", packet))
1074                 seen.add(packet_index)
1075                 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
1076                 info = self._packet_infos[packet_index]
1077                 self.assertTrue(info is not None)
1078                 self.assertEqual(packet_index, info.index)
1079                 saved_packet = info.data
1080                 self.assertEqual(ip.src, saved_packet[IPv6].src)
1081                 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
1082                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1083             except Exception:
1084                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1085                 raise
1086         for index in self._packet_infos:
1087             self.assertTrue(index in seen or index in dropped_packet_indexes,
1088                             "Packet with packet_index %d not received" % index)
1089
1090     def test_reassembly(self):
1091         """ basic reassembly """
1092
1093         self.pg_enable_capture()
1094         self.src_if.add_stream(self.fragments_400)
1095         self.pg_start()
1096
1097         packets = self.dst_if.get_capture(len(self.pkt_infos))
1098         self.verify_capture(packets)
1099         self.src_if.assert_nothing_captured()
1100
1101         # run it all again to verify correctness
1102         self.pg_enable_capture()
1103         self.src_if.add_stream(self.fragments_400)
1104         self.pg_start()
1105
1106         packets = self.dst_if.get_capture(len(self.pkt_infos))
1107         self.verify_capture(packets)
1108         self.src_if.assert_nothing_captured()
1109
1110     def test_buffer_boundary(self):
1111         """ fragment header crossing buffer boundary """
1112
1113         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1114              IPv6(src=self.src_if.remote_ip6,
1115                   dst=self.src_if.local_ip6) /
1116              IPv6ExtHdrHopByHop(
1117                  options=[HBHOptUnknown(otype=0xff, optlen=0)] * 1000) /
1118              IPv6ExtHdrFragment(m=1) /
1119              UDP(sport=1234, dport=5678) /
1120              Raw())
1121         self.pg_enable_capture()
1122         self.src_if.add_stream([p])
1123         self.pg_start()
1124         self.src_if.assert_nothing_captured()
1125         self.dst_if.assert_nothing_captured()
1126
1127     def test_verify_clear_trace_mid_reassembly(self):
1128         """ verify clear trace works mid-reassembly """
1129
1130         self.pg_enable_capture()
1131         self.src_if.add_stream(self.fragments_400[0:-1])
1132         self.pg_start()
1133
1134         self.logger.debug(self.vapi.cli("show trace"))
1135         self.vapi.cli("clear trace")
1136
1137         self.src_if.add_stream(self.fragments_400[-1])
1138         self.pg_start()
1139         packets = self.dst_if.get_capture(len(self.pkt_infos))
1140         self.verify_capture(packets)
1141
1142     def test_reversed(self):
1143         """ reverse order reassembly """
1144
1145         fragments = list(self.fragments_400)
1146         fragments.reverse()
1147
1148         self.pg_enable_capture()
1149         self.src_if.add_stream(fragments)
1150         self.pg_start()
1151
1152         packets = self.dst_if.get_capture(len(self.pkt_infos))
1153         self.verify_capture(packets)
1154         self.src_if.assert_nothing_captured()
1155
1156         # run it all again to verify correctness
1157         self.pg_enable_capture()
1158         self.src_if.add_stream(fragments)
1159         self.pg_start()
1160
1161         packets = self.dst_if.get_capture(len(self.pkt_infos))
1162         self.verify_capture(packets)
1163         self.src_if.assert_nothing_captured()
1164
1165     def test_random(self):
1166         """ random order reassembly """
1167
1168         fragments = list(self.fragments_400)
1169         shuffle(fragments)
1170
1171         self.pg_enable_capture()
1172         self.src_if.add_stream(fragments)
1173         self.pg_start()
1174
1175         packets = self.dst_if.get_capture(len(self.pkt_infos))
1176         self.verify_capture(packets)
1177         self.src_if.assert_nothing_captured()
1178
1179         # run it all again to verify correctness
1180         self.pg_enable_capture()
1181         self.src_if.add_stream(fragments)
1182         self.pg_start()
1183
1184         packets = self.dst_if.get_capture(len(self.pkt_infos))
1185         self.verify_capture(packets)
1186         self.src_if.assert_nothing_captured()
1187
1188     def test_duplicates(self):
1189         """ duplicate fragments """
1190
1191         fragments = [
1192             x for (_, frags, _) in self.pkt_infos
1193             for x in frags
1194             for _ in range(0, min(2, len(frags)))
1195         ]
1196
1197         self.pg_enable_capture()
1198         self.src_if.add_stream(fragments)
1199         self.pg_start()
1200
1201         packets = self.dst_if.get_capture(len(self.pkt_infos))
1202         self.verify_capture(packets)
1203         self.src_if.assert_nothing_captured()
1204
1205     def test_long_fragment_chain(self):
1206         """ long fragment chain """
1207
1208         error_cnt_str = \
1209             "/err/ip6-full-reassembly-feature/fragment chain too long (drop)"
1210
1211         error_cnt = self.statistics.get_err_counter(error_cnt_str)
1212
1213         self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
1214                                     max_reassembly_length=3,
1215                                     expire_walk_interval_ms=50, is_ip6=1)
1216
1217         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1218              IPv6(src=self.src_if.remote_ip6,
1219                   dst=self.dst_if.remote_ip6) /
1220              UDP(sport=1234, dport=5678) /
1221              Raw(b"X" * 1000))
1222         frags = fragment_rfc8200(p, 1, 300) + fragment_rfc8200(p, 2, 500)
1223
1224         self.pg_enable_capture()
1225         self.src_if.add_stream(frags)
1226         self.pg_start()
1227
1228         self.dst_if.get_capture(1)
1229         self.assert_error_counter_equal(error_cnt_str, error_cnt + 1)
1230
1231     def test_overlap1(self):
1232         """ overlapping fragments case #1 """
1233
1234         fragments = []
1235         for _, frags_400, frags_300 in self.pkt_infos:
1236             if len(frags_300) == 1:
1237                 fragments.extend(frags_400)
1238             else:
1239                 for i, j in zip(frags_300, frags_400):
1240                     fragments.extend(i)
1241                     fragments.extend(j)
1242
1243         dropped_packet_indexes = set(
1244             index for (index, _, frags) in self.pkt_infos if len(frags) > 1
1245         )
1246
1247         self.pg_enable_capture()
1248         self.src_if.add_stream(fragments)
1249         self.pg_start()
1250
1251         packets = self.dst_if.get_capture(
1252             len(self.pkt_infos) - len(dropped_packet_indexes))
1253         self.verify_capture(packets, dropped_packet_indexes)
1254         self.src_if.assert_nothing_captured()
1255
1256     def test_overlap2(self):
1257         """ overlapping fragments case #2 """
1258
1259         fragments = []
1260         for _, frags_400, frags_300 in self.pkt_infos:
1261             if len(frags_400) == 1:
1262                 fragments.extend(frags_400)
1263             else:
1264                 # care must be taken here so that there are no fragments
1265                 # received by vpp after reassembly is finished, otherwise
1266                 # new reassemblies will be started and packet generator will
1267                 # freak out when it detects unfreed buffers
1268                 zipped = zip(frags_400, frags_300)
1269                 for i, j in zipped:
1270                     fragments.extend(i)
1271                     fragments.extend(j)
1272                 fragments.pop()
1273
1274         dropped_packet_indexes = set(
1275             index for (index, _, frags) in self.pkt_infos if len(frags) > 1
1276         )
1277
1278         self.pg_enable_capture()
1279         self.src_if.add_stream(fragments)
1280         self.pg_start()
1281
1282         packets = self.dst_if.get_capture(
1283             len(self.pkt_infos) - len(dropped_packet_indexes))
1284         self.verify_capture(packets, dropped_packet_indexes)
1285         self.src_if.assert_nothing_captured()
1286
1287     def test_timeout_inline(self):
1288         """ timeout (inline) """
1289
1290         dropped_packet_indexes = set(
1291             index for (index, frags, _) in self.pkt_infos if len(frags) > 1
1292         )
1293
1294         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1295                                     max_reassembly_length=3,
1296                                     expire_walk_interval_ms=10000, is_ip6=1)
1297
1298         self.pg_enable_capture()
1299         self.src_if.add_stream(self.fragments_400)
1300         self.pg_start()
1301
1302         packets = self.dst_if.get_capture(
1303             len(self.pkt_infos) - len(dropped_packet_indexes))
1304         self.verify_capture(packets, dropped_packet_indexes)
1305         pkts = self.src_if.get_capture(
1306             expected_count=len(dropped_packet_indexes))
1307         for icmp in pkts:
1308             self.assertIn(ICMPv6TimeExceeded, icmp)
1309             self.assertIn(IPv6ExtHdrFragment, icmp)
1310             self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
1311             dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
1312
1313     def test_timeout_cleanup(self):
1314         """ timeout (cleanup) """
1315
1316         # whole packets + fragmented packets sans last fragment
1317         fragments = [
1318             x for (_, frags_400, _) in self.pkt_infos
1319             for x in frags_400[:-1 if len(frags_400) > 1 else None]
1320         ]
1321
1322         # last fragments for fragmented packets
1323         fragments2 = [frags_400[-1]
1324                       for (_, frags_400, _) in self.pkt_infos
1325                       if len(frags_400) > 1]
1326
1327         dropped_packet_indexes = set(
1328             index for (index, frags_400, _) in self.pkt_infos
1329             if len(frags_400) > 1)
1330
1331         self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
1332                                     max_reassembly_length=1000,
1333                                     expire_walk_interval_ms=50)
1334
1335         self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
1336                                     max_reassembly_length=1000,
1337                                     expire_walk_interval_ms=50, is_ip6=1)
1338
1339         self.pg_enable_capture()
1340         self.src_if.add_stream(fragments)
1341         self.pg_start()
1342
1343         self.sleep(.25, "wait before sending rest of fragments")
1344
1345         self.src_if.add_stream(fragments2)
1346         self.pg_start()
1347
1348         packets = self.dst_if.get_capture(
1349             len(self.pkt_infos) - len(dropped_packet_indexes))
1350         self.verify_capture(packets, dropped_packet_indexes)
1351         pkts = self.src_if.get_capture(
1352             expected_count=len(dropped_packet_indexes))
1353         for icmp in pkts:
1354             self.assertIn(ICMPv6TimeExceeded, icmp)
1355             self.assertIn(IPv6ExtHdrFragment, icmp)
1356             self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
1357             dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
1358
1359     def test_disabled(self):
1360         """ reassembly disabled """
1361
1362         dropped_packet_indexes = set(
1363             index for (index, frags_400, _) in self.pkt_infos
1364             if len(frags_400) > 1)
1365
1366         self.vapi.ip_reassembly_set(timeout_ms=1000, max_reassemblies=0,
1367                                     max_reassembly_length=3,
1368                                     expire_walk_interval_ms=10000, is_ip6=1)
1369
1370         self.pg_enable_capture()
1371         self.src_if.add_stream(self.fragments_400)
1372         self.pg_start()
1373
1374         packets = self.dst_if.get_capture(
1375             len(self.pkt_infos) - len(dropped_packet_indexes))
1376         self.verify_capture(packets, dropped_packet_indexes)
1377         self.src_if.assert_nothing_captured()
1378
1379     def test_missing_upper(self):
1380         """ missing upper layer """
1381         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1382              IPv6(src=self.src_if.remote_ip6,
1383                   dst=self.src_if.local_ip6) /
1384              UDP(sport=1234, dport=5678) /
1385              Raw())
1386         self.extend_packet(p, 1000, self.padding)
1387         fragments = fragment_rfc8200(p, 1, 500)
1388         bad_fragment = p.__class__(scapy.compat.raw(fragments[1]))
1389         bad_fragment[IPv6ExtHdrFragment].nh = 59
1390         bad_fragment[IPv6ExtHdrFragment].offset = 0
1391         self.pg_enable_capture()
1392         self.src_if.add_stream([bad_fragment])
1393         self.pg_start()
1394         pkts = self.src_if.get_capture(expected_count=1)
1395         icmp = pkts[0]
1396         self.assertIn(ICMPv6ParamProblem, icmp)
1397         self.assert_equal(icmp[ICMPv6ParamProblem].code, 3, "ICMP code")
1398
1399     def test_invalid_frag_size(self):
1400         """ fragment size not a multiple of 8 """
1401         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1402              IPv6(src=self.src_if.remote_ip6,
1403                   dst=self.src_if.local_ip6) /
1404              UDP(sport=1234, dport=5678) /
1405              Raw())
1406         self.extend_packet(p, 1000, self.padding)
1407         fragments = fragment_rfc8200(p, 1, 500)
1408         bad_fragment = fragments[0]
1409         self.extend_packet(bad_fragment, len(bad_fragment) + 5)
1410         self.pg_enable_capture()
1411         self.src_if.add_stream([bad_fragment])
1412         self.pg_start()
1413         pkts = self.src_if.get_capture(expected_count=1)
1414         icmp = pkts[0]
1415         self.assertIn(ICMPv6ParamProblem, icmp)
1416         self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
1417
1418     def test_invalid_packet_size(self):
1419         """ total packet size > 65535 """
1420         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1421              IPv6(src=self.src_if.remote_ip6,
1422                   dst=self.src_if.local_ip6) /
1423              UDP(sport=1234, dport=5678) /
1424              Raw())
1425         self.extend_packet(p, 1000, self.padding)
1426         fragments = fragment_rfc8200(p, 1, 500)
1427         bad_fragment = fragments[1]
1428         bad_fragment[IPv6ExtHdrFragment].offset = 65500
1429         self.pg_enable_capture()
1430         self.src_if.add_stream([bad_fragment])
1431         self.pg_start()
1432         pkts = self.src_if.get_capture(expected_count=1)
1433         icmp = pkts[0]
1434         self.assertIn(ICMPv6ParamProblem, icmp)
1435         self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
1436
1437
1438 class TestIPv6MWReassembly(VppTestCase):
1439     """ IPv6 Reassembly (multiple workers) """
1440     worker_config = "workers %d" % worker_count
1441
1442     @classmethod
1443     def setUpClass(cls):
1444         super(TestIPv6MWReassembly, cls).setUpClass()
1445
1446         cls.create_pg_interfaces(range(worker_count+1))
1447         cls.src_if = cls.pg0
1448         cls.send_ifs = cls.pg_interfaces[:-1]
1449         cls.dst_if = cls.pg_interfaces[-1]
1450
1451         # setup all interfaces
1452         for i in cls.pg_interfaces:
1453             i.admin_up()
1454             i.config_ip6()
1455             i.resolve_ndp()
1456
1457         # packets sizes reduced here because we are generating packets without
1458         # Ethernet headers, which are added later (diff fragments go via
1459         # different interfaces)
1460         cls.packet_sizes = [64-len(Ether()), 512-len(Ether()),
1461                             1518-len(Ether()), 9018-len(Ether())]
1462         cls.padding = " abcdefghijklmn"
1463         cls.create_stream(cls.packet_sizes)
1464         cls.create_fragments()
1465
1466     @classmethod
1467     def tearDownClass(cls):
1468         super(TestIPv6MWReassembly, cls).tearDownClass()
1469
1470     def setUp(self):
1471         """ Test setup - force timeout on existing reassemblies """
1472         super(TestIPv6MWReassembly, self).setUp()
1473         for intf in self.send_ifs:
1474             self.vapi.ip_reassembly_enable_disable(
1475                 sw_if_index=intf.sw_if_index, enable_ip6=True)
1476         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1477                                     max_reassembly_length=1000,
1478                                     expire_walk_interval_ms=10, is_ip6=1)
1479         self.sleep(.25)
1480         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1481                                     max_reassembly_length=1000,
1482                                     expire_walk_interval_ms=1000, is_ip6=1)
1483
1484     def tearDown(self):
1485         super(TestIPv6MWReassembly, self).tearDown()
1486
1487     def show_commands_at_teardown(self):
1488         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1489         self.logger.debug(self.vapi.ppcli("show buffers"))
1490
1491     @classmethod
1492     def create_stream(cls, packet_sizes, packet_count=test_packet_count):
1493         """Create input packet stream
1494
1495         :param list packet_sizes: Required packet sizes.
1496         """
1497         for i in range(0, packet_count):
1498             info = cls.create_packet_info(cls.src_if, cls.src_if)
1499             payload = cls.info_to_payload(info)
1500             p = (IPv6(src=cls.src_if.remote_ip6,
1501                       dst=cls.dst_if.remote_ip6) /
1502                  UDP(sport=1234, dport=5678) /
1503                  Raw(payload))
1504             size = packet_sizes[(i // 2) % len(packet_sizes)]
1505             cls.extend_packet(p, size, cls.padding)
1506             info.data = p
1507
1508     @classmethod
1509     def create_fragments(cls):
1510         infos = cls._packet_infos
1511         cls.pkt_infos = []
1512         for index, info in infos.items():
1513             p = info.data
1514             # cls.logger.debug(ppp("Packet:",
1515             #                      p.__class__(scapy.compat.raw(p))))
1516             fragments_400 = fragment_rfc8200(p, index, 400)
1517             cls.pkt_infos.append((index, fragments_400))
1518         cls.fragments_400 = [
1519             x for (_, frags) in cls.pkt_infos for x in frags]
1520         cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, " %
1521                          (len(infos), len(cls.fragments_400)))
1522
1523     def verify_capture(self, capture, dropped_packet_indexes=[]):
1524         """Verify captured packet strea .
1525
1526         :param list capture: Captured packet stream.
1527         """
1528         info = None
1529         seen = set()
1530         for packet in capture:
1531             try:
1532                 self.logger.debug(ppp("Got packet:", packet))
1533                 ip = packet[IPv6]
1534                 udp = packet[UDP]
1535                 payload_info = self.payload_to_info(packet[Raw])
1536                 packet_index = payload_info.index
1537                 self.assertTrue(
1538                     packet_index not in dropped_packet_indexes,
1539                     ppp("Packet received, but should be dropped:", packet))
1540                 if packet_index in seen:
1541                     raise Exception(ppp("Duplicate packet received", packet))
1542                 seen.add(packet_index)
1543                 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
1544                 info = self._packet_infos[packet_index]
1545                 self.assertTrue(info is not None)
1546                 self.assertEqual(packet_index, info.index)
1547                 saved_packet = info.data
1548                 self.assertEqual(ip.src, saved_packet[IPv6].src)
1549                 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
1550                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1551             except Exception:
1552                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1553                 raise
1554         for index in self._packet_infos:
1555             self.assertTrue(index in seen or index in dropped_packet_indexes,
1556                             "Packet with packet_index %d not received" % index)
1557
1558     def send_packets(self, packets):
1559         for counter in range(worker_count):
1560             if 0 == len(packets[counter]):
1561                 continue
1562             send_if = self.send_ifs[counter]
1563             send_if.add_stream(
1564                 (Ether(dst=send_if.local_mac, src=send_if.remote_mac) / x
1565                  for x in packets[counter]),
1566                 worker=counter)
1567         self.pg_start()
1568
1569     def test_worker_conflict(self):
1570         """ 1st and FO=0 fragments on different workers """
1571
1572         # in first wave we send fragments which don't start at offset 0
1573         # then we send fragments with offset 0 on a different thread
1574         # then the rest of packets on a random thread
1575         first_packets = [[] for n in range(worker_count)]
1576         second_packets = [[] for n in range(worker_count)]
1577         rest_of_packets = [[] for n in range(worker_count)]
1578         for (_, p) in self.pkt_infos:
1579             wi = randrange(worker_count)
1580             second_packets[wi].append(p[0])
1581             if len(p) <= 1:
1582                 continue
1583             wi2 = wi
1584             while wi2 == wi:
1585                 wi2 = randrange(worker_count)
1586             first_packets[wi2].append(p[1])
1587             wi3 = randrange(worker_count)
1588             rest_of_packets[wi3].extend(p[2:])
1589
1590         self.pg_enable_capture()
1591         self.send_packets(first_packets)
1592         self.send_packets(second_packets)
1593         self.send_packets(rest_of_packets)
1594
1595         packets = self.dst_if.get_capture(len(self.pkt_infos))
1596         self.verify_capture(packets)
1597         for send_if in self.send_ifs:
1598             send_if.assert_nothing_captured()
1599
1600         self.logger.debug(self.vapi.ppcli("show trace"))
1601         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1602         self.logger.debug(self.vapi.ppcli("show buffers"))
1603         self.vapi.cli("clear trace")
1604
1605         self.pg_enable_capture()
1606         self.send_packets(first_packets)
1607         self.send_packets(second_packets)
1608         self.send_packets(rest_of_packets)
1609
1610         packets = self.dst_if.get_capture(len(self.pkt_infos))
1611         self.verify_capture(packets)
1612         for send_if in self.send_ifs:
1613             send_if.assert_nothing_captured()
1614
1615
1616 class TestIPv6SVReassembly(VppTestCase):
1617     """ IPv6 Shallow Virtual Reassembly """
1618
1619     @classmethod
1620     def setUpClass(cls):
1621         super(TestIPv6SVReassembly, cls).setUpClass()
1622
1623         cls.create_pg_interfaces([0, 1])
1624         cls.src_if = cls.pg0
1625         cls.dst_if = cls.pg1
1626
1627         # setup all interfaces
1628         for i in cls.pg_interfaces:
1629             i.admin_up()
1630             i.config_ip6()
1631             i.resolve_ndp()
1632
1633     def setUp(self):
1634         """ Test setup - force timeout on existing reassemblies """
1635         super(TestIPv6SVReassembly, self).setUp()
1636         self.vapi.ip_reassembly_enable_disable(
1637             sw_if_index=self.src_if.sw_if_index, enable_ip6=True,
1638             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL)
1639         self.vapi.ip_reassembly_set(
1640             timeout_ms=0, max_reassemblies=1000,
1641             max_reassembly_length=1000,
1642             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
1643             expire_walk_interval_ms=10, is_ip6=1)
1644         self.sleep(.25)
1645         self.vapi.ip_reassembly_set(
1646             timeout_ms=1000000, max_reassemblies=1000,
1647             max_reassembly_length=1000,
1648             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
1649             expire_walk_interval_ms=10000, is_ip6=1)
1650
1651     def tearDown(self):
1652         super(TestIPv6SVReassembly, self).tearDown()
1653         self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
1654         self.logger.debug(self.vapi.ppcli("show buffers"))
1655
1656     def test_basic(self):
1657         """ basic reassembly """
1658         payload_len = 1000
1659         payload = ""
1660         counter = 0
1661         while len(payload) < payload_len:
1662             payload += "%u " % counter
1663             counter += 1
1664
1665         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1666              IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1667              UDP(sport=1234, dport=5678) /
1668              Raw(payload))
1669         fragments = fragment_rfc8200(p, 1, payload_len/4)
1670
1671         # send fragment #2 - should be cached inside reassembly
1672         self.pg_enable_capture()
1673         self.src_if.add_stream(fragments[1])
1674         self.pg_start()
1675         self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
1676         self.logger.debug(self.vapi.ppcli("show buffers"))
1677         self.logger.debug(self.vapi.ppcli("show trace"))
1678         self.dst_if.assert_nothing_captured()
1679
1680         # send fragment #1 - reassembly is finished now and both fragments
1681         # forwarded
1682         self.pg_enable_capture()
1683         self.src_if.add_stream(fragments[0])
1684         self.pg_start()
1685         self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
1686         self.logger.debug(self.vapi.ppcli("show buffers"))
1687         self.logger.debug(self.vapi.ppcli("show trace"))
1688         c = self.dst_if.get_capture(2)
1689         for sent, recvd in zip([fragments[1], fragments[0]], c):
1690             self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
1691             self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
1692             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
1693
1694         # send rest of fragments - should be immediately forwarded
1695         self.pg_enable_capture()
1696         self.src_if.add_stream(fragments[2:])
1697         self.pg_start()
1698         c = self.dst_if.get_capture(len(fragments[2:]))
1699         for sent, recvd in zip(fragments[2:], c):
1700             self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
1701             self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
1702             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
1703
1704     def test_verify_clear_trace_mid_reassembly(self):
1705         """ verify clear trace works mid-reassembly """
1706         payload_len = 1000
1707         payload = ""
1708         counter = 0
1709         while len(payload) < payload_len:
1710             payload += "%u " % counter
1711             counter += 1
1712
1713         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1714              IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1715              UDP(sport=1234, dport=5678) /
1716              Raw(payload))
1717         fragments = fragment_rfc8200(p, 1, payload_len/4)
1718
1719         self.pg_enable_capture()
1720         self.src_if.add_stream(fragments[1])
1721         self.pg_start()
1722
1723         self.logger.debug(self.vapi.cli("show trace"))
1724         self.vapi.cli("clear trace")
1725
1726         self.pg_enable_capture()
1727         self.src_if.add_stream(fragments[0])
1728         self.pg_start()
1729         self.dst_if.get_capture(2)
1730
1731         self.logger.debug(self.vapi.cli("show trace"))
1732         self.vapi.cli("clear trace")
1733
1734         self.pg_enable_capture()
1735         self.src_if.add_stream(fragments[2:])
1736         self.pg_start()
1737         self.dst_if.get_capture(len(fragments[2:]))
1738
1739     def test_timeout(self):
1740         """ reassembly timeout """
1741         payload_len = 1000
1742         payload = ""
1743         counter = 0
1744         while len(payload) < payload_len:
1745             payload += "%u " % counter
1746             counter += 1
1747
1748         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1749              IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1750              UDP(sport=1234, dport=5678) /
1751              Raw(payload))
1752         fragments = fragment_rfc8200(p, 1, payload_len/4)
1753
1754         self.vapi.ip_reassembly_set(
1755             timeout_ms=100, max_reassemblies=1000,
1756             max_reassembly_length=1000,
1757             expire_walk_interval_ms=50,
1758             is_ip6=1,
1759             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL)
1760
1761         # send fragments #2 and #1 - should be forwarded
1762         self.pg_enable_capture()
1763         self.src_if.add_stream(fragments[0:2])
1764         self.pg_start()
1765         self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
1766         self.logger.debug(self.vapi.ppcli("show buffers"))
1767         self.logger.debug(self.vapi.ppcli("show trace"))
1768         c = self.dst_if.get_capture(2)
1769         for sent, recvd in zip([fragments[1], fragments[0]], c):
1770             self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
1771             self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
1772             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
1773
1774         # wait for cleanup
1775         self.sleep(.25, "wait before sending rest of fragments")
1776
1777         # send rest of fragments - shouldn't be forwarded
1778         self.pg_enable_capture()
1779         self.src_if.add_stream(fragments[2:])
1780         self.pg_start()
1781         self.dst_if.assert_nothing_captured()
1782
1783     def test_lru(self):
1784         """ reassembly reuses LRU element """
1785
1786         self.vapi.ip_reassembly_set(
1787             timeout_ms=1000000, max_reassemblies=1,
1788             max_reassembly_length=1000,
1789             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
1790             is_ip6=1, expire_walk_interval_ms=10000)
1791
1792         payload_len = 1000
1793         payload = ""
1794         counter = 0
1795         while len(payload) < payload_len:
1796             payload += "%u " % counter
1797             counter += 1
1798
1799         packet_count = 10
1800
1801         fragments = [f
1802                      for i in range(packet_count)
1803                      for p in (Ether(dst=self.src_if.local_mac,
1804                                      src=self.src_if.remote_mac) /
1805                                IPv6(src=self.src_if.remote_ip6,
1806                                     dst=self.dst_if.remote_ip6) /
1807                                UDP(sport=1234, dport=5678) /
1808                                Raw(payload))
1809                      for f in fragment_rfc8200(p, i, payload_len/4)]
1810
1811         self.pg_enable_capture()
1812         self.src_if.add_stream(fragments)
1813         self.pg_start()
1814         c = self.dst_if.get_capture(len(fragments))
1815         for sent, recvd in zip(fragments, c):
1816             self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
1817             self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
1818             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
1819
1820
1821 class TestIPv4ReassemblyLocalNode(VppTestCase):
1822     """ IPv4 Reassembly for packets coming to ip4-local node """
1823
1824     @classmethod
1825     def setUpClass(cls):
1826         super(TestIPv4ReassemblyLocalNode, cls).setUpClass()
1827
1828         cls.create_pg_interfaces([0])
1829         cls.src_dst_if = cls.pg0
1830
1831         # setup all interfaces
1832         for i in cls.pg_interfaces:
1833             i.admin_up()
1834             i.config_ip4()
1835             i.resolve_arp()
1836
1837         cls.padding = " abcdefghijklmn"
1838         cls.create_stream()
1839         cls.create_fragments()
1840
1841     @classmethod
1842     def tearDownClass(cls):
1843         super(TestIPv4ReassemblyLocalNode, cls).tearDownClass()
1844
1845     def setUp(self):
1846         """ Test setup - force timeout on existing reassemblies """
1847         super(TestIPv4ReassemblyLocalNode, self).setUp()
1848         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1849                                     max_reassembly_length=1000,
1850                                     expire_walk_interval_ms=10)
1851         self.sleep(.25)
1852         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1853                                     max_reassembly_length=1000,
1854                                     expire_walk_interval_ms=10000)
1855
1856     def tearDown(self):
1857         super(TestIPv4ReassemblyLocalNode, self).tearDown()
1858
1859     def show_commands_at_teardown(self):
1860         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
1861         self.logger.debug(self.vapi.ppcli("show buffers"))
1862
1863     @classmethod
1864     def create_stream(cls, packet_count=test_packet_count):
1865         """Create input packet stream for defined interface.
1866
1867         :param list packet_sizes: Required packet sizes.
1868         """
1869         for i in range(0, packet_count):
1870             info = cls.create_packet_info(cls.src_dst_if, cls.src_dst_if)
1871             payload = cls.info_to_payload(info)
1872             p = (Ether(dst=cls.src_dst_if.local_mac,
1873                        src=cls.src_dst_if.remote_mac) /
1874                  IP(id=info.index, src=cls.src_dst_if.remote_ip4,
1875                     dst=cls.src_dst_if.local_ip4) /
1876                  ICMP(type='echo-request', id=1234) /
1877                  Raw(payload))
1878             cls.extend_packet(p, 1518, cls.padding)
1879             info.data = p
1880
1881     @classmethod
1882     def create_fragments(cls):
1883         infos = cls._packet_infos
1884         cls.pkt_infos = []
1885         for index, info in infos.items():
1886             p = info.data
1887             # cls.logger.debug(ppp("Packet:",
1888             #                      p.__class__(scapy.compat.raw(p))))
1889             fragments_300 = fragment_rfc791(p, 300)
1890             cls.pkt_infos.append((index, fragments_300))
1891         cls.fragments_300 = [x for (_, frags) in cls.pkt_infos for x in frags]
1892         cls.logger.debug("Fragmented %s packets into %s 300-byte fragments" %
1893                          (len(infos), len(cls.fragments_300)))
1894
1895     def verify_capture(self, capture):
1896         """Verify captured packet stream.
1897
1898         :param list capture: Captured packet stream.
1899         """
1900         info = None
1901         seen = set()
1902         for packet in capture:
1903             try:
1904                 self.logger.debug(ppp("Got packet:", packet))
1905                 ip = packet[IP]
1906                 icmp = packet[ICMP]
1907                 payload_info = self.payload_to_info(packet[Raw])
1908                 packet_index = payload_info.index
1909                 if packet_index in seen:
1910                     raise Exception(ppp("Duplicate packet received", packet))
1911                 seen.add(packet_index)
1912                 self.assertEqual(payload_info.dst, self.src_dst_if.sw_if_index)
1913                 info = self._packet_infos[packet_index]
1914                 self.assertIsNotNone(info)
1915                 self.assertEqual(packet_index, info.index)
1916                 saved_packet = info.data
1917                 self.assertEqual(ip.src, saved_packet[IP].dst)
1918                 self.assertEqual(ip.dst, saved_packet[IP].src)
1919                 self.assertEqual(icmp.type, 0)  # echo reply
1920                 self.assertEqual(icmp.id, saved_packet[ICMP].id)
1921                 self.assertEqual(icmp.payload, saved_packet[ICMP].payload)
1922             except Exception:
1923                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1924                 raise
1925         for index in self._packet_infos:
1926             self.assertIn(index, seen,
1927                           "Packet with packet_index %d not received" % index)
1928
1929     def test_reassembly(self):
1930         """ basic reassembly """
1931
1932         self.pg_enable_capture()
1933         self.src_dst_if.add_stream(self.fragments_300)
1934         self.pg_start()
1935
1936         packets = self.src_dst_if.get_capture(len(self.pkt_infos))
1937         self.verify_capture(packets)
1938
1939         # run it all again to verify correctness
1940         self.pg_enable_capture()
1941         self.src_dst_if.add_stream(self.fragments_300)
1942         self.pg_start()
1943
1944         packets = self.src_dst_if.get_capture(len(self.pkt_infos))
1945         self.verify_capture(packets)
1946
1947
1948 class TestFIFReassembly(VppTestCase):
1949     """ Fragments in fragments reassembly """
1950
1951     @classmethod
1952     def setUpClass(cls):
1953         super(TestFIFReassembly, cls).setUpClass()
1954
1955         cls.create_pg_interfaces([0, 1])
1956         cls.src_if = cls.pg0
1957         cls.dst_if = cls.pg1
1958         for i in cls.pg_interfaces:
1959             i.admin_up()
1960             i.config_ip4()
1961             i.resolve_arp()
1962             i.config_ip6()
1963             i.resolve_ndp()
1964
1965         cls.packet_sizes = [64, 512, 1518, 9018]
1966         cls.padding = " abcdefghijklmn"
1967
1968     @classmethod
1969     def tearDownClass(cls):
1970         super(TestFIFReassembly, cls).tearDownClass()
1971
1972     def setUp(self):
1973         """ Test setup - force timeout on existing reassemblies """
1974         super(TestFIFReassembly, self).setUp()
1975         self.vapi.ip_reassembly_enable_disable(
1976             sw_if_index=self.src_if.sw_if_index, enable_ip4=True,
1977             enable_ip6=True)
1978         self.vapi.ip_reassembly_enable_disable(
1979             sw_if_index=self.dst_if.sw_if_index, enable_ip4=True,
1980             enable_ip6=True)
1981         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1982                                     max_reassembly_length=1000,
1983                                     expire_walk_interval_ms=10)
1984         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1985                                     max_reassembly_length=1000,
1986                                     expire_walk_interval_ms=10, is_ip6=1)
1987         self.sleep(.25)
1988         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1989                                     max_reassembly_length=1000,
1990                                     expire_walk_interval_ms=10000)
1991         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1992                                     max_reassembly_length=1000,
1993                                     expire_walk_interval_ms=10000, is_ip6=1)
1994
1995     def tearDown(self):
1996         super(TestFIFReassembly, self).tearDown()
1997
1998     def show_commands_at_teardown(self):
1999         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
2000         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
2001         self.logger.debug(self.vapi.ppcli("show buffers"))
2002
2003     def verify_capture(self, capture, ip_class, dropped_packet_indexes=[]):
2004         """Verify captured packet stream.
2005
2006         :param list capture: Captured packet stream.
2007         """
2008         info = None
2009         seen = set()
2010         for packet in capture:
2011             try:
2012                 self.logger.debug(ppp("Got packet:", packet))
2013                 ip = packet[ip_class]
2014                 udp = packet[UDP]
2015                 payload_info = self.payload_to_info(packet[Raw])
2016                 packet_index = payload_info.index
2017                 self.assertTrue(
2018                     packet_index not in dropped_packet_indexes,
2019                     ppp("Packet received, but should be dropped:", packet))
2020                 if packet_index in seen:
2021                     raise Exception(ppp("Duplicate packet received", packet))
2022                 seen.add(packet_index)
2023                 self.assertEqual(payload_info.dst, self.dst_if.sw_if_index)
2024                 info = self._packet_infos[packet_index]
2025                 self.assertTrue(info is not None)
2026                 self.assertEqual(packet_index, info.index)
2027                 saved_packet = info.data
2028                 self.assertEqual(ip.src, saved_packet[ip_class].src)
2029                 self.assertEqual(ip.dst, saved_packet[ip_class].dst)
2030                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
2031             except Exception:
2032                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
2033                 raise
2034         for index in self._packet_infos:
2035             self.assertTrue(index in seen or index in dropped_packet_indexes,
2036                             "Packet with packet_index %d not received" % index)
2037
2038     def test_fif4(self):
2039         """ Fragments in fragments (4o4) """
2040
2041         # TODO this should be ideally in setUpClass, but then we hit a bug
2042         # with VppIpRoute incorrectly reporting it's present when it's not
2043         # so we need to manually remove the vpp config, thus we cannot have
2044         # it shared for multiple test cases
2045         self.tun_ip4 = "1.1.1.2"
2046
2047         self.gre4 = VppGreInterface(self, self.src_if.local_ip4, self.tun_ip4)
2048         self.gre4.add_vpp_config()
2049         self.gre4.admin_up()
2050         self.gre4.config_ip4()
2051
2052         self.vapi.ip_reassembly_enable_disable(
2053             sw_if_index=self.gre4.sw_if_index, enable_ip4=True)
2054
2055         self.route4 = VppIpRoute(self, self.tun_ip4, 32,
2056                                  [VppRoutePath(self.src_if.remote_ip4,
2057                                                self.src_if.sw_if_index)])
2058         self.route4.add_vpp_config()
2059
2060         self.reset_packet_infos()
2061         for i in range(test_packet_count):
2062             info = self.create_packet_info(self.src_if, self.dst_if)
2063             payload = self.info_to_payload(info)
2064             # Ethernet header here is only for size calculation, thus it
2065             # doesn't matter how it's initialized. This is to ensure that
2066             # reassembled packet is not > 9000 bytes, so that it's not dropped
2067             p = (Ether() /
2068                  IP(id=i, src=self.src_if.remote_ip4,
2069                     dst=self.dst_if.remote_ip4) /
2070                  UDP(sport=1234, dport=5678) /
2071                  Raw(payload))
2072             size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
2073             self.extend_packet(p, size, self.padding)
2074             info.data = p[IP]  # use only IP part, without ethernet header
2075
2076         fragments = [x for _, p in self._packet_infos.items()
2077                      for x in fragment_rfc791(p.data, 400)]
2078
2079         encapped_fragments = \
2080             [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
2081              IP(src=self.tun_ip4, dst=self.src_if.local_ip4) /
2082                 GRE() /
2083                 p
2084                 for p in fragments]
2085
2086         fragmented_encapped_fragments = \
2087             [x for p in encapped_fragments
2088              for x in fragment_rfc791(p, 200)]
2089
2090         self.src_if.add_stream(fragmented_encapped_fragments)
2091
2092         self.pg_enable_capture(self.pg_interfaces)
2093         self.pg_start()
2094
2095         self.src_if.assert_nothing_captured()
2096         packets = self.dst_if.get_capture(len(self._packet_infos))
2097         self.verify_capture(packets, IP)
2098
2099         # TODO remove gre vpp config by hand until VppIpRoute gets fixed
2100         # so that it's query_vpp_config() works as it should
2101         self.gre4.remove_vpp_config()
2102         self.logger.debug(self.vapi.ppcli("show interface"))
2103
2104     def test_fif6(self):
2105         """ Fragments in fragments (6o6) """
2106         # TODO this should be ideally in setUpClass, but then we hit a bug
2107         # with VppIpRoute incorrectly reporting it's present when it's not
2108         # so we need to manually remove the vpp config, thus we cannot have
2109         # it shared for multiple test cases
2110         self.tun_ip6 = "1002::1"
2111
2112         self.gre6 = VppGreInterface(self, self.src_if.local_ip6, self.tun_ip6)
2113         self.gre6.add_vpp_config()
2114         self.gre6.admin_up()
2115         self.gre6.config_ip6()
2116
2117         self.vapi.ip_reassembly_enable_disable(
2118             sw_if_index=self.gre6.sw_if_index, enable_ip6=True)
2119
2120         self.route6 = VppIpRoute(self, self.tun_ip6, 128,
2121                                  [VppRoutePath(
2122                                      self.src_if.remote_ip6,
2123                                      self.src_if.sw_if_index)])
2124         self.route6.add_vpp_config()
2125
2126         self.reset_packet_infos()
2127         for i in range(test_packet_count):
2128             info = self.create_packet_info(self.src_if, self.dst_if)
2129             payload = self.info_to_payload(info)
2130             # Ethernet header here is only for size calculation, thus it
2131             # doesn't matter how it's initialized. This is to ensure that
2132             # reassembled packet is not > 9000 bytes, so that it's not dropped
2133             p = (Ether() /
2134                  IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
2135                  UDP(sport=1234, dport=5678) /
2136                  Raw(payload))
2137             size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
2138             self.extend_packet(p, size, self.padding)
2139             info.data = p[IPv6]  # use only IPv6 part, without ethernet header
2140
2141         fragments = [x for _, i in self._packet_infos.items()
2142                      for x in fragment_rfc8200(
2143                          i.data, i.index, 400)]
2144
2145         encapped_fragments = \
2146             [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
2147              IPv6(src=self.tun_ip6, dst=self.src_if.local_ip6) /
2148                 GRE() /
2149                 p
2150                 for p in fragments]
2151
2152         fragmented_encapped_fragments = \
2153             [x for p in encapped_fragments for x in (
2154                 fragment_rfc8200(
2155                     p,
2156                     2 * len(self._packet_infos) + p[IPv6ExtHdrFragment].id,
2157                     200)
2158                 if IPv6ExtHdrFragment in p else [p]
2159             )
2160             ]
2161
2162         self.src_if.add_stream(fragmented_encapped_fragments)
2163
2164         self.pg_enable_capture(self.pg_interfaces)
2165         self.pg_start()
2166
2167         self.src_if.assert_nothing_captured()
2168         packets = self.dst_if.get_capture(len(self._packet_infos))
2169         self.verify_capture(packets, IPv6)
2170
2171         # TODO remove gre vpp config by hand until VppIpRoute gets fixed
2172         # so that it's query_vpp_config() works as it should
2173         self.gre6.remove_vpp_config()
2174
2175
2176 if __name__ == '__main__':
2177     unittest.main(testRunner=VppTestRunner)