ip: extension header parsing fails for fragment header
[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], self.pg0)
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
1459 class TestIPv6MWReassembly(VppTestCase):
1460     """ IPv6 Reassembly (multiple workers) """
1461     vpp_worker_count = 3
1462
1463     @classmethod
1464     def setUpClass(cls):
1465         super(TestIPv6MWReassembly, cls).setUpClass()
1466
1467         cls.create_pg_interfaces(range(cls.vpp_worker_count+1))
1468         cls.src_if = cls.pg0
1469         cls.send_ifs = cls.pg_interfaces[:-1]
1470         cls.dst_if = cls.pg_interfaces[-1]
1471
1472         # setup all interfaces
1473         for i in cls.pg_interfaces:
1474             i.admin_up()
1475             i.config_ip6()
1476             i.resolve_ndp()
1477
1478         # packets sizes reduced here because we are generating packets without
1479         # Ethernet headers, which are added later (diff fragments go via
1480         # different interfaces)
1481         cls.packet_sizes = [64-len(Ether()), 512-len(Ether()),
1482                             1518-len(Ether()), 9018-len(Ether())]
1483         cls.padding = " abcdefghijklmn"
1484         cls.create_stream(cls.packet_sizes)
1485         cls.create_fragments()
1486
1487     @classmethod
1488     def tearDownClass(cls):
1489         super(TestIPv6MWReassembly, cls).tearDownClass()
1490
1491     def setUp(self):
1492         """ Test setup - force timeout on existing reassemblies """
1493         super(TestIPv6MWReassembly, self).setUp()
1494         for intf in self.send_ifs:
1495             self.vapi.ip_reassembly_enable_disable(
1496                 sw_if_index=intf.sw_if_index, enable_ip6=True)
1497         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1498                                     max_reassembly_length=1000,
1499                                     expire_walk_interval_ms=10, is_ip6=1)
1500         self.virtual_sleep(.25)
1501         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1502                                     max_reassembly_length=1000,
1503                                     expire_walk_interval_ms=1000, is_ip6=1)
1504
1505     def tearDown(self):
1506         super(TestIPv6MWReassembly, self).tearDown()
1507
1508     def show_commands_at_teardown(self):
1509         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1510         self.logger.debug(self.vapi.ppcli("show buffers"))
1511
1512     @classmethod
1513     def create_stream(cls, packet_sizes, packet_count=test_packet_count):
1514         """Create input packet stream
1515
1516         :param list packet_sizes: Required packet sizes.
1517         """
1518         for i in range(0, packet_count):
1519             info = cls.create_packet_info(cls.src_if, cls.src_if)
1520             payload = cls.info_to_payload(info)
1521             p = (IPv6(src=cls.src_if.remote_ip6,
1522                       dst=cls.dst_if.remote_ip6) /
1523                  UDP(sport=1234, dport=5678) /
1524                  Raw(payload))
1525             size = packet_sizes[(i // 2) % len(packet_sizes)]
1526             cls.extend_packet(p, size, cls.padding)
1527             info.data = p
1528
1529     @classmethod
1530     def create_fragments(cls):
1531         infos = cls._packet_infos
1532         cls.pkt_infos = []
1533         for index, info in infos.items():
1534             p = info.data
1535             # cls.logger.debug(ppp("Packet:",
1536             #                      p.__class__(scapy.compat.raw(p))))
1537             fragments_400 = fragment_rfc8200(p, index, 400)
1538             cls.pkt_infos.append((index, fragments_400))
1539         cls.fragments_400 = [
1540             x for (_, frags) in cls.pkt_infos for x in frags]
1541         cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, " %
1542                          (len(infos), len(cls.fragments_400)))
1543
1544     def verify_capture(self, capture, dropped_packet_indexes=[]):
1545         """Verify captured packet strea .
1546
1547         :param list capture: Captured packet stream.
1548         """
1549         info = None
1550         seen = set()
1551         for packet in capture:
1552             try:
1553                 self.logger.debug(ppp("Got packet:", packet))
1554                 ip = packet[IPv6]
1555                 udp = packet[UDP]
1556                 payload_info = self.payload_to_info(packet[Raw])
1557                 packet_index = payload_info.index
1558                 self.assertTrue(
1559                     packet_index not in dropped_packet_indexes,
1560                     ppp("Packet received, but should be dropped:", packet))
1561                 if packet_index in seen:
1562                     raise Exception(ppp("Duplicate packet received", packet))
1563                 seen.add(packet_index)
1564                 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
1565                 info = self._packet_infos[packet_index]
1566                 self.assertTrue(info is not None)
1567                 self.assertEqual(packet_index, info.index)
1568                 saved_packet = info.data
1569                 self.assertEqual(ip.src, saved_packet[IPv6].src)
1570                 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
1571                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1572             except Exception:
1573                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1574                 raise
1575         for index in self._packet_infos:
1576             self.assertTrue(index in seen or index in dropped_packet_indexes,
1577                             "Packet with packet_index %d not received" % index)
1578
1579     def send_packets(self, packets):
1580         for counter in range(self.vpp_worker_count):
1581             if 0 == len(packets[counter]):
1582                 continue
1583             send_if = self.send_ifs[counter]
1584             send_if.add_stream(
1585                 (Ether(dst=send_if.local_mac, src=send_if.remote_mac) / x
1586                  for x in packets[counter]),
1587                 worker=counter)
1588         self.pg_start()
1589
1590     def test_worker_conflict(self):
1591         """ 1st and FO=0 fragments on different workers """
1592
1593         # in first wave we send fragments which don't start at offset 0
1594         # then we send fragments with offset 0 on a different thread
1595         # then the rest of packets on a random thread
1596         first_packets = [[] for n in range(self.vpp_worker_count)]
1597         second_packets = [[] for n in range(self.vpp_worker_count)]
1598         rest_of_packets = [[] for n in range(self.vpp_worker_count)]
1599         for (_, p) in self.pkt_infos:
1600             wi = randrange(self.vpp_worker_count)
1601             second_packets[wi].append(p[0])
1602             if len(p) <= 1:
1603                 continue
1604             wi2 = wi
1605             while wi2 == wi:
1606                 wi2 = randrange(self.vpp_worker_count)
1607             first_packets[wi2].append(p[1])
1608             wi3 = randrange(self.vpp_worker_count)
1609             rest_of_packets[wi3].extend(p[2:])
1610
1611         self.pg_enable_capture()
1612         self.send_packets(first_packets)
1613         self.send_packets(second_packets)
1614         self.send_packets(rest_of_packets)
1615
1616         packets = self.dst_if.get_capture(len(self.pkt_infos))
1617         self.verify_capture(packets)
1618         for send_if in self.send_ifs:
1619             send_if.assert_nothing_captured()
1620
1621         self.logger.debug(self.vapi.ppcli("show trace"))
1622         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1623         self.logger.debug(self.vapi.ppcli("show buffers"))
1624         self.vapi.cli("clear trace")
1625
1626         self.pg_enable_capture()
1627         self.send_packets(first_packets)
1628         self.send_packets(second_packets)
1629         self.send_packets(rest_of_packets)
1630
1631         packets = self.dst_if.get_capture(len(self.pkt_infos))
1632         self.verify_capture(packets)
1633         for send_if in self.send_ifs:
1634             send_if.assert_nothing_captured()
1635
1636
1637 class TestIPv6SVReassembly(VppTestCase):
1638     """ IPv6 Shallow Virtual Reassembly """
1639
1640     @classmethod
1641     def setUpClass(cls):
1642         super(TestIPv6SVReassembly, cls).setUpClass()
1643
1644         cls.create_pg_interfaces([0, 1])
1645         cls.src_if = cls.pg0
1646         cls.dst_if = cls.pg1
1647
1648         # setup all interfaces
1649         for i in cls.pg_interfaces:
1650             i.admin_up()
1651             i.config_ip6()
1652             i.resolve_ndp()
1653
1654     def setUp(self):
1655         """ Test setup - force timeout on existing reassemblies """
1656         super(TestIPv6SVReassembly, self).setUp()
1657         self.vapi.ip_reassembly_enable_disable(
1658             sw_if_index=self.src_if.sw_if_index, enable_ip6=True,
1659             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL)
1660         self.vapi.ip_reassembly_set(
1661             timeout_ms=0, max_reassemblies=1000,
1662             max_reassembly_length=1000,
1663             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
1664             expire_walk_interval_ms=10, is_ip6=1)
1665         self.virtual_sleep(.25)
1666         self.vapi.ip_reassembly_set(
1667             timeout_ms=1000000, max_reassemblies=1000,
1668             max_reassembly_length=1000,
1669             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
1670             expire_walk_interval_ms=10000, is_ip6=1)
1671
1672     def tearDown(self):
1673         super(TestIPv6SVReassembly, self).tearDown()
1674         self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
1675         self.logger.debug(self.vapi.ppcli("show buffers"))
1676
1677     def test_basic(self):
1678         """ basic reassembly """
1679         payload_len = 1000
1680         payload = ""
1681         counter = 0
1682         while len(payload) < payload_len:
1683             payload += "%u " % counter
1684             counter += 1
1685
1686         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1687              IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1688              UDP(sport=1234, dport=5678) /
1689              Raw(payload))
1690         fragments = fragment_rfc8200(p, 1, payload_len/4)
1691
1692         # send fragment #2 - should be cached inside reassembly
1693         self.pg_enable_capture()
1694         self.src_if.add_stream(fragments[1])
1695         self.pg_start()
1696         self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
1697         self.logger.debug(self.vapi.ppcli("show buffers"))
1698         self.logger.debug(self.vapi.ppcli("show trace"))
1699         self.dst_if.assert_nothing_captured()
1700
1701         # send fragment #1 - reassembly is finished now and both fragments
1702         # forwarded
1703         self.pg_enable_capture()
1704         self.src_if.add_stream(fragments[0])
1705         self.pg_start()
1706         self.logger.debug(self.vapi.ppcli("show ip6-sv-reassembly details"))
1707         self.logger.debug(self.vapi.ppcli("show buffers"))
1708         self.logger.debug(self.vapi.ppcli("show trace"))
1709         c = self.dst_if.get_capture(2)
1710         for sent, recvd in zip([fragments[1], fragments[0]], c):
1711             self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
1712             self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
1713             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
1714
1715         # send rest of fragments - should be immediately forwarded
1716         self.pg_enable_capture()
1717         self.src_if.add_stream(fragments[2:])
1718         self.pg_start()
1719         c = self.dst_if.get_capture(len(fragments[2:]))
1720         for sent, recvd in zip(fragments[2:], c):
1721             self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
1722             self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
1723             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
1724
1725     def test_verify_clear_trace_mid_reassembly(self):
1726         """ verify clear trace works mid-reassembly """
1727         payload_len = 1000
1728         payload = ""
1729         counter = 0
1730         while len(payload) < payload_len:
1731             payload += "%u " % counter
1732             counter += 1
1733
1734         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1735              IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1736              UDP(sport=1234, dport=5678) /
1737              Raw(payload))
1738         fragments = fragment_rfc8200(p, 1, payload_len/4)
1739
1740         self.pg_enable_capture()
1741         self.src_if.add_stream(fragments[1])
1742         self.pg_start()
1743
1744         self.logger.debug(self.vapi.cli("show trace"))
1745         self.vapi.cli("clear trace")
1746
1747         self.pg_enable_capture()
1748         self.src_if.add_stream(fragments[0])
1749         self.pg_start()
1750         self.dst_if.get_capture(2)
1751
1752         self.logger.debug(self.vapi.cli("show trace"))
1753         self.vapi.cli("clear trace")
1754
1755         self.pg_enable_capture()
1756         self.src_if.add_stream(fragments[2:])
1757         self.pg_start()
1758         self.dst_if.get_capture(len(fragments[2:]))
1759
1760     def test_timeout(self):
1761         """ reassembly timeout """
1762         payload_len = 1000
1763         payload = ""
1764         counter = 0
1765         while len(payload) < payload_len:
1766             payload += "%u " % counter
1767             counter += 1
1768
1769         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1770              IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1771              UDP(sport=1234, dport=5678) /
1772              Raw(payload))
1773         fragments = fragment_rfc8200(p, 1, payload_len/4)
1774
1775         self.vapi.ip_reassembly_set(
1776             timeout_ms=100, max_reassemblies=1000,
1777             max_reassembly_length=1000,
1778             expire_walk_interval_ms=50,
1779             is_ip6=1,
1780             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL)
1781
1782         # send fragments #2 and #1 - should be forwarded
1783         self.pg_enable_capture()
1784         self.src_if.add_stream(fragments[0:2])
1785         self.pg_start()
1786         self.logger.debug(self.vapi.ppcli("show ip4-sv-reassembly details"))
1787         self.logger.debug(self.vapi.ppcli("show buffers"))
1788         self.logger.debug(self.vapi.ppcli("show trace"))
1789         c = self.dst_if.get_capture(2)
1790         for sent, recvd in zip([fragments[1], fragments[0]], c):
1791             self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
1792             self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
1793             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
1794
1795         # wait for cleanup
1796         self.virtual_sleep(.25, "wait before sending rest of fragments")
1797
1798         # send rest of fragments - shouldn't be forwarded
1799         self.pg_enable_capture()
1800         self.src_if.add_stream(fragments[2:])
1801         self.pg_start()
1802         self.dst_if.assert_nothing_captured()
1803
1804     def test_lru(self):
1805         """ reassembly reuses LRU element """
1806
1807         self.vapi.ip_reassembly_set(
1808             timeout_ms=1000000, max_reassemblies=1,
1809             max_reassembly_length=1000,
1810             type=VppEnum.vl_api_ip_reass_type_t.IP_REASS_TYPE_SHALLOW_VIRTUAL,
1811             is_ip6=1, expire_walk_interval_ms=10000)
1812
1813         payload_len = 1000
1814         payload = ""
1815         counter = 0
1816         while len(payload) < payload_len:
1817             payload += "%u " % counter
1818             counter += 1
1819
1820         packet_count = 10
1821
1822         fragments = [f
1823                      for i in range(packet_count)
1824                      for p in (Ether(dst=self.src_if.local_mac,
1825                                      src=self.src_if.remote_mac) /
1826                                IPv6(src=self.src_if.remote_ip6,
1827                                     dst=self.dst_if.remote_ip6) /
1828                                UDP(sport=1234, dport=5678) /
1829                                Raw(payload))
1830                      for f in fragment_rfc8200(p, i, payload_len/4)]
1831
1832         self.pg_enable_capture()
1833         self.src_if.add_stream(fragments)
1834         self.pg_start()
1835         c = self.dst_if.get_capture(len(fragments))
1836         for sent, recvd in zip(fragments, c):
1837             self.assertEqual(sent[IPv6].src, recvd[IPv6].src)
1838             self.assertEqual(sent[IPv6].dst, recvd[IPv6].dst)
1839             self.assertEqual(sent[Raw].payload, recvd[Raw].payload)
1840
1841
1842 class TestIPv4ReassemblyLocalNode(VppTestCase):
1843     """ IPv4 Reassembly for packets coming to ip4-local node """
1844
1845     @classmethod
1846     def setUpClass(cls):
1847         super(TestIPv4ReassemblyLocalNode, cls).setUpClass()
1848
1849         cls.create_pg_interfaces([0])
1850         cls.src_dst_if = cls.pg0
1851
1852         # setup all interfaces
1853         for i in cls.pg_interfaces:
1854             i.admin_up()
1855             i.config_ip4()
1856             i.resolve_arp()
1857
1858         cls.padding = " abcdefghijklmn"
1859         cls.create_stream()
1860         cls.create_fragments()
1861
1862     @classmethod
1863     def tearDownClass(cls):
1864         super(TestIPv4ReassemblyLocalNode, cls).tearDownClass()
1865
1866     def setUp(self):
1867         """ Test setup - force timeout on existing reassemblies """
1868         super(TestIPv4ReassemblyLocalNode, self).setUp()
1869         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1870                                     max_reassembly_length=1000,
1871                                     expire_walk_interval_ms=10)
1872         self.virtual_sleep(.25)
1873         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1874                                     max_reassembly_length=1000,
1875                                     expire_walk_interval_ms=10000)
1876
1877     def tearDown(self):
1878         super(TestIPv4ReassemblyLocalNode, self).tearDown()
1879
1880     def show_commands_at_teardown(self):
1881         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
1882         self.logger.debug(self.vapi.ppcli("show buffers"))
1883
1884     @classmethod
1885     def create_stream(cls, packet_count=test_packet_count):
1886         """Create input packet stream for defined interface.
1887
1888         :param list packet_sizes: Required packet sizes.
1889         """
1890         for i in range(0, packet_count):
1891             info = cls.create_packet_info(cls.src_dst_if, cls.src_dst_if)
1892             payload = cls.info_to_payload(info)
1893             p = (Ether(dst=cls.src_dst_if.local_mac,
1894                        src=cls.src_dst_if.remote_mac) /
1895                  IP(id=info.index, src=cls.src_dst_if.remote_ip4,
1896                     dst=cls.src_dst_if.local_ip4) /
1897                  ICMP(type='echo-request', id=1234) /
1898                  Raw(payload))
1899             cls.extend_packet(p, 1518, cls.padding)
1900             info.data = p
1901
1902     @classmethod
1903     def create_fragments(cls):
1904         infos = cls._packet_infos
1905         cls.pkt_infos = []
1906         for index, info in infos.items():
1907             p = info.data
1908             # cls.logger.debug(ppp("Packet:",
1909             #                      p.__class__(scapy.compat.raw(p))))
1910             fragments_300 = fragment_rfc791(p, 300)
1911             cls.pkt_infos.append((index, fragments_300))
1912         cls.fragments_300 = [x for (_, frags) in cls.pkt_infos for x in frags]
1913         cls.logger.debug("Fragmented %s packets into %s 300-byte fragments" %
1914                          (len(infos), len(cls.fragments_300)))
1915
1916     def verify_capture(self, capture):
1917         """Verify captured packet stream.
1918
1919         :param list capture: Captured packet stream.
1920         """
1921         info = None
1922         seen = set()
1923         for packet in capture:
1924             try:
1925                 self.logger.debug(ppp("Got packet:", packet))
1926                 ip = packet[IP]
1927                 icmp = packet[ICMP]
1928                 payload_info = self.payload_to_info(packet[Raw])
1929                 packet_index = payload_info.index
1930                 if packet_index in seen:
1931                     raise Exception(ppp("Duplicate packet received", packet))
1932                 seen.add(packet_index)
1933                 self.assertEqual(payload_info.dst, self.src_dst_if.sw_if_index)
1934                 info = self._packet_infos[packet_index]
1935                 self.assertIsNotNone(info)
1936                 self.assertEqual(packet_index, info.index)
1937                 saved_packet = info.data
1938                 self.assertEqual(ip.src, saved_packet[IP].dst)
1939                 self.assertEqual(ip.dst, saved_packet[IP].src)
1940                 self.assertEqual(icmp.type, 0)  # echo reply
1941                 self.assertEqual(icmp.id, saved_packet[ICMP].id)
1942                 self.assertEqual(icmp.payload, saved_packet[ICMP].payload)
1943             except Exception:
1944                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1945                 raise
1946         for index in self._packet_infos:
1947             self.assertIn(index, seen,
1948                           "Packet with packet_index %d not received" % index)
1949
1950     def test_reassembly(self):
1951         """ basic reassembly """
1952
1953         self.pg_enable_capture()
1954         self.src_dst_if.add_stream(self.fragments_300)
1955         self.pg_start()
1956
1957         packets = self.src_dst_if.get_capture(len(self.pkt_infos))
1958         self.verify_capture(packets)
1959
1960         # run it all again to verify correctness
1961         self.pg_enable_capture()
1962         self.src_dst_if.add_stream(self.fragments_300)
1963         self.pg_start()
1964
1965         packets = self.src_dst_if.get_capture(len(self.pkt_infos))
1966         self.verify_capture(packets)
1967
1968
1969 class TestFIFReassembly(VppTestCase):
1970     """ Fragments in fragments reassembly """
1971
1972     @classmethod
1973     def setUpClass(cls):
1974         super(TestFIFReassembly, cls).setUpClass()
1975
1976         cls.create_pg_interfaces([0, 1])
1977         cls.src_if = cls.pg0
1978         cls.dst_if = cls.pg1
1979         for i in cls.pg_interfaces:
1980             i.admin_up()
1981             i.config_ip4()
1982             i.resolve_arp()
1983             i.config_ip6()
1984             i.resolve_ndp()
1985
1986         cls.packet_sizes = [64, 512, 1518, 9018]
1987         cls.padding = " abcdefghijklmn"
1988
1989     @classmethod
1990     def tearDownClass(cls):
1991         super(TestFIFReassembly, cls).tearDownClass()
1992
1993     def setUp(self):
1994         """ Test setup - force timeout on existing reassemblies """
1995         super(TestFIFReassembly, self).setUp()
1996         self.vapi.ip_reassembly_enable_disable(
1997             sw_if_index=self.src_if.sw_if_index, enable_ip4=True,
1998             enable_ip6=True)
1999         self.vapi.ip_reassembly_enable_disable(
2000             sw_if_index=self.dst_if.sw_if_index, enable_ip4=True,
2001             enable_ip6=True)
2002         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
2003                                     max_reassembly_length=1000,
2004                                     expire_walk_interval_ms=10)
2005         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
2006                                     max_reassembly_length=1000,
2007                                     expire_walk_interval_ms=10, is_ip6=1)
2008         self.virtual_sleep(.25)
2009         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
2010                                     max_reassembly_length=1000,
2011                                     expire_walk_interval_ms=10000)
2012         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
2013                                     max_reassembly_length=1000,
2014                                     expire_walk_interval_ms=10000, is_ip6=1)
2015
2016     def tearDown(self):
2017         super(TestFIFReassembly, self).tearDown()
2018
2019     def show_commands_at_teardown(self):
2020         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
2021         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
2022         self.logger.debug(self.vapi.ppcli("show buffers"))
2023
2024     def verify_capture(self, capture, ip_class, dropped_packet_indexes=[]):
2025         """Verify captured packet stream.
2026
2027         :param list capture: Captured packet stream.
2028         """
2029         info = None
2030         seen = set()
2031         for packet in capture:
2032             try:
2033                 self.logger.debug(ppp("Got packet:", packet))
2034                 ip = packet[ip_class]
2035                 udp = packet[UDP]
2036                 payload_info = self.payload_to_info(packet[Raw])
2037                 packet_index = payload_info.index
2038                 self.assertTrue(
2039                     packet_index not in dropped_packet_indexes,
2040                     ppp("Packet received, but should be dropped:", packet))
2041                 if packet_index in seen:
2042                     raise Exception(ppp("Duplicate packet received", packet))
2043                 seen.add(packet_index)
2044                 self.assertEqual(payload_info.dst, self.dst_if.sw_if_index)
2045                 info = self._packet_infos[packet_index]
2046                 self.assertTrue(info is not None)
2047                 self.assertEqual(packet_index, info.index)
2048                 saved_packet = info.data
2049                 self.assertEqual(ip.src, saved_packet[ip_class].src)
2050                 self.assertEqual(ip.dst, saved_packet[ip_class].dst)
2051                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
2052             except Exception:
2053                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
2054                 raise
2055         for index in self._packet_infos:
2056             self.assertTrue(index in seen or index in dropped_packet_indexes,
2057                             "Packet with packet_index %d not received" % index)
2058
2059     def test_fif4(self):
2060         """ Fragments in fragments (4o4) """
2061
2062         # TODO this should be ideally in setUpClass, but then we hit a bug
2063         # with VppIpRoute incorrectly reporting it's present when it's not
2064         # so we need to manually remove the vpp config, thus we cannot have
2065         # it shared for multiple test cases
2066         self.tun_ip4 = "1.1.1.2"
2067
2068         self.gre4 = VppGreInterface(self, self.src_if.local_ip4, self.tun_ip4)
2069         self.gre4.add_vpp_config()
2070         self.gre4.admin_up()
2071         self.gre4.config_ip4()
2072
2073         self.vapi.ip_reassembly_enable_disable(
2074             sw_if_index=self.gre4.sw_if_index, enable_ip4=True)
2075
2076         self.route4 = VppIpRoute(self, self.tun_ip4, 32,
2077                                  [VppRoutePath(self.src_if.remote_ip4,
2078                                                self.src_if.sw_if_index)])
2079         self.route4.add_vpp_config()
2080
2081         self.reset_packet_infos()
2082         for i in range(test_packet_count):
2083             info = self.create_packet_info(self.src_if, self.dst_if)
2084             payload = self.info_to_payload(info)
2085             # Ethernet header here is only for size calculation, thus it
2086             # doesn't matter how it's initialized. This is to ensure that
2087             # reassembled packet is not > 9000 bytes, so that it's not dropped
2088             p = (Ether() /
2089                  IP(id=i, src=self.src_if.remote_ip4,
2090                     dst=self.dst_if.remote_ip4) /
2091                  UDP(sport=1234, dport=5678) /
2092                  Raw(payload))
2093             size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
2094             self.extend_packet(p, size, self.padding)
2095             info.data = p[IP]  # use only IP part, without ethernet header
2096
2097         fragments = [x for _, p in self._packet_infos.items()
2098                      for x in fragment_rfc791(p.data, 400)]
2099
2100         encapped_fragments = \
2101             [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
2102              IP(src=self.tun_ip4, dst=self.src_if.local_ip4) /
2103                 GRE() /
2104                 p
2105                 for p in fragments]
2106
2107         fragmented_encapped_fragments = \
2108             [x for p in encapped_fragments
2109              for x in fragment_rfc791(p, 200)]
2110
2111         self.src_if.add_stream(fragmented_encapped_fragments)
2112
2113         self.pg_enable_capture(self.pg_interfaces)
2114         self.pg_start()
2115
2116         self.src_if.assert_nothing_captured()
2117         packets = self.dst_if.get_capture(len(self._packet_infos))
2118         self.verify_capture(packets, IP)
2119
2120         # TODO remove gre vpp config by hand until VppIpRoute gets fixed
2121         # so that it's query_vpp_config() works as it should
2122         self.gre4.remove_vpp_config()
2123         self.logger.debug(self.vapi.ppcli("show interface"))
2124
2125     def test_fif6(self):
2126         """ Fragments in fragments (6o6) """
2127         # TODO this should be ideally in setUpClass, but then we hit a bug
2128         # with VppIpRoute incorrectly reporting it's present when it's not
2129         # so we need to manually remove the vpp config, thus we cannot have
2130         # it shared for multiple test cases
2131         self.tun_ip6 = "1002::1"
2132
2133         self.gre6 = VppGreInterface(self, self.src_if.local_ip6, self.tun_ip6)
2134         self.gre6.add_vpp_config()
2135         self.gre6.admin_up()
2136         self.gre6.config_ip6()
2137
2138         self.vapi.ip_reassembly_enable_disable(
2139             sw_if_index=self.gre6.sw_if_index, enable_ip6=True)
2140
2141         self.route6 = VppIpRoute(self, self.tun_ip6, 128,
2142                                  [VppRoutePath(
2143                                      self.src_if.remote_ip6,
2144                                      self.src_if.sw_if_index)])
2145         self.route6.add_vpp_config()
2146
2147         self.reset_packet_infos()
2148         for i in range(test_packet_count):
2149             info = self.create_packet_info(self.src_if, self.dst_if)
2150             payload = self.info_to_payload(info)
2151             # Ethernet header here is only for size calculation, thus it
2152             # doesn't matter how it's initialized. This is to ensure that
2153             # reassembled packet is not > 9000 bytes, so that it's not dropped
2154             p = (Ether() /
2155                  IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
2156                  UDP(sport=1234, dport=5678) /
2157                  Raw(payload))
2158             size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
2159             self.extend_packet(p, size, self.padding)
2160             info.data = p[IPv6]  # use only IPv6 part, without ethernet header
2161
2162         fragments = [x for _, i in self._packet_infos.items()
2163                      for x in fragment_rfc8200(
2164                          i.data, i.index, 400)]
2165
2166         encapped_fragments = \
2167             [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
2168              IPv6(src=self.tun_ip6, dst=self.src_if.local_ip6) /
2169                 GRE() /
2170                 p
2171                 for p in fragments]
2172
2173         fragmented_encapped_fragments = \
2174             [x for p in encapped_fragments for x in (
2175                 fragment_rfc8200(
2176                     p,
2177                     2 * len(self._packet_infos) + p[IPv6ExtHdrFragment].id,
2178                     200)
2179                 if IPv6ExtHdrFragment in p else [p]
2180             )
2181             ]
2182
2183         self.src_if.add_stream(fragmented_encapped_fragments)
2184
2185         self.pg_enable_capture(self.pg_interfaces)
2186         self.pg_start()
2187
2188         self.src_if.assert_nothing_captured()
2189         packets = self.dst_if.get_capture(len(self._packet_infos))
2190         self.verify_capture(packets, IPv6)
2191
2192         # TODO remove gre vpp config by hand until VppIpRoute gets fixed
2193         # so that it's query_vpp_config() works as it should
2194         self.gre6.remove_vpp_config()
2195
2196
2197 if __name__ == '__main__':
2198     unittest.main(testRunner=VppTestRunner)