ip: respect buffer boundary when searching for ipv6 headers
[vpp.git] / test / test_reassembly.py
1 #!/usr/bin/env python
2
3 import six
4 import unittest
5 from random import shuffle, choice, randrange
6
7 from framework import VppTestCase, VppTestRunner
8
9 import scapy.compat
10 from scapy.packet import Raw
11 from scapy.layers.l2 import Ether, GRE
12 from scapy.layers.inet import IP, UDP, ICMP
13 from scapy.layers.inet6 import HBHOptUnknown, ICMPv6ParamProblem,\
14         ICMPv6TimeExceeded, IPv6, IPv6ExtHdrFragment, IPv6ExtHdrHopByHop
15 from framework import VppTestCase, VppTestRunner
16 from util import ppp, ppc, fragment_rfc791, fragment_rfc8200
17 from vpp_gre_interface import VppGreInterface
18 from vpp_ip import DpoProto
19 from vpp_ip_route import VppIpRoute, VppRoutePath, FibPathProto
20 from vpp_papi import VppEnum
21
22 # 35 is enough to have >257 400-byte fragments
23 test_packet_count = 35
24
25 # number of workers used for multi-worker test cases
26 worker_count = 3
27
28
29 class TestIPv4Reassembly(VppTestCase):
30     """ IPv4 Reassembly """
31
32     @classmethod
33     def setUpClass(cls):
34         super(TestIPv4Reassembly, cls).setUpClass()
35
36         cls.create_pg_interfaces([0, 1])
37         cls.src_if = cls.pg0
38         cls.dst_if = cls.pg1
39
40         # setup all interfaces
41         for i in cls.pg_interfaces:
42             i.admin_up()
43             i.config_ip4()
44             i.resolve_arp()
45
46         # packet sizes
47         cls.packet_sizes = [64, 512, 1518, 9018]
48         cls.padding = " abcdefghijklmn"
49         cls.create_stream(cls.packet_sizes)
50         cls.create_fragments()
51
52     @classmethod
53     def tearDownClass(cls):
54         super(TestIPv4Reassembly, cls).tearDownClass()
55
56     def setUp(self):
57         """ Test setup - force timeout on existing reassemblies """
58         super(TestIPv4Reassembly, self).setUp()
59         self.vapi.ip_reassembly_enable_disable(
60             sw_if_index=self.src_if.sw_if_index, enable_ip4=True)
61         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
62                                     max_reassembly_length=1000,
63                                     expire_walk_interval_ms=10)
64         self.sleep(.25)
65         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
66                                     max_reassembly_length=1000,
67                                     expire_walk_interval_ms=10000)
68
69     def tearDown(self):
70         super(TestIPv4Reassembly, self).tearDown()
71
72     def show_commands_at_teardown(self):
73         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
74         self.logger.debug(self.vapi.ppcli("show buffers"))
75
76     @classmethod
77     def create_stream(cls, packet_sizes, packet_count=test_packet_count):
78         """Create input packet stream
79
80         :param list packet_sizes: Required packet sizes.
81         """
82         for i in range(0, packet_count):
83             info = cls.create_packet_info(cls.src_if, cls.src_if)
84             payload = cls.info_to_payload(info)
85             p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
86                  IP(id=info.index, src=cls.src_if.remote_ip4,
87                     dst=cls.dst_if.remote_ip4) /
88                  UDP(sport=1234, dport=5678) /
89                  Raw(payload))
90             size = packet_sizes[(i // 2) % len(packet_sizes)]
91             cls.extend_packet(p, size, cls.padding)
92             info.data = p
93
94     @classmethod
95     def create_fragments(cls):
96         infos = cls._packet_infos
97         cls.pkt_infos = []
98         for index, info in six.iteritems(infos):
99             p = info.data
100             # cls.logger.debug(ppp("Packet:",
101             #                      p.__class__(scapy.compat.raw(p))))
102             fragments_400 = fragment_rfc791(p, 400)
103             fragments_300 = fragment_rfc791(p, 300)
104             fragments_200 = [
105                 x for f in fragments_400 for x in fragment_rfc791(f, 200)]
106             cls.pkt_infos.append(
107                 (index, fragments_400, fragments_300, fragments_200))
108         cls.fragments_400 = [
109             x for (_, frags, _, _) in cls.pkt_infos for x in frags]
110         cls.fragments_300 = [
111             x for (_, _, frags, _) in cls.pkt_infos for x in frags]
112         cls.fragments_200 = [
113             x for (_, _, _, frags) in cls.pkt_infos for x in frags]
114         cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
115                          "%s 300-byte fragments and %s 200-byte fragments" %
116                          (len(infos), len(cls.fragments_400),
117                              len(cls.fragments_300), len(cls.fragments_200)))
118
119     def verify_capture(self, capture, dropped_packet_indexes=[]):
120         """Verify captured packet stream.
121
122         :param list capture: Captured packet stream.
123         """
124         info = None
125         seen = set()
126         for packet in capture:
127             try:
128                 self.logger.debug(ppp("Got packet:", packet))
129                 ip = packet[IP]
130                 udp = packet[UDP]
131                 payload_info = self.payload_to_info(packet[Raw])
132                 packet_index = payload_info.index
133                 self.assertTrue(
134                     packet_index not in dropped_packet_indexes,
135                     ppp("Packet received, but should be dropped:", packet))
136                 if packet_index in seen:
137                     raise Exception(ppp("Duplicate packet received", packet))
138                 seen.add(packet_index)
139                 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
140                 info = self._packet_infos[packet_index]
141                 self.assertTrue(info is not None)
142                 self.assertEqual(packet_index, info.index)
143                 saved_packet = info.data
144                 self.assertEqual(ip.src, saved_packet[IP].src)
145                 self.assertEqual(ip.dst, saved_packet[IP].dst)
146                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
147             except Exception:
148                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
149                 raise
150         for index in self._packet_infos:
151             self.assertTrue(index in seen or index in dropped_packet_indexes,
152                             "Packet with packet_index %d not received" % index)
153
154     def test_reassembly(self):
155         """ basic reassembly """
156
157         self.pg_enable_capture()
158         self.src_if.add_stream(self.fragments_200)
159         self.pg_start()
160
161         packets = self.dst_if.get_capture(len(self.pkt_infos))
162         self.verify_capture(packets)
163         self.src_if.assert_nothing_captured()
164
165         # run it all again to verify correctness
166         self.pg_enable_capture()
167         self.src_if.add_stream(self.fragments_200)
168         self.pg_start()
169
170         packets = self.dst_if.get_capture(len(self.pkt_infos))
171         self.verify_capture(packets)
172         self.src_if.assert_nothing_captured()
173
174     def test_reversed(self):
175         """ reverse order reassembly """
176
177         fragments = list(self.fragments_200)
178         fragments.reverse()
179
180         self.pg_enable_capture()
181         self.src_if.add_stream(fragments)
182         self.pg_start()
183
184         packets = self.dst_if.get_capture(len(self.packet_infos))
185         self.verify_capture(packets)
186         self.src_if.assert_nothing_captured()
187
188         # run it all again to verify correctness
189         self.pg_enable_capture()
190         self.src_if.add_stream(fragments)
191         self.pg_start()
192
193         packets = self.dst_if.get_capture(len(self.packet_infos))
194         self.verify_capture(packets)
195         self.src_if.assert_nothing_captured()
196
197     def test_long_fragment_chain(self):
198         """ long fragment chain """
199
200         error_cnt_str = \
201             "/err/ip4-full-reassembly-feature/fragment chain too long (drop)"
202
203         error_cnt = self.statistics.get_err_counter(error_cnt_str)
204
205         self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
206                                     max_reassembly_length=3,
207                                     expire_walk_interval_ms=50)
208
209         p1 = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
210               IP(id=1000, src=self.src_if.remote_ip4,
211                  dst=self.dst_if.remote_ip4) /
212               UDP(sport=1234, dport=5678) /
213               Raw("X" * 1000))
214         p2 = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
215               IP(id=1001, src=self.src_if.remote_ip4,
216                  dst=self.dst_if.remote_ip4) /
217               UDP(sport=1234, dport=5678) /
218               Raw("X" * 1000))
219         frags = fragment_rfc791(p1, 200) + fragment_rfc791(p2, 500)
220
221         self.pg_enable_capture()
222         self.src_if.add_stream(frags)
223         self.pg_start()
224
225         self.dst_if.get_capture(1)
226         self.assert_error_counter_equal(error_cnt_str, error_cnt + 1)
227
228     def test_5737(self):
229         """ fragment length + ip header size > 65535 """
230         self.vapi.cli("clear errors")
231         raw = ('E\x00\x00\x88,\xf8\x1f\xfe@\x01\x98\x00\xc0\xa8\n-\xc0\xa8\n'
232                '\x01\x08\x00\xf0J\xed\xcb\xf1\xf5Test-group: IPv4.IPv4.ipv4-'
233                'message.Ethernet-Payload.IPv4-Packet.IPv4-Header.Fragment-Of'
234                'fset; Test-case: 5737')
235
236         malformed_packet = (Ether(dst=self.src_if.local_mac,
237                                   src=self.src_if.remote_mac) /
238                             IP(raw))
239         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
240              IP(id=1000, src=self.src_if.remote_ip4,
241                 dst=self.dst_if.remote_ip4) /
242              UDP(sport=1234, dport=5678) /
243              Raw("X" * 1000))
244         valid_fragments = fragment_rfc791(p, 400)
245
246         self.pg_enable_capture()
247         self.src_if.add_stream([malformed_packet] + valid_fragments)
248         self.pg_start()
249
250         self.dst_if.get_capture(1)
251         self.logger.debug(self.vapi.ppcli("show error"))
252         self.assert_packet_counter_equal("ip4-full-reassembly-feature", 1)
253         # TODO remove above, uncomment below once clearing of counters
254         # is supported
255         # self.assert_packet_counter_equal(
256         #     "/err/ip4-full-reassembly-feature/malformed packets", 1)
257
258     def test_44924(self):
259         """ compress tiny fragments """
260         packets = [(Ether(dst=self.src_if.local_mac,
261                           src=self.src_if.remote_mac) /
262                     IP(id=24339, flags="MF", frag=0, ttl=64,
263                        src=self.src_if.remote_ip4,
264                        dst=self.dst_if.remote_ip4) /
265                     ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
266                     Raw(load='Test-group: IPv4')),
267                    (Ether(dst=self.src_if.local_mac,
268                           src=self.src_if.remote_mac) /
269                     IP(id=24339, flags="MF", frag=3, ttl=64,
270                        src=self.src_if.remote_ip4,
271                        dst=self.dst_if.remote_ip4) /
272                     ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
273                     Raw(load='.IPv4.Fragmentation.vali')),
274                    (Ether(dst=self.src_if.local_mac,
275                           src=self.src_if.remote_mac) /
276                     IP(id=24339, frag=6, ttl=64,
277                        src=self.src_if.remote_ip4,
278                        dst=self.dst_if.remote_ip4) /
279                     ICMP(type="echo-request", code=0, id=0x1fe6, seq=0x2407) /
280                     Raw(load='d; Test-case: 44924'))
281                    ]
282
283         self.pg_enable_capture()
284         self.src_if.add_stream(packets)
285         self.pg_start()
286
287         self.dst_if.get_capture(1)
288
289     def test_frag_1(self):
290         """ fragment of size 1 """
291         self.vapi.cli("clear errors")
292         malformed_packets = [(Ether(dst=self.src_if.local_mac,
293                                     src=self.src_if.remote_mac) /
294                               IP(id=7, len=21, flags="MF", frag=0, ttl=64,
295                                  src=self.src_if.remote_ip4,
296                                  dst=self.dst_if.remote_ip4) /
297                               ICMP(type="echo-request")),
298                              (Ether(dst=self.src_if.local_mac,
299                                     src=self.src_if.remote_mac) /
300                               IP(id=7, len=21, frag=1, ttl=64,
301                                  src=self.src_if.remote_ip4,
302                                  dst=self.dst_if.remote_ip4) /
303                               Raw(load='\x08')),
304                              ]
305
306         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
307              IP(id=1000, src=self.src_if.remote_ip4,
308                 dst=self.dst_if.remote_ip4) /
309              UDP(sport=1234, dport=5678) /
310              Raw("X" * 1000))
311         valid_fragments = fragment_rfc791(p, 400)
312
313         self.pg_enable_capture()
314         self.src_if.add_stream(malformed_packets + valid_fragments)
315         self.pg_start()
316
317         self.dst_if.get_capture(1)
318
319         self.assert_packet_counter_equal("ip4-full-reassembly-feature", 1)
320         # TODO remove above, uncomment below once clearing of counters
321         # is supported
322         # self.assert_packet_counter_equal(
323         #     "/err/ip4-full-reassembly-feature/malformed packets", 1)
324
325     def test_random(self):
326         """ random order reassembly """
327
328         fragments = list(self.fragments_200)
329         shuffle(fragments)
330
331         self.pg_enable_capture()
332         self.src_if.add_stream(fragments)
333         self.pg_start()
334
335         packets = self.dst_if.get_capture(len(self.packet_infos))
336         self.verify_capture(packets)
337         self.src_if.assert_nothing_captured()
338
339         # run it all again to verify correctness
340         self.pg_enable_capture()
341         self.src_if.add_stream(fragments)
342         self.pg_start()
343
344         packets = self.dst_if.get_capture(len(self.packet_infos))
345         self.verify_capture(packets)
346         self.src_if.assert_nothing_captured()
347
348     def test_duplicates(self):
349         """ duplicate fragments """
350
351         fragments = [
352             x for (_, frags, _, _) in self.pkt_infos
353             for x in frags
354             for _ in range(0, min(2, len(frags)))
355         ]
356
357         self.pg_enable_capture()
358         self.src_if.add_stream(fragments)
359         self.pg_start()
360
361         packets = self.dst_if.get_capture(len(self.pkt_infos))
362         self.verify_capture(packets)
363         self.src_if.assert_nothing_captured()
364
365     def test_overlap1(self):
366         """ overlapping fragments case #1 """
367
368         fragments = []
369         for _, _, frags_300, frags_200 in self.pkt_infos:
370             if len(frags_300) == 1:
371                 fragments.extend(frags_300)
372             else:
373                 for i, j in zip(frags_200, frags_300):
374                     fragments.extend(i)
375                     fragments.extend(j)
376
377         self.pg_enable_capture()
378         self.src_if.add_stream(fragments)
379         self.pg_start()
380
381         packets = self.dst_if.get_capture(len(self.pkt_infos))
382         self.verify_capture(packets)
383         self.src_if.assert_nothing_captured()
384
385         # run it all to verify correctness
386         self.pg_enable_capture()
387         self.src_if.add_stream(fragments)
388         self.pg_start()
389
390         packets = self.dst_if.get_capture(len(self.pkt_infos))
391         self.verify_capture(packets)
392         self.src_if.assert_nothing_captured()
393
394     def test_overlap2(self):
395         """ overlapping fragments case #2 """
396
397         fragments = []
398         for _, _, frags_300, frags_200 in self.pkt_infos:
399             if len(frags_300) == 1:
400                 fragments.extend(frags_300)
401             else:
402                 # care must be taken here so that there are no fragments
403                 # received by vpp after reassembly is finished, otherwise
404                 # new reassemblies will be started and packet generator will
405                 # freak out when it detects unfreed buffers
406                 zipped = zip(frags_300, frags_200)
407                 for i, j in zipped:
408                     fragments.extend(i)
409                     fragments.extend(j)
410                 fragments.pop()
411
412         self.pg_enable_capture()
413         self.src_if.add_stream(fragments)
414         self.pg_start()
415
416         packets = self.dst_if.get_capture(len(self.pkt_infos))
417         self.verify_capture(packets)
418         self.src_if.assert_nothing_captured()
419
420         # run it all to verify correctness
421         self.pg_enable_capture()
422         self.src_if.add_stream(fragments)
423         self.pg_start()
424
425         packets = self.dst_if.get_capture(len(self.pkt_infos))
426         self.verify_capture(packets)
427         self.src_if.assert_nothing_captured()
428
429     def test_timeout_inline(self):
430         """ timeout (inline) """
431
432         dropped_packet_indexes = set(
433             index for (index, frags, _, _) in self.pkt_infos if len(frags) > 1
434         )
435
436         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
437                                     max_reassembly_length=3,
438                                     expire_walk_interval_ms=10000)
439
440         self.pg_enable_capture()
441         self.src_if.add_stream(self.fragments_400)
442         self.pg_start()
443
444         packets = self.dst_if.get_capture(
445             len(self.pkt_infos) - len(dropped_packet_indexes))
446         self.verify_capture(packets, dropped_packet_indexes)
447         self.src_if.assert_nothing_captured()
448
449     def test_timeout_cleanup(self):
450         """ timeout (cleanup) """
451
452         # whole packets + fragmented packets sans last fragment
453         fragments = [
454             x for (_, frags_400, _, _) in self.pkt_infos
455             for x in frags_400[:-1 if len(frags_400) > 1 else None]
456         ]
457
458         # last fragments for fragmented packets
459         fragments2 = [frags_400[-1]
460                       for (_, frags_400, _, _) in self.pkt_infos
461                       if len(frags_400) > 1]
462
463         dropped_packet_indexes = set(
464             index for (index, frags_400, _, _) in self.pkt_infos
465             if len(frags_400) > 1)
466
467         self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
468                                     max_reassembly_length=1000,
469                                     expire_walk_interval_ms=50)
470
471         self.pg_enable_capture()
472         self.src_if.add_stream(fragments)
473         self.pg_start()
474
475         self.sleep(.25, "wait before sending rest of fragments")
476
477         self.src_if.add_stream(fragments2)
478         self.pg_start()
479
480         packets = self.dst_if.get_capture(
481             len(self.pkt_infos) - len(dropped_packet_indexes))
482         self.verify_capture(packets, dropped_packet_indexes)
483         self.src_if.assert_nothing_captured()
484
485     def test_disabled(self):
486         """ reassembly disabled """
487
488         dropped_packet_indexes = set(
489             index for (index, frags_400, _, _) in self.pkt_infos
490             if len(frags_400) > 1)
491
492         self.vapi.ip_reassembly_set(timeout_ms=1000, max_reassemblies=0,
493                                     max_reassembly_length=3,
494                                     expire_walk_interval_ms=10000)
495
496         self.pg_enable_capture()
497         self.src_if.add_stream(self.fragments_400)
498         self.pg_start()
499
500         packets = self.dst_if.get_capture(
501             len(self.pkt_infos) - len(dropped_packet_indexes))
502         self.verify_capture(packets, dropped_packet_indexes)
503         self.src_if.assert_nothing_captured()
504
505
506 class TestIPv4MWReassembly(VppTestCase):
507     """ IPv4 Reassembly (multiple workers) """
508     worker_config = "workers %d" % worker_count
509
510     @classmethod
511     def setUpClass(cls):
512         super(TestIPv4MWReassembly, cls).setUpClass()
513
514         cls.create_pg_interfaces(range(worker_count+1))
515         cls.src_if = cls.pg0
516         cls.send_ifs = cls.pg_interfaces[:-1]
517         cls.dst_if = cls.pg_interfaces[-1]
518
519         # setup all interfaces
520         for i in cls.pg_interfaces:
521             i.admin_up()
522             i.config_ip4()
523             i.resolve_arp()
524
525         # packets sizes reduced here because we are generating packets without
526         # Ethernet headers, which are added later (diff fragments go via
527         # different interfaces)
528         cls.packet_sizes = [64-len(Ether()), 512-len(Ether()),
529                             1518-len(Ether()), 9018-len(Ether())]
530         cls.padding = " abcdefghijklmn"
531         cls.create_stream(cls.packet_sizes)
532         cls.create_fragments()
533
534     @classmethod
535     def tearDownClass(cls):
536         super(TestIPv4MWReassembly, cls).tearDownClass()
537
538     def setUp(self):
539         """ Test setup - force timeout on existing reassemblies """
540         super(TestIPv4MWReassembly, self).setUp()
541         for intf in self.send_ifs:
542             self.vapi.ip_reassembly_enable_disable(
543                 sw_if_index=intf.sw_if_index, enable_ip4=True)
544         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
545                                     max_reassembly_length=1000,
546                                     expire_walk_interval_ms=10)
547         self.sleep(.25)
548         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
549                                     max_reassembly_length=1000,
550                                     expire_walk_interval_ms=10000)
551
552     def tearDown(self):
553         super(TestIPv4MWReassembly, self).tearDown()
554
555     def show_commands_at_teardown(self):
556         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
557         self.logger.debug(self.vapi.ppcli("show buffers"))
558
559     @classmethod
560     def create_stream(cls, packet_sizes, packet_count=test_packet_count):
561         """Create input packet stream
562
563         :param list packet_sizes: Required packet sizes.
564         """
565         for i in range(0, packet_count):
566             info = cls.create_packet_info(cls.src_if, cls.src_if)
567             payload = cls.info_to_payload(info)
568             p = (IP(id=info.index, src=cls.src_if.remote_ip4,
569                     dst=cls.dst_if.remote_ip4) /
570                  UDP(sport=1234, dport=5678) /
571                  Raw(payload))
572             size = packet_sizes[(i // 2) % len(packet_sizes)]
573             cls.extend_packet(p, size, cls.padding)
574             info.data = p
575
576     @classmethod
577     def create_fragments(cls):
578         infos = cls._packet_infos
579         cls.pkt_infos = []
580         for index, info in six.iteritems(infos):
581             p = info.data
582             # cls.logger.debug(ppp("Packet:",
583             #                      p.__class__(scapy.compat.raw(p))))
584             fragments_400 = fragment_rfc791(p, 400)
585             cls.pkt_infos.append((index, fragments_400))
586         cls.fragments_400 = [
587             x for (_, frags) in cls.pkt_infos for x in frags]
588         cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, " %
589                          (len(infos), len(cls.fragments_400)))
590
591     def verify_capture(self, capture, dropped_packet_indexes=[]):
592         """Verify captured packet stream.
593
594         :param list capture: Captured packet stream.
595         """
596         info = None
597         seen = set()
598         for packet in capture:
599             try:
600                 self.logger.debug(ppp("Got packet:", packet))
601                 ip = packet[IP]
602                 udp = packet[UDP]
603                 payload_info = self.payload_to_info(packet[Raw])
604                 packet_index = payload_info.index
605                 self.assertTrue(
606                     packet_index not in dropped_packet_indexes,
607                     ppp("Packet received, but should be dropped:", packet))
608                 if packet_index in seen:
609                     raise Exception(ppp("Duplicate packet received", packet))
610                 seen.add(packet_index)
611                 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
612                 info = self._packet_infos[packet_index]
613                 self.assertTrue(info is not None)
614                 self.assertEqual(packet_index, info.index)
615                 saved_packet = info.data
616                 self.assertEqual(ip.src, saved_packet[IP].src)
617                 self.assertEqual(ip.dst, saved_packet[IP].dst)
618                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
619             except Exception:
620                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
621                 raise
622         for index in self._packet_infos:
623             self.assertTrue(index in seen or index in dropped_packet_indexes,
624                             "Packet with packet_index %d not received" % index)
625
626     def send_packets(self, packets):
627         for counter in range(worker_count):
628             if 0 == len(packets[counter]):
629                 continue
630             send_if = self.send_ifs[counter]
631             send_if.add_stream(
632                 (Ether(dst=send_if.local_mac, src=send_if.remote_mac) / x
633                  for x in packets[counter]),
634                 worker=counter)
635         self.pg_start()
636
637     def test_worker_conflict(self):
638         """ 1st and FO=0 fragments on different workers """
639
640         # in first wave we send fragments which don't start at offset 0
641         # then we send fragments with offset 0 on a different thread
642         # then the rest of packets on a random thread
643         first_packets = [[] for n in range(worker_count)]
644         second_packets = [[] for n in range(worker_count)]
645         rest_of_packets = [[] for n in range(worker_count)]
646         for (_, p) in self.pkt_infos:
647             wi = randrange(worker_count)
648             second_packets[wi].append(p[0])
649             if len(p) <= 1:
650                 continue
651             wi2 = wi
652             while wi2 == wi:
653                 wi2 = randrange(worker_count)
654             first_packets[wi2].append(p[1])
655             wi3 = randrange(worker_count)
656             rest_of_packets[wi3].extend(p[2:])
657
658         self.pg_enable_capture()
659         self.send_packets(first_packets)
660         self.send_packets(second_packets)
661         self.send_packets(rest_of_packets)
662
663         packets = self.dst_if.get_capture(len(self.pkt_infos))
664         self.verify_capture(packets)
665         for send_if in self.send_ifs:
666             send_if.assert_nothing_captured()
667
668         self.pg_enable_capture()
669         self.send_packets(first_packets)
670         self.send_packets(second_packets)
671         self.send_packets(rest_of_packets)
672
673         packets = self.dst_if.get_capture(len(self.pkt_infos))
674         self.verify_capture(packets)
675         for send_if in self.send_ifs:
676             send_if.assert_nothing_captured()
677
678
679 class TestIPv6Reassembly(VppTestCase):
680     """ IPv6 Reassembly """
681
682     @classmethod
683     def setUpClass(cls):
684         super(TestIPv6Reassembly, cls).setUpClass()
685
686         cls.create_pg_interfaces([0, 1])
687         cls.src_if = cls.pg0
688         cls.dst_if = cls.pg1
689
690         # setup all interfaces
691         for i in cls.pg_interfaces:
692             i.admin_up()
693             i.config_ip6()
694             i.resolve_ndp()
695
696         # packet sizes
697         cls.packet_sizes = [64, 512, 1518, 9018]
698         cls.padding = " abcdefghijklmn"
699         cls.create_stream(cls.packet_sizes)
700         cls.create_fragments()
701
702     @classmethod
703     def tearDownClass(cls):
704         super(TestIPv6Reassembly, cls).tearDownClass()
705
706     def setUp(self):
707         """ Test setup - force timeout on existing reassemblies """
708         super(TestIPv6Reassembly, self).setUp()
709         self.vapi.ip_reassembly_enable_disable(
710             sw_if_index=self.src_if.sw_if_index, enable_ip6=True)
711         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
712                                     max_reassembly_length=1000,
713                                     expire_walk_interval_ms=10, is_ip6=1)
714         self.sleep(.25)
715         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
716                                     max_reassembly_length=1000,
717                                     expire_walk_interval_ms=10000, is_ip6=1)
718         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
719         self.logger.debug(self.vapi.ppcli("show buffers"))
720
721     def tearDown(self):
722         super(TestIPv6Reassembly, self).tearDown()
723
724     def show_commands_at_teardown(self):
725         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
726         self.logger.debug(self.vapi.ppcli("show buffers"))
727
728     @classmethod
729     def create_stream(cls, packet_sizes, packet_count=test_packet_count):
730         """Create input packet stream for defined interface.
731
732         :param list packet_sizes: Required packet sizes.
733         """
734         for i in range(0, packet_count):
735             info = cls.create_packet_info(cls.src_if, cls.src_if)
736             payload = cls.info_to_payload(info)
737             p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) /
738                  IPv6(src=cls.src_if.remote_ip6,
739                       dst=cls.dst_if.remote_ip6) /
740                  UDP(sport=1234, dport=5678) /
741                  Raw(payload))
742             size = packet_sizes[(i // 2) % len(packet_sizes)]
743             cls.extend_packet(p, size, cls.padding)
744             info.data = p
745
746     @classmethod
747     def create_fragments(cls):
748         infos = cls._packet_infos
749         cls.pkt_infos = []
750         for index, info in six.iteritems(infos):
751             p = info.data
752             # cls.logger.debug(ppp("Packet:",
753             #                      p.__class__(scapy.compat.raw(p))))
754             fragments_400 = fragment_rfc8200(p, info.index, 400)
755             fragments_300 = fragment_rfc8200(p, info.index, 300)
756             cls.pkt_infos.append((index, fragments_400, fragments_300))
757         cls.fragments_400 = [
758             x for _, frags, _ in cls.pkt_infos for x in frags]
759         cls.fragments_300 = [
760             x for _, _, frags in cls.pkt_infos for x in frags]
761         cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, "
762                          "and %s 300-byte fragments" %
763                          (len(infos), len(cls.fragments_400),
764                              len(cls.fragments_300)))
765
766     def verify_capture(self, capture, dropped_packet_indexes=[]):
767         """Verify captured packet strea .
768
769         :param list capture: Captured packet stream.
770         """
771         info = None
772         seen = set()
773         for packet in capture:
774             try:
775                 self.logger.debug(ppp("Got packet:", packet))
776                 ip = packet[IPv6]
777                 udp = packet[UDP]
778                 payload_info = self.payload_to_info(packet[Raw])
779                 packet_index = payload_info.index
780                 self.assertTrue(
781                     packet_index not in dropped_packet_indexes,
782                     ppp("Packet received, but should be dropped:", packet))
783                 if packet_index in seen:
784                     raise Exception(ppp("Duplicate packet received", packet))
785                 seen.add(packet_index)
786                 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
787                 info = self._packet_infos[packet_index]
788                 self.assertTrue(info is not None)
789                 self.assertEqual(packet_index, info.index)
790                 saved_packet = info.data
791                 self.assertEqual(ip.src, saved_packet[IPv6].src)
792                 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
793                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
794             except Exception:
795                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
796                 raise
797         for index in self._packet_infos:
798             self.assertTrue(index in seen or index in dropped_packet_indexes,
799                             "Packet with packet_index %d not received" % index)
800
801     def test_reassembly(self):
802         """ basic reassembly """
803
804         self.pg_enable_capture()
805         self.src_if.add_stream(self.fragments_400)
806         self.pg_start()
807
808         packets = self.dst_if.get_capture(len(self.pkt_infos))
809         self.verify_capture(packets)
810         self.src_if.assert_nothing_captured()
811
812         # run it all again to verify correctness
813         self.pg_enable_capture()
814         self.src_if.add_stream(self.fragments_400)
815         self.pg_start()
816
817         packets = self.dst_if.get_capture(len(self.pkt_infos))
818         self.verify_capture(packets)
819         self.src_if.assert_nothing_captured()
820
821     def test_buffer_boundary(self):
822         """ fragment header crossing buffer boundary """
823
824         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
825              IPv6(src=self.src_if.remote_ip6,
826                   dst=self.src_if.local_ip6) /
827              IPv6ExtHdrHopByHop(
828                  options=[HBHOptUnknown(otype=0xff, optlen=0)] * 1000) /
829              IPv6ExtHdrFragment(m=1) /
830              UDP(sport=1234, dport=5678) /
831              Raw())
832         self.pg_enable_capture()
833         self.src_if.add_stream([p])
834         self.pg_start()
835         self.src_if.assert_nothing_captured()
836         self.dst_if.assert_nothing_captured()
837
838     def test_reversed(self):
839         """ reverse order reassembly """
840
841         fragments = list(self.fragments_400)
842         fragments.reverse()
843
844         self.pg_enable_capture()
845         self.src_if.add_stream(fragments)
846         self.pg_start()
847
848         packets = self.dst_if.get_capture(len(self.pkt_infos))
849         self.verify_capture(packets)
850         self.src_if.assert_nothing_captured()
851
852         # run it all again to verify correctness
853         self.pg_enable_capture()
854         self.src_if.add_stream(fragments)
855         self.pg_start()
856
857         packets = self.dst_if.get_capture(len(self.pkt_infos))
858         self.verify_capture(packets)
859         self.src_if.assert_nothing_captured()
860
861     def test_random(self):
862         """ random order reassembly """
863
864         fragments = list(self.fragments_400)
865         shuffle(fragments)
866
867         self.pg_enable_capture()
868         self.src_if.add_stream(fragments)
869         self.pg_start()
870
871         packets = self.dst_if.get_capture(len(self.pkt_infos))
872         self.verify_capture(packets)
873         self.src_if.assert_nothing_captured()
874
875         # run it all again to verify correctness
876         self.pg_enable_capture()
877         self.src_if.add_stream(fragments)
878         self.pg_start()
879
880         packets = self.dst_if.get_capture(len(self.pkt_infos))
881         self.verify_capture(packets)
882         self.src_if.assert_nothing_captured()
883
884     def test_duplicates(self):
885         """ duplicate fragments """
886
887         fragments = [
888             x for (_, frags, _) in self.pkt_infos
889             for x in frags
890             for _ in range(0, min(2, len(frags)))
891         ]
892
893         self.pg_enable_capture()
894         self.src_if.add_stream(fragments)
895         self.pg_start()
896
897         packets = self.dst_if.get_capture(len(self.pkt_infos))
898         self.verify_capture(packets)
899         self.src_if.assert_nothing_captured()
900
901     def test_long_fragment_chain(self):
902         """ long fragment chain """
903
904         error_cnt_str = \
905             "/err/ip6-full-reassembly-feature/fragment chain too long (drop)"
906
907         error_cnt = self.statistics.get_err_counter(error_cnt_str)
908
909         self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
910                                     max_reassembly_length=3,
911                                     expire_walk_interval_ms=50, is_ip6=1)
912
913         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
914              IPv6(src=self.src_if.remote_ip6,
915                   dst=self.dst_if.remote_ip6) /
916              UDP(sport=1234, dport=5678) /
917              Raw("X" * 1000))
918         frags = fragment_rfc8200(p, 1, 300) + fragment_rfc8200(p, 2, 500)
919
920         self.pg_enable_capture()
921         self.src_if.add_stream(frags)
922         self.pg_start()
923
924         self.dst_if.get_capture(1)
925         self.assert_error_counter_equal(error_cnt_str, error_cnt + 1)
926
927     def test_overlap1(self):
928         """ overlapping fragments case #1 """
929
930         fragments = []
931         for _, frags_400, frags_300 in self.pkt_infos:
932             if len(frags_300) == 1:
933                 fragments.extend(frags_400)
934             else:
935                 for i, j in zip(frags_300, frags_400):
936                     fragments.extend(i)
937                     fragments.extend(j)
938
939         dropped_packet_indexes = set(
940             index for (index, _, frags) in self.pkt_infos if len(frags) > 1
941         )
942
943         self.pg_enable_capture()
944         self.src_if.add_stream(fragments)
945         self.pg_start()
946
947         packets = self.dst_if.get_capture(
948             len(self.pkt_infos) - len(dropped_packet_indexes))
949         self.verify_capture(packets, dropped_packet_indexes)
950         self.src_if.assert_nothing_captured()
951
952     def test_overlap2(self):
953         """ overlapping fragments case #2 """
954
955         fragments = []
956         for _, frags_400, frags_300 in self.pkt_infos:
957             if len(frags_400) == 1:
958                 fragments.extend(frags_400)
959             else:
960                 # care must be taken here so that there are no fragments
961                 # received by vpp after reassembly is finished, otherwise
962                 # new reassemblies will be started and packet generator will
963                 # freak out when it detects unfreed buffers
964                 zipped = zip(frags_400, frags_300)
965                 for i, j in zipped:
966                     fragments.extend(i)
967                     fragments.extend(j)
968                 fragments.pop()
969
970         dropped_packet_indexes = set(
971             index for (index, _, frags) in self.pkt_infos if len(frags) > 1
972         )
973
974         self.pg_enable_capture()
975         self.src_if.add_stream(fragments)
976         self.pg_start()
977
978         packets = self.dst_if.get_capture(
979             len(self.pkt_infos) - len(dropped_packet_indexes))
980         self.verify_capture(packets, dropped_packet_indexes)
981         self.src_if.assert_nothing_captured()
982
983     def test_timeout_inline(self):
984         """ timeout (inline) """
985
986         dropped_packet_indexes = set(
987             index for (index, frags, _) in self.pkt_infos if len(frags) > 1
988         )
989
990         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
991                                     max_reassembly_length=3,
992                                     expire_walk_interval_ms=10000, is_ip6=1)
993
994         self.pg_enable_capture()
995         self.src_if.add_stream(self.fragments_400)
996         self.pg_start()
997
998         packets = self.dst_if.get_capture(
999             len(self.pkt_infos) - len(dropped_packet_indexes))
1000         self.verify_capture(packets, dropped_packet_indexes)
1001         pkts = self.src_if.get_capture(
1002             expected_count=len(dropped_packet_indexes))
1003         for icmp in pkts:
1004             self.assertIn(ICMPv6TimeExceeded, icmp)
1005             self.assertIn(IPv6ExtHdrFragment, icmp)
1006             self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
1007             dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
1008
1009     def test_timeout_cleanup(self):
1010         """ timeout (cleanup) """
1011
1012         # whole packets + fragmented packets sans last fragment
1013         fragments = [
1014             x for (_, frags_400, _) in self.pkt_infos
1015             for x in frags_400[:-1 if len(frags_400) > 1 else None]
1016         ]
1017
1018         # last fragments for fragmented packets
1019         fragments2 = [frags_400[-1]
1020                       for (_, frags_400, _) in self.pkt_infos
1021                       if len(frags_400) > 1]
1022
1023         dropped_packet_indexes = set(
1024             index for (index, frags_400, _) in self.pkt_infos
1025             if len(frags_400) > 1)
1026
1027         self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
1028                                     max_reassembly_length=1000,
1029                                     expire_walk_interval_ms=50)
1030
1031         self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000,
1032                                     max_reassembly_length=1000,
1033                                     expire_walk_interval_ms=50, is_ip6=1)
1034
1035         self.pg_enable_capture()
1036         self.src_if.add_stream(fragments)
1037         self.pg_start()
1038
1039         self.sleep(.25, "wait before sending rest of fragments")
1040
1041         self.src_if.add_stream(fragments2)
1042         self.pg_start()
1043
1044         packets = self.dst_if.get_capture(
1045             len(self.pkt_infos) - len(dropped_packet_indexes))
1046         self.verify_capture(packets, dropped_packet_indexes)
1047         pkts = self.src_if.get_capture(
1048             expected_count=len(dropped_packet_indexes))
1049         for icmp in pkts:
1050             self.assertIn(ICMPv6TimeExceeded, icmp)
1051             self.assertIn(IPv6ExtHdrFragment, icmp)
1052             self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes)
1053             dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id)
1054
1055     def test_disabled(self):
1056         """ reassembly disabled """
1057
1058         dropped_packet_indexes = set(
1059             index for (index, frags_400, _) in self.pkt_infos
1060             if len(frags_400) > 1)
1061
1062         self.vapi.ip_reassembly_set(timeout_ms=1000, max_reassemblies=0,
1063                                     max_reassembly_length=3,
1064                                     expire_walk_interval_ms=10000, is_ip6=1)
1065
1066         self.pg_enable_capture()
1067         self.src_if.add_stream(self.fragments_400)
1068         self.pg_start()
1069
1070         packets = self.dst_if.get_capture(
1071             len(self.pkt_infos) - len(dropped_packet_indexes))
1072         self.verify_capture(packets, dropped_packet_indexes)
1073         self.src_if.assert_nothing_captured()
1074
1075     def test_missing_upper(self):
1076         """ missing upper layer """
1077         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1078              IPv6(src=self.src_if.remote_ip6,
1079                   dst=self.src_if.local_ip6) /
1080              UDP(sport=1234, dport=5678) /
1081              Raw())
1082         self.extend_packet(p, 1000, self.padding)
1083         fragments = fragment_rfc8200(p, 1, 500)
1084         bad_fragment = p.__class__(scapy.compat.raw(fragments[1]))
1085         bad_fragment[IPv6ExtHdrFragment].nh = 59
1086         bad_fragment[IPv6ExtHdrFragment].offset = 0
1087         self.pg_enable_capture()
1088         self.src_if.add_stream([bad_fragment])
1089         self.pg_start()
1090         pkts = self.src_if.get_capture(expected_count=1)
1091         icmp = pkts[0]
1092         self.assertIn(ICMPv6ParamProblem, icmp)
1093         self.assert_equal(icmp[ICMPv6ParamProblem].code, 3, "ICMP code")
1094
1095     def test_invalid_frag_size(self):
1096         """ fragment size not a multiple of 8 """
1097         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1098              IPv6(src=self.src_if.remote_ip6,
1099                   dst=self.src_if.local_ip6) /
1100              UDP(sport=1234, dport=5678) /
1101              Raw())
1102         self.extend_packet(p, 1000, self.padding)
1103         fragments = fragment_rfc8200(p, 1, 500)
1104         bad_fragment = fragments[0]
1105         self.extend_packet(bad_fragment, len(bad_fragment) + 5)
1106         self.pg_enable_capture()
1107         self.src_if.add_stream([bad_fragment])
1108         self.pg_start()
1109         pkts = self.src_if.get_capture(expected_count=1)
1110         icmp = pkts[0]
1111         self.assertIn(ICMPv6ParamProblem, icmp)
1112         self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
1113
1114     def test_invalid_packet_size(self):
1115         """ total packet size > 65535 """
1116         p = (Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1117              IPv6(src=self.src_if.remote_ip6,
1118                   dst=self.src_if.local_ip6) /
1119              UDP(sport=1234, dport=5678) /
1120              Raw())
1121         self.extend_packet(p, 1000, self.padding)
1122         fragments = fragment_rfc8200(p, 1, 500)
1123         bad_fragment = fragments[1]
1124         bad_fragment[IPv6ExtHdrFragment].offset = 65500
1125         self.pg_enable_capture()
1126         self.src_if.add_stream([bad_fragment])
1127         self.pg_start()
1128         pkts = self.src_if.get_capture(expected_count=1)
1129         icmp = pkts[0]
1130         self.assertIn(ICMPv6ParamProblem, icmp)
1131         self.assert_equal(icmp[ICMPv6ParamProblem].code, 0, "ICMP code")
1132
1133
1134 class TestIPv6MWReassembly(VppTestCase):
1135     """ IPv6 Reassembly (multiple workers) """
1136     worker_config = "workers %d" % worker_count
1137
1138     @classmethod
1139     def setUpClass(cls):
1140         super(TestIPv6MWReassembly, cls).setUpClass()
1141
1142         cls.create_pg_interfaces(range(worker_count+1))
1143         cls.src_if = cls.pg0
1144         cls.send_ifs = cls.pg_interfaces[:-1]
1145         cls.dst_if = cls.pg_interfaces[-1]
1146
1147         # setup all interfaces
1148         for i in cls.pg_interfaces:
1149             i.admin_up()
1150             i.config_ip6()
1151             i.resolve_ndp()
1152
1153         # packets sizes reduced here because we are generating packets without
1154         # Ethernet headers, which are added later (diff fragments go via
1155         # different interfaces)
1156         cls.packet_sizes = [64-len(Ether()), 512-len(Ether()),
1157                             1518-len(Ether()), 9018-len(Ether())]
1158         cls.padding = " abcdefghijklmn"
1159         cls.create_stream(cls.packet_sizes)
1160         cls.create_fragments()
1161
1162     @classmethod
1163     def tearDownClass(cls):
1164         super(TestIPv6MWReassembly, cls).tearDownClass()
1165
1166     def setUp(self):
1167         """ Test setup - force timeout on existing reassemblies """
1168         super(TestIPv6MWReassembly, self).setUp()
1169         for intf in self.send_ifs:
1170             self.vapi.ip_reassembly_enable_disable(
1171                 sw_if_index=intf.sw_if_index, enable_ip6=True)
1172         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1173                                     max_reassembly_length=1000,
1174                                     expire_walk_interval_ms=10, is_ip6=1)
1175         self.sleep(.25)
1176         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1177                                     max_reassembly_length=1000,
1178                                     expire_walk_interval_ms=1000, is_ip6=1)
1179
1180     def tearDown(self):
1181         super(TestIPv6MWReassembly, self).tearDown()
1182
1183     def show_commands_at_teardown(self):
1184         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1185         self.logger.debug(self.vapi.ppcli("show buffers"))
1186
1187     @classmethod
1188     def create_stream(cls, packet_sizes, packet_count=test_packet_count):
1189         """Create input packet stream
1190
1191         :param list packet_sizes: Required packet sizes.
1192         """
1193         for i in range(0, packet_count):
1194             info = cls.create_packet_info(cls.src_if, cls.src_if)
1195             payload = cls.info_to_payload(info)
1196             p = (IPv6(src=cls.src_if.remote_ip6,
1197                       dst=cls.dst_if.remote_ip6) /
1198                  UDP(sport=1234, dport=5678) /
1199                  Raw(payload))
1200             size = packet_sizes[(i // 2) % len(packet_sizes)]
1201             cls.extend_packet(p, size, cls.padding)
1202             info.data = p
1203
1204     @classmethod
1205     def create_fragments(cls):
1206         infos = cls._packet_infos
1207         cls.pkt_infos = []
1208         for index, info in six.iteritems(infos):
1209             p = info.data
1210             # cls.logger.debug(ppp("Packet:",
1211             #                      p.__class__(scapy.compat.raw(p))))
1212             fragments_400 = fragment_rfc8200(p, index, 400)
1213             cls.pkt_infos.append((index, fragments_400))
1214         cls.fragments_400 = [
1215             x for (_, frags) in cls.pkt_infos for x in frags]
1216         cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, " %
1217                          (len(infos), len(cls.fragments_400)))
1218
1219     def verify_capture(self, capture, dropped_packet_indexes=[]):
1220         """Verify captured packet strea .
1221
1222         :param list capture: Captured packet stream.
1223         """
1224         info = None
1225         seen = set()
1226         for packet in capture:
1227             try:
1228                 self.logger.debug(ppp("Got packet:", packet))
1229                 ip = packet[IPv6]
1230                 udp = packet[UDP]
1231                 payload_info = self.payload_to_info(packet[Raw])
1232                 packet_index = payload_info.index
1233                 self.assertTrue(
1234                     packet_index not in dropped_packet_indexes,
1235                     ppp("Packet received, but should be dropped:", packet))
1236                 if packet_index in seen:
1237                     raise Exception(ppp("Duplicate packet received", packet))
1238                 seen.add(packet_index)
1239                 self.assertEqual(payload_info.dst, self.src_if.sw_if_index)
1240                 info = self._packet_infos[packet_index]
1241                 self.assertTrue(info is not None)
1242                 self.assertEqual(packet_index, info.index)
1243                 saved_packet = info.data
1244                 self.assertEqual(ip.src, saved_packet[IPv6].src)
1245                 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
1246                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1247             except Exception:
1248                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1249                 raise
1250         for index in self._packet_infos:
1251             self.assertTrue(index in seen or index in dropped_packet_indexes,
1252                             "Packet with packet_index %d not received" % index)
1253
1254     def send_packets(self, packets):
1255         for counter in range(worker_count):
1256             if 0 == len(packets[counter]):
1257                 continue
1258             send_if = self.send_ifs[counter]
1259             send_if.add_stream(
1260                 (Ether(dst=send_if.local_mac, src=send_if.remote_mac) / x
1261                  for x in packets[counter]),
1262                 worker=counter)
1263         self.pg_start()
1264
1265     def test_worker_conflict(self):
1266         """ 1st and FO=0 fragments on different workers """
1267
1268         # in first wave we send fragments which don't start at offset 0
1269         # then we send fragments with offset 0 on a different thread
1270         # then the rest of packets on a random thread
1271         first_packets = [[] for n in range(worker_count)]
1272         second_packets = [[] for n in range(worker_count)]
1273         rest_of_packets = [[] for n in range(worker_count)]
1274         for (_, p) in self.pkt_infos:
1275             wi = randrange(worker_count)
1276             second_packets[wi].append(p[0])
1277             if len(p) <= 1:
1278                 continue
1279             wi2 = wi
1280             while wi2 == wi:
1281                 wi2 = randrange(worker_count)
1282             first_packets[wi2].append(p[1])
1283             wi3 = randrange(worker_count)
1284             rest_of_packets[wi3].extend(p[2:])
1285
1286         self.pg_enable_capture()
1287         self.send_packets(first_packets)
1288         self.send_packets(second_packets)
1289         self.send_packets(rest_of_packets)
1290
1291         packets = self.dst_if.get_capture(len(self.pkt_infos))
1292         self.verify_capture(packets)
1293         for send_if in self.send_ifs:
1294             send_if.assert_nothing_captured()
1295
1296         self.pg_enable_capture()
1297         self.send_packets(first_packets)
1298         self.send_packets(second_packets)
1299         self.send_packets(rest_of_packets)
1300
1301         packets = self.dst_if.get_capture(len(self.pkt_infos))
1302         self.verify_capture(packets)
1303         for send_if in self.send_ifs:
1304             send_if.assert_nothing_captured()
1305
1306
1307 class TestIPv4ReassemblyLocalNode(VppTestCase):
1308     """ IPv4 Reassembly for packets coming to ip4-local node """
1309
1310     @classmethod
1311     def setUpClass(cls):
1312         super(TestIPv4ReassemblyLocalNode, cls).setUpClass()
1313
1314         cls.create_pg_interfaces([0])
1315         cls.src_dst_if = cls.pg0
1316
1317         # setup all interfaces
1318         for i in cls.pg_interfaces:
1319             i.admin_up()
1320             i.config_ip4()
1321             i.resolve_arp()
1322
1323         cls.padding = " abcdefghijklmn"
1324         cls.create_stream()
1325         cls.create_fragments()
1326
1327     @classmethod
1328     def tearDownClass(cls):
1329         super(TestIPv4ReassemblyLocalNode, cls).tearDownClass()
1330
1331     def setUp(self):
1332         """ Test setup - force timeout on existing reassemblies """
1333         super(TestIPv4ReassemblyLocalNode, self).setUp()
1334         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1335                                     max_reassembly_length=1000,
1336                                     expire_walk_interval_ms=10)
1337         self.sleep(.25)
1338         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1339                                     max_reassembly_length=1000,
1340                                     expire_walk_interval_ms=10000)
1341
1342     def tearDown(self):
1343         super(TestIPv4ReassemblyLocalNode, self).tearDown()
1344
1345     def show_commands_at_teardown(self):
1346         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
1347         self.logger.debug(self.vapi.ppcli("show buffers"))
1348
1349     @classmethod
1350     def create_stream(cls, packet_count=test_packet_count):
1351         """Create input packet stream for defined interface.
1352
1353         :param list packet_sizes: Required packet sizes.
1354         """
1355         for i in range(0, packet_count):
1356             info = cls.create_packet_info(cls.src_dst_if, cls.src_dst_if)
1357             payload = cls.info_to_payload(info)
1358             p = (Ether(dst=cls.src_dst_if.local_mac,
1359                        src=cls.src_dst_if.remote_mac) /
1360                  IP(id=info.index, src=cls.src_dst_if.remote_ip4,
1361                     dst=cls.src_dst_if.local_ip4) /
1362                  ICMP(type='echo-request', id=1234) /
1363                  Raw(payload))
1364             cls.extend_packet(p, 1518, cls.padding)
1365             info.data = p
1366
1367     @classmethod
1368     def create_fragments(cls):
1369         infos = cls._packet_infos
1370         cls.pkt_infos = []
1371         for index, info in six.iteritems(infos):
1372             p = info.data
1373             # cls.logger.debug(ppp("Packet:",
1374             #                      p.__class__(scapy.compat.raw(p))))
1375             fragments_300 = fragment_rfc791(p, 300)
1376             cls.pkt_infos.append((index, fragments_300))
1377         cls.fragments_300 = [x for (_, frags) in cls.pkt_infos for x in frags]
1378         cls.logger.debug("Fragmented %s packets into %s 300-byte fragments" %
1379                          (len(infos), len(cls.fragments_300)))
1380
1381     def verify_capture(self, capture):
1382         """Verify captured packet stream.
1383
1384         :param list capture: Captured packet stream.
1385         """
1386         info = None
1387         seen = set()
1388         for packet in capture:
1389             try:
1390                 self.logger.debug(ppp("Got packet:", packet))
1391                 ip = packet[IP]
1392                 icmp = packet[ICMP]
1393                 payload_info = self.payload_to_info(packet[Raw])
1394                 packet_index = payload_info.index
1395                 if packet_index in seen:
1396                     raise Exception(ppp("Duplicate packet received", packet))
1397                 seen.add(packet_index)
1398                 self.assertEqual(payload_info.dst, self.src_dst_if.sw_if_index)
1399                 info = self._packet_infos[packet_index]
1400                 self.assertIsNotNone(info)
1401                 self.assertEqual(packet_index, info.index)
1402                 saved_packet = info.data
1403                 self.assertEqual(ip.src, saved_packet[IP].dst)
1404                 self.assertEqual(ip.dst, saved_packet[IP].src)
1405                 self.assertEqual(icmp.type, 0)  # echo reply
1406                 self.assertEqual(icmp.id, saved_packet[ICMP].id)
1407                 self.assertEqual(icmp.payload, saved_packet[ICMP].payload)
1408             except Exception:
1409                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1410                 raise
1411         for index in self._packet_infos:
1412             self.assertIn(index, seen,
1413                           "Packet with packet_index %d not received" % index)
1414
1415     def test_reassembly(self):
1416         """ basic reassembly """
1417
1418         self.pg_enable_capture()
1419         self.src_dst_if.add_stream(self.fragments_300)
1420         self.pg_start()
1421
1422         packets = self.src_dst_if.get_capture(len(self.pkt_infos))
1423         self.verify_capture(packets)
1424
1425         # run it all again to verify correctness
1426         self.pg_enable_capture()
1427         self.src_dst_if.add_stream(self.fragments_300)
1428         self.pg_start()
1429
1430         packets = self.src_dst_if.get_capture(len(self.pkt_infos))
1431         self.verify_capture(packets)
1432
1433
1434 class TestFIFReassembly(VppTestCase):
1435     """ Fragments in fragments reassembly """
1436
1437     @classmethod
1438     def setUpClass(cls):
1439         super(TestFIFReassembly, cls).setUpClass()
1440
1441         cls.create_pg_interfaces([0, 1])
1442         cls.src_if = cls.pg0
1443         cls.dst_if = cls.pg1
1444         for i in cls.pg_interfaces:
1445             i.admin_up()
1446             i.config_ip4()
1447             i.resolve_arp()
1448             i.config_ip6()
1449             i.resolve_ndp()
1450
1451         cls.packet_sizes = [64, 512, 1518, 9018]
1452         cls.padding = " abcdefghijklmn"
1453
1454     @classmethod
1455     def tearDownClass(cls):
1456         super(TestFIFReassembly, cls).tearDownClass()
1457
1458     def setUp(self):
1459         """ Test setup - force timeout on existing reassemblies """
1460         super(TestFIFReassembly, self).setUp()
1461         self.vapi.ip_reassembly_enable_disable(
1462             sw_if_index=self.src_if.sw_if_index, enable_ip4=True,
1463             enable_ip6=True)
1464         self.vapi.ip_reassembly_enable_disable(
1465             sw_if_index=self.dst_if.sw_if_index, enable_ip4=True,
1466             enable_ip6=True)
1467         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1468                                     max_reassembly_length=1000,
1469                                     expire_walk_interval_ms=10)
1470         self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000,
1471                                     max_reassembly_length=1000,
1472                                     expire_walk_interval_ms=10, is_ip6=1)
1473         self.sleep(.25)
1474         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1475                                     max_reassembly_length=1000,
1476                                     expire_walk_interval_ms=10000)
1477         self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000,
1478                                     max_reassembly_length=1000,
1479                                     expire_walk_interval_ms=10000, is_ip6=1)
1480
1481     def tearDown(self):
1482         super(TestFIFReassembly, self).tearDown()
1483
1484     def show_commands_at_teardown(self):
1485         self.logger.debug(self.vapi.ppcli("show ip4-full-reassembly details"))
1486         self.logger.debug(self.vapi.ppcli("show ip6-full-reassembly details"))
1487         self.logger.debug(self.vapi.ppcli("show buffers"))
1488
1489     def verify_capture(self, capture, ip_class, dropped_packet_indexes=[]):
1490         """Verify captured packet stream.
1491
1492         :param list capture: Captured packet stream.
1493         """
1494         info = None
1495         seen = set()
1496         for packet in capture:
1497             try:
1498                 self.logger.debug(ppp("Got packet:", packet))
1499                 ip = packet[ip_class]
1500                 udp = packet[UDP]
1501                 payload_info = self.payload_to_info(packet[Raw])
1502                 packet_index = payload_info.index
1503                 self.assertTrue(
1504                     packet_index not in dropped_packet_indexes,
1505                     ppp("Packet received, but should be dropped:", packet))
1506                 if packet_index in seen:
1507                     raise Exception(ppp("Duplicate packet received", packet))
1508                 seen.add(packet_index)
1509                 self.assertEqual(payload_info.dst, self.dst_if.sw_if_index)
1510                 info = self._packet_infos[packet_index]
1511                 self.assertTrue(info is not None)
1512                 self.assertEqual(packet_index, info.index)
1513                 saved_packet = info.data
1514                 self.assertEqual(ip.src, saved_packet[ip_class].src)
1515                 self.assertEqual(ip.dst, saved_packet[ip_class].dst)
1516                 self.assertEqual(udp.payload, saved_packet[UDP].payload)
1517             except Exception:
1518                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
1519                 raise
1520         for index in self._packet_infos:
1521             self.assertTrue(index in seen or index in dropped_packet_indexes,
1522                             "Packet with packet_index %d not received" % index)
1523
1524     def test_fif4(self):
1525         """ Fragments in fragments (4o4) """
1526
1527         # TODO this should be ideally in setUpClass, but then we hit a bug
1528         # with VppIpRoute incorrectly reporting it's present when it's not
1529         # so we need to manually remove the vpp config, thus we cannot have
1530         # it shared for multiple test cases
1531         self.tun_ip4 = "1.1.1.2"
1532
1533         self.gre4 = VppGreInterface(self, self.src_if.local_ip4, self.tun_ip4)
1534         self.gre4.add_vpp_config()
1535         self.gre4.admin_up()
1536         self.gre4.config_ip4()
1537
1538         self.vapi.ip_reassembly_enable_disable(
1539             sw_if_index=self.gre4.sw_if_index, enable_ip4=True)
1540
1541         self.route4 = VppIpRoute(self, self.tun_ip4, 32,
1542                                  [VppRoutePath(self.src_if.remote_ip4,
1543                                                self.src_if.sw_if_index)])
1544         self.route4.add_vpp_config()
1545
1546         self.reset_packet_infos()
1547         for i in range(test_packet_count):
1548             info = self.create_packet_info(self.src_if, self.dst_if)
1549             payload = self.info_to_payload(info)
1550             # Ethernet header here is only for size calculation, thus it
1551             # doesn't matter how it's initialized. This is to ensure that
1552             # reassembled packet is not > 9000 bytes, so that it's not dropped
1553             p = (Ether() /
1554                  IP(id=i, src=self.src_if.remote_ip4,
1555                     dst=self.dst_if.remote_ip4) /
1556                  UDP(sport=1234, dport=5678) /
1557                  Raw(payload))
1558             size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
1559             self.extend_packet(p, size, self.padding)
1560             info.data = p[IP]  # use only IP part, without ethernet header
1561
1562         fragments = [x for _, p in six.iteritems(self._packet_infos)
1563                      for x in fragment_rfc791(p.data, 400)]
1564
1565         encapped_fragments = \
1566             [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1567              IP(src=self.tun_ip4, dst=self.src_if.local_ip4) /
1568                 GRE() /
1569                 p
1570                 for p in fragments]
1571
1572         fragmented_encapped_fragments = \
1573             [x for p in encapped_fragments
1574              for x in fragment_rfc791(p, 200)]
1575
1576         self.src_if.add_stream(fragmented_encapped_fragments)
1577
1578         self.pg_enable_capture(self.pg_interfaces)
1579         self.pg_start()
1580
1581         self.src_if.assert_nothing_captured()
1582         packets = self.dst_if.get_capture(len(self._packet_infos))
1583         self.verify_capture(packets, IP)
1584
1585         # TODO remove gre vpp config by hand until VppIpRoute gets fixed
1586         # so that it's query_vpp_config() works as it should
1587         self.gre4.remove_vpp_config()
1588         self.logger.debug(self.vapi.ppcli("show interface"))
1589
1590     def test_fif6(self):
1591         """ Fragments in fragments (6o6) """
1592         # TODO this should be ideally in setUpClass, but then we hit a bug
1593         # with VppIpRoute incorrectly reporting it's present when it's not
1594         # so we need to manually remove the vpp config, thus we cannot have
1595         # it shared for multiple test cases
1596         self.tun_ip6 = "1002::1"
1597
1598         self.gre6 = VppGreInterface(self, self.src_if.local_ip6, self.tun_ip6)
1599         self.gre6.add_vpp_config()
1600         self.gre6.admin_up()
1601         self.gre6.config_ip6()
1602
1603         self.vapi.ip_reassembly_enable_disable(
1604             sw_if_index=self.gre6.sw_if_index, enable_ip6=True)
1605
1606         self.route6 = VppIpRoute(self, self.tun_ip6, 128,
1607                                  [VppRoutePath(
1608                                      self.src_if.remote_ip6,
1609                                      self.src_if.sw_if_index)])
1610         self.route6.add_vpp_config()
1611
1612         self.reset_packet_infos()
1613         for i in range(test_packet_count):
1614             info = self.create_packet_info(self.src_if, self.dst_if)
1615             payload = self.info_to_payload(info)
1616             # Ethernet header here is only for size calculation, thus it
1617             # doesn't matter how it's initialized. This is to ensure that
1618             # reassembled packet is not > 9000 bytes, so that it's not dropped
1619             p = (Ether() /
1620                  IPv6(src=self.src_if.remote_ip6, dst=self.dst_if.remote_ip6) /
1621                  UDP(sport=1234, dport=5678) /
1622                  Raw(payload))
1623             size = self.packet_sizes[(i // 2) % len(self.packet_sizes)]
1624             self.extend_packet(p, size, self.padding)
1625             info.data = p[IPv6]  # use only IPv6 part, without ethernet header
1626
1627         fragments = [x for _, i in six.iteritems(self._packet_infos)
1628                      for x in fragment_rfc8200(
1629                          i.data, i.index, 400)]
1630
1631         encapped_fragments = \
1632             [Ether(dst=self.src_if.local_mac, src=self.src_if.remote_mac) /
1633              IPv6(src=self.tun_ip6, dst=self.src_if.local_ip6) /
1634                 GRE() /
1635                 p
1636                 for p in fragments]
1637
1638         fragmented_encapped_fragments = \
1639             [x for p in encapped_fragments for x in (
1640                 fragment_rfc8200(
1641                     p,
1642                     2 * len(self._packet_infos) + p[IPv6ExtHdrFragment].id,
1643                     200)
1644                 if IPv6ExtHdrFragment in p else [p]
1645             )
1646             ]
1647
1648         self.src_if.add_stream(fragmented_encapped_fragments)
1649
1650         self.pg_enable_capture(self.pg_interfaces)
1651         self.pg_start()
1652
1653         self.src_if.assert_nothing_captured()
1654         packets = self.dst_if.get_capture(len(self._packet_infos))
1655         self.verify_capture(packets, IPv6)
1656
1657         # TODO remove gre vpp config by hand until VppIpRoute gets fixed
1658         # so that it's query_vpp_config() works as it should
1659         self.gre6.remove_vpp_config()
1660
1661
1662 if __name__ == '__main__':
1663     unittest.main(testRunner=VppTestRunner)