ip: Fragmentation fixes
[vpp.git] / test / test_ipip.py
1 #!/usr/bin/env python
2 """IP{4,6} over IP{v,6} tunnel functional tests"""
3
4 import unittest
5 from scapy.layers.inet6 import IPv6, Ether, IP, UDP, IPv6ExtHdrFragment, Raw
6 from scapy.all import fragment, fragment6, RandShort, defragment6
7 from framework import VppTestCase, VppTestRunner
8 from vpp_ip import DpoProto
9 from vpp_ip_route import VppIpRoute, VppRoutePath, VppIpTable, FibPathProto
10 from socket import AF_INET, AF_INET6, inet_pton
11 from util import reassemble4
12
13 """ Testipip is a subclass of  VPPTestCase classes.
14
15 IPIP tests.
16
17 """
18
19
20 def ipip_add_tunnel(test, src, dst, table_id=0, tc_tos=0xff):
21     """ Add a IPIP tunnel """
22     return test.vapi.ipip_add_tunnel(
23         tunnel={
24             'src': src,
25             'dst': dst,
26             'table_id': table_id,
27             'instance': 0xffffffff,
28             'tc_tos': tc_tos
29         }
30     )
31
32
33 class TestIPIP(VppTestCase):
34     """ IPIP Test Case """
35
36     @classmethod
37     def setUpClass(cls):
38         super(TestIPIP, cls).setUpClass()
39         cls.create_pg_interfaces(range(2))
40         cls.interfaces = list(cls.pg_interfaces)
41
42     @classmethod
43     def tearDownClass(cls):
44         super(TestIPIP, cls).tearDownClass()
45
46     def setUp(self):
47         super(TestIPIP, self).setUp()
48         for i in self.interfaces:
49             i.admin_up()
50             i.config_ip4()
51             i.config_ip6()
52             i.disable_ipv6_ra()
53             i.resolve_arp()
54             i.resolve_ndp()
55
56     def tearDown(self):
57         super(TestIPIP, self).tearDown()
58         if not self.vpp_dead:
59             for i in self.pg_interfaces:
60                 i.unconfig_ip4()
61                 i.unconfig_ip6()
62                 i.admin_down()
63
64     def validate(self, rx, expected):
65         self.assertEqual(rx, expected.__class__(expected))
66
67     def generate_ip4_frags(self, payload_length, fragment_size):
68         p_ether = Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac)
69         p_payload = UDP(sport=1234, dport=1234) / self.payload(payload_length)
70         p_ip4 = IP(src="1.2.3.4", dst=self.pg0.remote_ip4)
71         outer_ip4 = (p_ether / IP(src=self.pg1.remote_ip4,
72                                   id=RandShort(),
73                                   dst=self.pg0.local_ip4) / p_ip4 / p_payload)
74         frags = fragment(outer_ip4, fragment_size)
75         p4_reply = (p_ip4 / p_payload)
76         p4_reply.ttl -= 1
77         return frags, p4_reply
78
79     def test_ipip4(self):
80         """ ip{v4,v6} over ip4 test """
81         p_ether = Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac)
82         p_ip6 = IPv6(src="1::1", dst="DEAD::1", nh='UDP', tc=42)
83         p_ip4 = IP(src="1.2.3.4", dst="130.67.0.1", tos=42)
84         p_payload = UDP(sport=1234, dport=1234) / Raw(b'X' * 100)
85
86         # IPv4 transport
87         rv = ipip_add_tunnel(self,
88                              self.pg0.local_ip4,
89                              self.pg1.remote_ip4,
90                              tc_tos=0xFF)
91         sw_if_index = rv.sw_if_index
92
93         # Set interface up and enable IP on it
94         self.vapi.sw_interface_set_flags(sw_if_index, 1)
95         self.vapi.sw_interface_set_unnumbered(
96             sw_if_index=self.pg0.sw_if_index,
97             unnumbered_sw_if_index=sw_if_index)
98
99         # Add IPv4 and IPv6 routes via tunnel interface
100         ip4_via_tunnel = VppIpRoute(
101             self, "130.67.0.0", 16,
102             [VppRoutePath("0.0.0.0",
103                           sw_if_index,
104                           proto=FibPathProto.FIB_PATH_NH_PROTO_IP4)])
105         ip4_via_tunnel.add_vpp_config()
106
107         ip6_via_tunnel = VppIpRoute(
108             self, "dead::", 16,
109             [VppRoutePath("::",
110                           sw_if_index,
111                           proto=FibPathProto.FIB_PATH_NH_PROTO_IP6)])
112         ip6_via_tunnel.add_vpp_config()
113
114         # IPv6 in to IPv4 tunnel
115         p6 = (p_ether / p_ip6 / p_payload)
116         p_inner_ip6 = p_ip6
117         p_inner_ip6.hlim -= 1
118         p6_reply = (IP(src=self.pg0.local_ip4, dst=self.pg1.remote_ip4,
119                        proto='ipv6', id=0, tos=42) / p_inner_ip6 / p_payload)
120         p6_reply.ttl -= 1
121         rx = self.send_and_expect(self.pg0, p6 * 10, self.pg1)
122         for p in rx:
123             self.validate(p[1], p6_reply)
124             self.assert_packet_checksums_valid(p)
125
126         # IPv4 in to IPv4 tunnel
127         p4 = (p_ether / p_ip4 / p_payload)
128         p_ip4_inner = p_ip4
129         p_ip4_inner.ttl -= 1
130         p4_reply = (IP(src=self.pg0.local_ip4, dst=self.pg1.remote_ip4,
131                        tos=42) /
132                     p_ip4_inner / p_payload)
133         p4_reply.ttl -= 1
134         p4_reply.id = 0
135         rx = self.send_and_expect(self.pg0, p4 * 10, self.pg1)
136         for p in rx:
137             self.validate(p[1], p4_reply)
138             self.assert_packet_checksums_valid(p)
139
140         # Decapsulation
141         p_ether = Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac)
142
143         # IPv4 tunnel to IPv4
144         p_ip4 = IP(src="1.2.3.4", dst=self.pg0.remote_ip4)
145         p4 = (p_ether / IP(src=self.pg1.remote_ip4,
146                            dst=self.pg0.local_ip4) / p_ip4 / p_payload)
147         p4_reply = (p_ip4 / p_payload)
148         p4_reply.ttl -= 1
149         rx = self.send_and_expect(self.pg1, p4 * 10, self.pg0)
150         for p in rx:
151             self.validate(p[1], p4_reply)
152             self.assert_packet_checksums_valid(p)
153
154         err = self.statistics.get_err_counter(
155             '/err/ipip4-input/packets decapsulated')
156         self.assertEqual(err, 10)
157
158         # IPv4 tunnel to IPv6
159         p_ip6 = IPv6(src="1:2:3::4", dst=self.pg0.remote_ip6)
160         p6 = (p_ether / IP(src=self.pg1.remote_ip4,
161                            dst=self.pg0.local_ip4) / p_ip6 / p_payload)
162         p6_reply = (p_ip6 / p_payload)
163         p6_reply.hlim = 63
164         rx = self.send_and_expect(self.pg1, p6 * 10, self.pg0)
165         for p in rx:
166             self.validate(p[1], p6_reply)
167             self.assert_packet_checksums_valid(p)
168
169         err = self.statistics.get_err_counter(
170             '/err/ipip4-input/packets decapsulated')
171         self.assertEqual(err, 20)
172
173         #
174         # Fragmentation / Reassembly and Re-fragmentation
175         #
176         rv = self.vapi.ip_reassembly_enable_disable(
177             sw_if_index=self.pg1.sw_if_index,
178             enable_ip4=1)
179
180         self.vapi.ip_reassembly_set(timeout_ms=1000, max_reassemblies=1000,
181                                     max_reassembly_length=1000,
182                                     expire_walk_interval_ms=10000,
183                                     is_ip6=0)
184
185         # Send lots of fragments, verify reassembled packet
186         frags, p4_reply = self.generate_ip4_frags(3131, 1400)
187         f = []
188         for i in range(0, 1000):
189             f.extend(frags)
190         self.pg1.add_stream(f)
191         self.pg_enable_capture()
192         self.pg_start()
193         rx = self.pg0.get_capture(1000)
194
195         for p in rx:
196             self.validate(p[1], p4_reply)
197
198         err = self.statistics.get_err_counter(
199             '/err/ipip4-input/packets decapsulated')
200         self.assertEqual(err, 1020)
201
202         f = []
203         r = []
204         for i in range(1, 90):
205             frags, p4_reply = self.generate_ip4_frags(i * 100, 1000)
206             f.extend(frags)
207             r.extend(p4_reply)
208         self.pg_enable_capture()
209         self.pg1.add_stream(f)
210         self.pg_start()
211         rx = self.pg0.get_capture(89)
212         i = 0
213         for p in rx:
214             self.validate(p[1], r[i])
215             i += 1
216
217         # Now try with re-fragmentation
218         #
219         # Send fragments to tunnel head-end, for the tunnel head end
220         # to reassemble and then refragment
221         #
222         self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [576, 0, 0, 0])
223         frags, p4_reply = self.generate_ip4_frags(3123, 1200)
224         self.pg_enable_capture()
225         self.pg1.add_stream(frags)
226         self.pg_start()
227         rx = self.pg0.get_capture(6)
228         reass_pkt = reassemble4(rx)
229         p4_reply.id = 256
230         self.validate(reass_pkt, p4_reply)
231
232         self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [1600, 0, 0, 0])
233         frags, p4_reply = self.generate_ip4_frags(3123, 1200)
234         self.pg_enable_capture()
235         self.pg1.add_stream(frags)
236         self.pg_start()
237         rx = self.pg0.get_capture(2)
238         reass_pkt = reassemble4(rx)
239         p4_reply.id = 512
240         self.validate(reass_pkt, p4_reply)
241
242         # send large packets through the tunnel, expect them to be fragmented
243         self.vapi.sw_interface_set_mtu(sw_if_index, [600, 0, 0, 0])
244
245         p4 = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
246               IP(src="1.2.3.4", dst="130.67.0.1", tos=42) /
247               UDP(sport=1234, dport=1234) / Raw(b'Q' * 1000))
248         rx = self.send_and_expect(self.pg0, p4 * 15, self.pg1, 30)
249         inners = []
250         for p in rx:
251             inners.append(p[IP].payload)
252         reass_pkt = reassemble4(inners)
253         for p in reass_pkt:
254             self.assert_packet_checksums_valid(p)
255             self.assertEqual(p[IP].ttl, 63)
256
257     def test_ipip_create(self):
258         """ ipip create / delete interface test """
259         rv = ipip_add_tunnel(self, '1.2.3.4', '2.3.4.5')
260         sw_if_index = rv.sw_if_index
261         self.vapi.ipip_del_tunnel(sw_if_index)
262
263     def test_ipip_vrf_create(self):
264         """ ipip create / delete interface VRF test """
265
266         t = VppIpTable(self, 20)
267         t.add_vpp_config()
268         rv = ipip_add_tunnel(self, '1.2.3.4', '2.3.4.5', table_id=20)
269         sw_if_index = rv.sw_if_index
270         self.vapi.ipip_del_tunnel(sw_if_index)
271
272     def payload(self, len):
273         return 'x' * len
274
275
276 class TestIPIP6(VppTestCase):
277     """ IPIP6 Test Case """
278
279     @classmethod
280     def setUpClass(cls):
281         super(TestIPIP6, cls).setUpClass()
282         cls.create_pg_interfaces(range(2))
283         cls.interfaces = list(cls.pg_interfaces)
284
285     @classmethod
286     def tearDownClass(cls):
287         super(TestIPIP6, cls).tearDownClass()
288
289     def setUp(self):
290         super(TestIPIP6, self).setUp()
291         for i in self.interfaces:
292             i.admin_up()
293             i.config_ip4()
294             i.config_ip6()
295             i.disable_ipv6_ra()
296             i.resolve_arp()
297             i.resolve_ndp()
298         self.setup_tunnel()
299
300     def tearDown(self):
301         if not self.vpp_dead:
302             self.destroy_tunnel()
303             for i in self.pg_interfaces:
304                 i.unconfig_ip4()
305                 i.unconfig_ip6()
306                 i.admin_down()
307             super(TestIPIP6, self).tearDown()
308
309     def setup_tunnel(self):
310         # IPv6 transport
311         rv = ipip_add_tunnel(self,
312                              self.pg0.local_ip6,
313                              self.pg1.remote_ip6,
314                              tc_tos=255)
315
316         sw_if_index = rv.sw_if_index
317         self.tunnel_if_index = sw_if_index
318         self.vapi.sw_interface_set_flags(sw_if_index, 1)
319         self.vapi.sw_interface_set_unnumbered(
320             sw_if_index=self.pg0.sw_if_index,
321             unnumbered_sw_if_index=sw_if_index)
322
323         # Add IPv4 and IPv6 routes via tunnel interface
324         ip4_via_tunnel = VppIpRoute(
325             self, "130.67.0.0", 16,
326             [VppRoutePath("0.0.0.0",
327                           sw_if_index,
328                           proto=FibPathProto.FIB_PATH_NH_PROTO_IP4)])
329         ip4_via_tunnel.add_vpp_config()
330
331         ip6_via_tunnel = VppIpRoute(
332             self, "dead::", 16,
333             [VppRoutePath("::",
334                           sw_if_index,
335                           proto=FibPathProto.FIB_PATH_NH_PROTO_IP6)])
336         ip6_via_tunnel.add_vpp_config()
337
338         self.tunnel_ip6_via_tunnel = ip6_via_tunnel
339         self.tunnel_ip4_via_tunnel = ip4_via_tunnel
340
341     def destroy_tunnel(self):
342         # IPv6 transport
343         self.tunnel_ip4_via_tunnel.remove_vpp_config()
344         self.tunnel_ip6_via_tunnel.remove_vpp_config()
345
346         rv = self.vapi.ipip_del_tunnel(sw_if_index=self.tunnel_if_index)
347
348     def validate(self, rx, expected):
349         self.assertEqual(rx, expected.__class__(expected))
350
351     def generate_ip6_frags(self, payload_length, fragment_size):
352         p_ether = Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac)
353         p_payload = UDP(sport=1234, dport=1234) / self.payload(payload_length)
354         p_ip6 = IPv6(src="1::1", dst=self.pg0.remote_ip6)
355         outer_ip6 = (p_ether / IPv6(src=self.pg1.remote_ip6,
356                                     dst=self.pg0.local_ip6) /
357                      IPv6ExtHdrFragment() / p_ip6 / p_payload)
358         frags = fragment6(outer_ip6, fragment_size)
359         p6_reply = (p_ip6 / p_payload)
360         p6_reply.hlim -= 1
361         return frags, p6_reply
362
363     def generate_ip6_hairpin_frags(self, payload_length, fragment_size):
364         p_ether = Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac)
365         p_payload = UDP(sport=1234, dport=1234) / self.payload(payload_length)
366         p_ip6 = IPv6(src="1::1", dst="dead::1")
367         outer_ip6 = (p_ether / IPv6(src=self.pg1.remote_ip6,
368                                     dst=self.pg0.local_ip6) /
369                      IPv6ExtHdrFragment() / p_ip6 / p_payload)
370         frags = fragment6(outer_ip6, fragment_size)
371         p_ip6.hlim -= 1
372         p6_reply = (IPv6(src=self.pg0.local_ip6, dst=self.pg1.remote_ip6,
373                          hlim=63) / p_ip6 / p_payload)
374
375         return frags, p6_reply
376
377     def test_encap(self):
378         """ ip{v4,v6} over ip6 test encap """
379         p_ether = Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac)
380         p_ip6 = IPv6(src="1::1", dst="DEAD::1", tc=42, nh='UDP')
381         p_ip4 = IP(src="1.2.3.4", dst="130.67.0.1", tos=42)
382         p_payload = UDP(sport=1234, dport=1234)
383
384         # Encapsulation
385         # IPv6 in to IPv6 tunnel
386         p6 = (p_ether / p_ip6 / p_payload)
387         p6_reply = (IPv6(src=self.pg0.local_ip6, dst=self.pg1.remote_ip6,
388                          hlim=64, tc=42) /
389                     p_ip6 / p_payload)
390         p6_reply[1].hlim -= 1
391         rx = self.send_and_expect(self.pg0, p6 * 11, self.pg1)
392         for p in rx:
393             self.validate(p[1], p6_reply)
394
395         # IPv4 in to IPv6 tunnel
396         p4 = (p_ether / p_ip4 / p_payload)
397         p4_reply = (IPv6(src=self.pg0.local_ip6,
398                          dst=self.pg1.remote_ip6, hlim=64, tc=42) /
399                     p_ip4 / p_payload)
400         p4_reply[1].ttl -= 1
401         rx = self.send_and_expect(self.pg0, p4 * 11, self.pg1)
402         for p in rx:
403             self.validate(p[1], p4_reply)
404
405     def test_decap(self):
406         """ ip{v4,v6} over ip6 test decap """
407
408         p_ether = Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac)
409         p_ip6 = IPv6(src="1::1", dst="DEAD::1", tc=42, nh='UDP')
410         p_ip4 = IP(src="1.2.3.4", dst=self.pg0.remote_ip4)
411         p_payload = UDP(sport=1234, dport=1234)
412
413         # Decapsulation
414         # IPv6 tunnel to IPv4
415
416         p4 = (p_ether / IPv6(src=self.pg1.remote_ip6,
417                              dst=self.pg0.local_ip6) / p_ip4 / p_payload)
418         p4_reply = (p_ip4 / p_payload)
419         p4_reply.ttl -= 1
420         rx = self.send_and_expect(self.pg1, p4 * 11, self.pg0)
421         for p in rx:
422             self.validate(p[1], p4_reply)
423
424         # IPv6 tunnel to IPv6
425         p_ip6 = IPv6(src="1:2:3::4", dst=self.pg0.remote_ip6)
426         p6 = (p_ether / IPv6(src=self.pg1.remote_ip6,
427                              dst=self.pg0.local_ip6) / p_ip6 / p_payload)
428         p6_reply = (p_ip6 / p_payload)
429         p6_reply.hlim = 63
430         rx = self.send_and_expect(self.pg1, p6 * 11, self.pg0)
431         for p in rx:
432             self.validate(p[1], p6_reply)
433
434     def test_frag(self):
435         """ ip{v4,v6} over ip6 test frag """
436
437         p_ether = Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac)
438         p_ip6 = IPv6(src="1::1", dst="DEAD::1", tc=42, nh='UDP')
439         p_ip4 = IP(src="1.2.3.4", dst=self.pg0.remote_ip4)
440         p_payload = UDP(sport=1234, dport=1234)
441
442         #
443         # Fragmentation / Reassembly and Re-fragmentation
444         #
445         rv = self.vapi.ip_reassembly_enable_disable(
446             sw_if_index=self.pg1.sw_if_index,
447             enable_ip6=1)
448
449         self.vapi.ip_reassembly_set(timeout_ms=1000, max_reassemblies=1000,
450                                     max_reassembly_length=1000,
451                                     expire_walk_interval_ms=10000,
452                                     is_ip6=1)
453
454         # Send lots of fragments, verify reassembled packet
455         before_cnt = self.statistics.get_err_counter(
456             '/err/ipip6-input/packets decapsulated')
457         frags, p6_reply = self.generate_ip6_frags(3131, 1400)
458         f = []
459         for i in range(0, 1000):
460             f.extend(frags)
461         self.pg1.add_stream(f)
462         self.pg_enable_capture()
463         self.pg_start()
464         rx = self.pg0.get_capture(1000)
465
466         for p in rx:
467             self.validate(p[1], p6_reply)
468
469         cnt = self.statistics.get_err_counter(
470             '/err/ipip6-input/packets decapsulated')
471         self.assertEqual(cnt, before_cnt + 1000)
472
473         f = []
474         r = []
475         # TODO: Check out why reassembly of atomic fragments don't work
476         for i in range(10, 90):
477             frags, p6_reply = self.generate_ip6_frags(i * 100, 1000)
478             f.extend(frags)
479             r.extend(p6_reply)
480         self.pg_enable_capture()
481         self.pg1.add_stream(f)
482         self.pg_start()
483         rx = self.pg0.get_capture(80)
484         i = 0
485         for p in rx:
486             self.validate(p[1], r[i])
487             i += 1
488
489         # Simple fragmentation
490         p_ether = Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac)
491         self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, [1280, 0, 0, 0])
492
493         # IPv6 in to IPv6 tunnel
494         p_payload = UDP(sport=1234, dport=1234) / self.payload(1300)
495
496         p6 = (p_ether / p_ip6 / p_payload)
497         p6_reply = (IPv6(src=self.pg0.local_ip6, dst=self.pg1.remote_ip6,
498                          hlim=63, tc=42) /
499                     p_ip6 / p_payload)
500         p6_reply[1].hlim -= 1
501         self.pg_enable_capture()
502         self.pg0.add_stream(p6)
503         self.pg_start()
504         rx = self.pg1.get_capture(2)
505
506         # Scapy defragment doesn't deal well with multiple layers
507         # of same type / Ethernet header first
508         f = [p[1] for p in rx]
509         reass_pkt = defragment6(f)
510         self.validate(reass_pkt, p6_reply)
511
512         # Now try with re-fragmentation
513         #
514         # Send large fragments to tunnel head-end, for the tunnel head end
515         # to reassemble and then refragment out the tunnel again.
516         # Hair-pinning
517         #
518         self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, [1280, 0, 0, 0])
519         frags, p6_reply = self.generate_ip6_hairpin_frags(8000, 1200)
520         self.pg_enable_capture()
521         self.pg1.add_stream(frags)
522         self.pg_start()
523         rx = self.pg1.get_capture(7)
524         f = [p[1] for p in rx]
525         reass_pkt = defragment6(f)
526         p6_reply.id = 256
527         self.validate(reass_pkt, p6_reply)
528
529     def test_ipip_create(self):
530         """ ipip create / delete interface test """
531         rv = ipip_add_tunnel(self, '1.2.3.4', '2.3.4.5')
532         sw_if_index = rv.sw_if_index
533         self.vapi.ipip_del_tunnel(sw_if_index)
534
535     def test_ipip_vrf_create(self):
536         """ ipip create / delete interface VRF test """
537
538         t = VppIpTable(self, 20)
539         t.add_vpp_config()
540         rv = ipip_add_tunnel(self, '1.2.3.4', '2.3.4.5', table_id=20)
541         sw_if_index = rv.sw_if_index
542         self.vapi.ipip_del_tunnel(sw_if_index)
543
544     def payload(self, len):
545         return 'x' * len
546
547
548 if __name__ == '__main__':
549     unittest.main(testRunner=VppTestRunner)