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