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