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