make test: Use VXLAN built in scapy 2.3.3
[vpp.git] / test / test_mpls.py
1 #!/usr/bin/env python
2
3 import unittest
4 import socket
5
6 from framework import VppTestCase, VppTestRunner
7 from vpp_ip_route import IpRoute, RoutePath, MplsRoute, MplsIpBind
8
9 from scapy.packet import Raw
10 from scapy.layers.l2 import Ether
11 from scapy.layers.inet import IP, UDP
12 from scapy.layers.inet6 import IPv6
13 from scapy.contrib.mpls import MPLS
14 from util import ppp
15
16
17 class TestMPLS(VppTestCase):
18     """ MPLS Test Case """
19
20     @classmethod
21     def setUpClass(cls):
22         super(TestMPLS, cls).setUpClass()
23
24     def setUp(self):
25         super(TestMPLS, self).setUp()
26
27         # create 2 pg interfaces
28         self.create_pg_interfaces(range(2))
29
30         # setup both interfaces
31         # assign them different tables.
32         table_id = 0
33
34         for i in self.pg_interfaces:
35             i.admin_up()
36             i.set_table_ip4(table_id)
37             i.set_table_ip6(table_id)
38             i.config_ip4()
39             i.resolve_arp()
40             i.config_ip6()
41             i.resolve_ndp()
42             i.enable_mpls()
43             table_id += 1
44
45     def tearDown(self):
46         super(TestMPLS, self).tearDown()
47
48     # the default of 64 matches the IP packet TTL default
49     def create_stream_labelled_ip4(self, src_if, mpls_labels, mpls_ttl=255):
50         pkts = []
51         for i in range(0, 257):
52             info = self.create_packet_info(src_if.sw_if_index,
53                                            src_if.sw_if_index)
54             payload = self.info_to_payload(info)
55             p = Ether(dst=src_if.local_mac, src=src_if.remote_mac)
56
57             for ii in range(len(mpls_labels)):
58                 if ii == len(mpls_labels) - 1:
59                     p = p / MPLS(label=mpls_labels[ii], ttl=mpls_ttl, s=1)
60                 else:
61                     p = p / MPLS(label=mpls_labels[ii], ttl=mpls_ttl, s=0)
62             p = (p / IP(src=src_if.remote_ip4, dst=src_if.remote_ip4) /
63                  UDP(sport=1234, dport=1234) /
64                  Raw(payload))
65             info.data = p.copy()
66             pkts.append(p)
67         return pkts
68
69     def create_stream_ip4(self, src_if, dst_ip):
70         pkts = []
71         for i in range(0, 257):
72             info = self.create_packet_info(src_if.sw_if_index,
73                                            src_if.sw_if_index)
74             payload = self.info_to_payload(info)
75             p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
76                  IP(src=src_if.remote_ip4, dst=dst_ip) /
77                  UDP(sport=1234, dport=1234) /
78                  Raw(payload))
79             info.data = p.copy()
80             pkts.append(p)
81         return pkts
82
83     def create_stream_labelled_ip6(self, src_if, mpls_label, mpls_ttl):
84         pkts = []
85         for i in range(0, 257):
86             info = self.create_packet_info(src_if.sw_if_index,
87                                            src_if.sw_if_index)
88             payload = self.info_to_payload(info)
89             p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
90                  MPLS(label=mpls_label, ttl=mpls_ttl) /
91                  IPv6(src=src_if.remote_ip6, dst=src_if.remote_ip6) /
92                  UDP(sport=1234, dport=1234) /
93                  Raw(payload))
94             info.data = p.copy()
95             pkts.append(p)
96         return pkts
97
98     @staticmethod
99     def verify_filter(capture, sent):
100         if not len(capture) == len(sent):
101             # filter out any IPv6 RAs from the capture
102             for p in capture:
103                 if p.haslayer(IPv6):
104                     capture.remove(p)
105         return capture
106
107     def verify_capture_ip4(self, src_if, capture, sent):
108         try:
109             capture = self.verify_filter(capture, sent)
110
111             self.assertEqual(len(capture), len(sent))
112
113             for i in range(len(capture)):
114                 tx = sent[i]
115                 rx = capture[i]
116
117                 # the rx'd packet has the MPLS label popped
118                 eth = rx[Ether]
119                 self.assertEqual(eth.type, 0x800)
120
121                 tx_ip = tx[IP]
122                 rx_ip = rx[IP]
123
124                 self.assertEqual(rx_ip.src, tx_ip.src)
125                 self.assertEqual(rx_ip.dst, tx_ip.dst)
126                 # IP processing post pop has decremented the TTL
127                 self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
128
129         except:
130             raise
131
132     def verify_mpls_stack(self, rx, mpls_labels, ttl=255, num=0):
133         # the rx'd packet has the MPLS label popped
134         eth = rx[Ether]
135         self.assertEqual(eth.type, 0x8847)
136
137         rx_mpls = rx[MPLS]
138
139         for ii in range(len(mpls_labels)):
140             self.assertEqual(rx_mpls.label, mpls_labels[ii])
141             self.assertEqual(rx_mpls.cos, 0)
142             if ii == num:
143                 self.assertEqual(rx_mpls.ttl, ttl)
144             else:
145                 self.assertEqual(rx_mpls.ttl, 255)
146
147             if ii == len(mpls_labels) - 1:
148                 self.assertEqual(rx_mpls.s, 1)
149             else:
150                 # not end of stack
151                 self.assertEqual(rx_mpls.s, 0)
152                 # pop the label to expose the next
153                 rx_mpls = rx_mpls[MPLS].payload
154
155     def verify_capture_labelled_ip4(self, src_if, capture, sent,
156                                     mpls_labels):
157         try:
158             capture = self.verify_filter(capture, sent)
159
160             self.assertEqual(len(capture), len(sent))
161
162             for i in range(len(capture)):
163                 tx = sent[i]
164                 rx = capture[i]
165                 tx_ip = tx[IP]
166                 rx_ip = rx[IP]
167
168                 # the MPLS TTL is copied from the IP
169                 self.verify_mpls_stack(
170                     rx, mpls_labels, rx_ip.ttl, len(mpls_labels) - 1)
171
172                 self.assertEqual(rx_ip.src, tx_ip.src)
173                 self.assertEqual(rx_ip.dst, tx_ip.dst)
174                 # IP processing post pop has decremented the TTL
175                 self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
176
177         except:
178             raise
179
180     def verify_capture_tunneled_ip4(self, src_if, capture, sent, mpls_labels):
181         try:
182             capture = self.verify_filter(capture, sent)
183
184             self.assertEqual(len(capture), len(sent))
185
186             for i in range(len(capture)):
187                 tx = sent[i]
188                 rx = capture[i]
189                 tx_ip = tx[IP]
190                 rx_ip = rx[IP]
191
192                 # the MPLS TTL is 255 since it enters a new tunnel
193                 self.verify_mpls_stack(
194                     rx, mpls_labels, 255, len(mpls_labels) - 1)
195
196                 self.assertEqual(rx_ip.src, tx_ip.src)
197                 self.assertEqual(rx_ip.dst, tx_ip.dst)
198                 # IP processing post pop has decremented the TTL
199                 self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
200
201         except:
202             raise
203
204     def verify_capture_labelled(self, src_if, capture, sent,
205                                 mpls_labels, ttl=254, num=0):
206         try:
207             capture = self.verify_filter(capture, sent)
208
209             self.assertEqual(len(capture), len(sent))
210
211             for i in range(len(capture)):
212                 rx = capture[i]
213                 self.verify_mpls_stack(rx, mpls_labels, ttl, num)
214         except:
215             raise
216
217     def verify_capture_ip6(self, src_if, capture, sent):
218         try:
219             self.assertEqual(len(capture), len(sent))
220
221             for i in range(len(capture)):
222                 tx = sent[i]
223                 rx = capture[i]
224
225                 # the rx'd packet has the MPLS label popped
226                 eth = rx[Ether]
227                 self.assertEqual(eth.type, 0x86DD)
228
229                 tx_ip = tx[IPv6]
230                 rx_ip = rx[IPv6]
231
232                 self.assertEqual(rx_ip.src, tx_ip.src)
233                 self.assertEqual(rx_ip.dst, tx_ip.dst)
234                 # IP processing post pop has decremented the TTL
235                 self.assertEqual(rx_ip.hlim + 1, tx_ip.hlim)
236
237         except:
238             raise
239
240     def test_swap(self):
241         """ MPLS label swap tests """
242
243         #
244         # A simple MPLS xconnect - eos label in label out
245         #
246         route_32_eos = MplsRoute(self, 32, 1,
247                                  [RoutePath(self.pg0.remote_ip4,
248                                             self.pg0.sw_if_index,
249                                             labels=[33])])
250         route_32_eos.add_vpp_config()
251
252         #
253         # a stream that matches the route for 10.0.0.1
254         # PG0 is in the default table
255         #
256         self.vapi.cli("clear trace")
257         tx = self.create_stream_labelled_ip4(self.pg0, [32])
258         self.pg0.add_stream(tx)
259
260         self.pg_enable_capture(self.pg_interfaces)
261         self.pg_start()
262
263         rx = self.pg0.get_capture()
264         self.verify_capture_labelled_ip4(self.pg0, rx, tx, [33])
265
266         #
267         # A simple MPLS xconnect - non-eos label in label out
268         #
269         route_32_neos = MplsRoute(self, 32, 0,
270                                   [RoutePath(self.pg0.remote_ip4,
271                                              self.pg0.sw_if_index,
272                                              labels=[33])])
273         route_32_neos.add_vpp_config()
274
275         #
276         # a stream that matches the route for 10.0.0.1
277         # PG0 is in the default table
278         #
279         self.vapi.cli("clear trace")
280         tx = self.create_stream_labelled_ip4(self.pg0, [32, 99])
281         self.pg0.add_stream(tx)
282
283         self.pg_enable_capture(self.pg_interfaces)
284         self.pg_start()
285
286         rx = self.pg0.get_capture()
287         self.verify_capture_labelled(self.pg0, rx, tx, [33, 99])
288
289         #
290         # An MPLS xconnect - EOS label in IP out
291         #
292         route_33_eos = MplsRoute(self, 33, 1,
293                                  [RoutePath(self.pg0.remote_ip4,
294                                             self.pg0.sw_if_index,
295                                             labels=[])])
296         route_33_eos.add_vpp_config()
297
298         self.vapi.cli("clear trace")
299         tx = self.create_stream_labelled_ip4(self.pg0, [33])
300         self.pg0.add_stream(tx)
301
302         self.pg_enable_capture(self.pg_interfaces)
303         self.pg_start()
304
305         rx = self.pg0.get_capture()
306         self.verify_capture_ip4(self.pg0, rx, tx)
307
308         #
309         # An MPLS xconnect - non-EOS label in IP out - an invalid configuration
310         # so this traffic should be dropped.
311         #
312         route_33_neos = MplsRoute(self, 33, 0,
313                                   [RoutePath(self.pg0.remote_ip4,
314                                              self.pg0.sw_if_index,
315                                              labels=[])])
316         route_33_neos.add_vpp_config()
317
318         self.vapi.cli("clear trace")
319         tx = self.create_stream_labelled_ip4(self.pg0, [33, 99])
320         self.pg0.add_stream(tx)
321
322         self.pg_enable_capture(self.pg_interfaces)
323         self.pg_start()
324
325         rx = self.pg0.get_capture()
326         try:
327             self.assertEqual(0, len(rx))
328         except:
329             self.logger.error("MPLS non-EOS packets popped and forwarded")
330             self.logger.error(ppp("", rx))
331             raise
332
333         #
334         # A recursive EOS x-connect, which resolves through another x-connect
335         #
336         route_34_eos = MplsRoute(self, 34, 1,
337                                  [RoutePath("0.0.0.0",
338                                             0xffffffff,
339                                             nh_via_label=32,
340                                             labels=[44, 45])])
341         route_34_eos.add_vpp_config()
342
343         self.vapi.cli("clear trace")
344         tx = self.create_stream_labelled_ip4(self.pg0, [34])
345         self.pg0.add_stream(tx)
346
347         self.pg_enable_capture(self.pg_interfaces)
348         self.pg_start()
349
350         rx = self.pg0.get_capture()
351         self.verify_capture_labelled_ip4(self.pg0, rx, tx, [33, 44, 45])
352
353         #
354         # A recursive non-EOS x-connect, which resolves through another
355         # x-connect
356         #
357         route_34_neos = MplsRoute(self, 34, 0,
358                                   [RoutePath("0.0.0.0",
359                                              0xffffffff,
360                                              nh_via_label=32,
361                                              labels=[44, 46])])
362         route_34_neos.add_vpp_config()
363
364         self.vapi.cli("clear trace")
365         tx = self.create_stream_labelled_ip4(self.pg0, [34, 99])
366         self.pg0.add_stream(tx)
367
368         self.pg_enable_capture(self.pg_interfaces)
369         self.pg_start()
370
371         rx = self.pg0.get_capture()
372         # it's the 2nd (counting from 0) label in the stack that is swapped
373         self.verify_capture_labelled(self.pg0, rx, tx, [33, 44, 46, 99], num=2)
374
375         #
376         # an recursive IP route that resolves through the recursive non-eos
377         # x-connect
378         #
379         ip_10_0_0_1 = IpRoute(self, "10.0.0.1", 32,
380                               [RoutePath("0.0.0.0",
381                                          0xffffffff,
382                                          nh_via_label=34,
383                                          labels=[55])])
384         ip_10_0_0_1.add_vpp_config()
385
386         self.vapi.cli("clear trace")
387         tx = self.create_stream_ip4(self.pg0, "10.0.0.1")
388         self.pg0.add_stream(tx)
389
390         self.pg_enable_capture(self.pg_interfaces)
391         self.pg_start()
392
393         rx = self.pg0.get_capture()
394         self.verify_capture_labelled_ip4(self.pg0, rx, tx, [33, 44, 46, 55])
395
396         ip_10_0_0_1.remove_vpp_config()
397         route_34_neos.remove_vpp_config()
398         route_34_eos.remove_vpp_config()
399         route_33_neos.remove_vpp_config()
400         route_33_eos.remove_vpp_config()
401         route_32_neos.remove_vpp_config()
402         route_32_eos.remove_vpp_config()
403
404     def test_bind(self):
405         """ MPLS Local Label Binding test """
406
407         #
408         # Add a non-recursive route with a single out label
409         #
410         route_10_0_0_1 = IpRoute(self, "10.0.0.1", 32,
411                                  [RoutePath(self.pg0.remote_ip4,
412                                             self.pg0.sw_if_index,
413                                             labels=[45])])
414         route_10_0_0_1.add_vpp_config()
415
416         # bind a local label to the route
417         binding = MplsIpBind(self, 44, "10.0.0.1", 32)
418         binding.add_vpp_config()
419
420         # non-EOS stream
421         self.vapi.cli("clear trace")
422         tx = self.create_stream_labelled_ip4(self.pg0, [44, 99])
423         self.pg0.add_stream(tx)
424
425         self.pg_enable_capture(self.pg_interfaces)
426         self.pg_start()
427
428         rx = self.pg0.get_capture()
429         self.verify_capture_labelled(self.pg0, rx, tx, [45, 99])
430
431         # EOS stream
432         self.vapi.cli("clear trace")
433         tx = self.create_stream_labelled_ip4(self.pg0, [44])
434         self.pg0.add_stream(tx)
435
436         self.pg_enable_capture(self.pg_interfaces)
437         self.pg_start()
438
439         rx = self.pg0.get_capture()
440         self.verify_capture_labelled(self.pg0, rx, tx, [45])
441
442         # IP stream
443         self.vapi.cli("clear trace")
444         tx = self.create_stream_ip4(self.pg0, "10.0.0.1")
445         self.pg0.add_stream(tx)
446
447         self.pg_enable_capture(self.pg_interfaces)
448         self.pg_start()
449
450         rx = self.pg0.get_capture()
451         self.verify_capture_labelled_ip4(self.pg0, rx, tx, [45])
452
453         #
454         # cleanup
455         #
456         binding.remove_vpp_config()
457         route_10_0_0_1.remove_vpp_config()
458
459     def test_imposition(self):
460         """ MPLS label imposition test """
461
462         #
463         # Add a non-recursive route with a single out label
464         #
465         route_10_0_0_1 = IpRoute(self, "10.0.0.1", 32,
466                                  [RoutePath(self.pg0.remote_ip4,
467                                             self.pg0.sw_if_index,
468                                             labels=[32])])
469         route_10_0_0_1.add_vpp_config()
470
471         #
472         # a stream that matches the route for 10.0.0.1
473         # PG0 is in the default table
474         #
475         self.vapi.cli("clear trace")
476         tx = self.create_stream_ip4(self.pg0, "10.0.0.1")
477         self.pg0.add_stream(tx)
478
479         self.pg_enable_capture(self.pg_interfaces)
480         self.pg_start()
481
482         rx = self.pg0.get_capture()
483         self.verify_capture_labelled_ip4(self.pg0, rx, tx, [32])
484
485         #
486         # Add a non-recursive route with a 3 out labels
487         #
488         route_10_0_0_2 = IpRoute(self, "10.0.0.2", 32,
489                                  [RoutePath(self.pg0.remote_ip4,
490                                             self.pg0.sw_if_index,
491                                             labels=[32, 33, 34])])
492         route_10_0_0_2.add_vpp_config()
493
494         #
495         # a stream that matches the route for 10.0.0.1
496         # PG0 is in the default table
497         #
498         self.vapi.cli("clear trace")
499         tx = self.create_stream_ip4(self.pg0, "10.0.0.2")
500         self.pg0.add_stream(tx)
501
502         self.pg_enable_capture(self.pg_interfaces)
503         self.pg_start()
504
505         rx = self.pg0.get_capture()
506         self.verify_capture_labelled_ip4(self.pg0, rx, tx, [32, 33, 34])
507
508         #
509         # add a recursive path, with output label, via the 1 label route
510         #
511         route_11_0_0_1 = IpRoute(self, "11.0.0.1", 32,
512                                  [RoutePath("10.0.0.1",
513                                             0xffffffff,
514                                             labels=[44])])
515         route_11_0_0_1.add_vpp_config()
516
517         #
518         # a stream that matches the route for 11.0.0.1, should pick up
519         # the label stack for 11.0.0.1 and 10.0.0.1
520         #
521         self.vapi.cli("clear trace")
522         tx = self.create_stream_ip4(self.pg0, "11.0.0.1")
523         self.pg0.add_stream(tx)
524
525         self.pg_enable_capture(self.pg_interfaces)
526         self.pg_start()
527
528         rx = self.pg0.get_capture()
529         self.verify_capture_labelled_ip4(self.pg0, rx, tx, [32, 44])
530
531         #
532         # add a recursive path, with 2 labels, via the 3 label route
533         #
534         route_11_0_0_2 = IpRoute(self, "11.0.0.2", 32,
535                                  [RoutePath("10.0.0.2",
536                                             0xffffffff,
537                                             labels=[44, 45])])
538         route_11_0_0_2.add_vpp_config()
539
540         #
541         # a stream that matches the route for 11.0.0.1, should pick up
542         # the label stack for 11.0.0.1 and 10.0.0.1
543         #
544         self.vapi.cli("clear trace")
545         tx = self.create_stream_ip4(self.pg0, "11.0.0.2")
546         self.pg0.add_stream(tx)
547
548         self.pg_enable_capture(self.pg_interfaces)
549         self.pg_start()
550
551         rx = self.pg0.get_capture()
552         self.verify_capture_labelled_ip4(
553             self.pg0, rx, tx, [32, 33, 34, 44, 45])
554
555         #
556         # cleanup
557         #
558         route_11_0_0_2.remove_vpp_config()
559         route_11_0_0_1.remove_vpp_config()
560         route_10_0_0_2.remove_vpp_config()
561         route_10_0_0_1.remove_vpp_config()
562
563     def test_tunnel(self):
564         """ MPLS Tunnel Tests """
565
566         #
567         # Create a tunnel with a single out label
568         #
569         nh_addr = socket.inet_pton(socket.AF_INET, self.pg0.remote_ip4)
570
571         reply = self.vapi.mpls_tunnel_add_del(
572             0xffffffff,  # don't know the if index yet
573             1,  # IPv4 next-hop
574             nh_addr,
575             self.pg0.sw_if_index,
576             0,  # next-hop-table-id
577             1,  # next-hop-weight
578             2,  # num-out-labels,
579             [44, 46]
580         )
581         self.vapi.sw_interface_set_flags(reply.sw_if_index, admin_up_down=1)
582
583         #
584         # add an unlabelled route through the new tunnel
585         #
586         dest_addr = socket.inet_pton(socket.AF_INET, "10.0.0.3")
587         nh_addr = socket.inet_pton(socket.AF_INET, "0.0.0.0")
588         dest_addr_len = 32
589
590         self.vapi.ip_add_del_route(
591             dest_addr,
592             dest_addr_len,
593             nh_addr,  # all zeros next-hop - tunnel is p2p
594             reply.sw_if_index,  # sw_if_index of the new tunnel
595             0,  # table-id
596             0,  # next-hop-table-id
597             1,  # next-hop-weight
598             0,  # num-out-labels,
599             []  # out-label
600         )
601
602         self.vapi.cli("clear trace")
603         tx = self.create_stream_ip4(self.pg0, "10.0.0.3")
604         self.pg0.add_stream(tx)
605
606         self.pg_enable_capture(self.pg_interfaces)
607         self.pg_start()
608
609         rx = self.pg0.get_capture()
610         self.verify_capture_tunneled_ip4(self.pg0, rx, tx, [44, 46])
611
612     def test_v4_exp_null(self):
613         """ MPLS V4 Explicit NULL test """
614
615         #
616         # The first test case has an MPLS TTL of 0
617         # all packet should be dropped
618         #
619         tx = self.create_stream_labelled_ip4(self.pg0, [0], 0)
620         self.pg0.add_stream(tx)
621
622         self.pg_enable_capture(self.pg_interfaces)
623         self.pg_start()
624
625         rx = self.pg0.get_capture()
626
627         try:
628             self.assertEqual(0, len(rx))
629         except:
630             self.logger.error("MPLS TTL=0 packets forwarded")
631             self.logger.error(ppp("", rx))
632             raise
633
634         #
635         # a stream with a non-zero MPLS TTL
636         # PG0 is in the default table
637         #
638         self.vapi.cli("clear trace")
639         tx = self.create_stream_labelled_ip4(self.pg0, [0])
640         self.pg0.add_stream(tx)
641
642         self.pg_enable_capture(self.pg_interfaces)
643         self.pg_start()
644
645         rx = self.pg0.get_capture()
646         self.verify_capture_ip4(self.pg0, rx, tx)
647
648         #
649         # a stream with a non-zero MPLS TTL
650         # PG1 is in table 1
651         # we are ensuring the post-pop lookup occurs in the VRF table
652         #
653         self.vapi.cli("clear trace")
654         tx = self.create_stream_labelled_ip4(self.pg1, [0])
655         self.pg1.add_stream(tx)
656
657         self.pg_enable_capture(self.pg_interfaces)
658         self.pg_start()
659
660         rx = self.pg1.get_capture()
661         self.verify_capture_ip4(self.pg0, rx, tx)
662
663     def test_v6_exp_null(self):
664         """ MPLS V6 Explicit NULL test """
665
666         #
667         # a stream with a non-zero MPLS TTL
668         # PG0 is in the default table
669         #
670         self.vapi.cli("clear trace")
671         tx = self.create_stream_labelled_ip6(self.pg0, 2, 2)
672         self.pg0.add_stream(tx)
673
674         self.pg_enable_capture(self.pg_interfaces)
675         self.pg_start()
676
677         rx = self.pg0.get_capture()
678         self.verify_capture_ip6(self.pg0, rx, tx)
679
680         #
681         # a stream with a non-zero MPLS TTL
682         # PG1 is in table 1
683         # we are ensuring the post-pop lookup occurs in the VRF table
684         #
685         self.vapi.cli("clear trace")
686         tx = self.create_stream_labelled_ip6(self.pg1, 2, 2)
687         self.pg1.add_stream(tx)
688
689         self.pg_enable_capture(self.pg_interfaces)
690         self.pg_start()
691
692         rx = self.pg1.get_capture()
693         self.verify_capture_ip6(self.pg0, rx, tx)
694
695
696 if __name__ == '__main__':
697     unittest.main(testRunner=VppTestRunner)