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