MPLS infrastructure improvments
[vpp.git] / test / test_mpls.py
1 #!/usr/bin/env python
2
3 import unittest
4 import socket
5 from logging import *
6
7 from framework import VppTestCase, VppTestRunner
8 from vpp_sub_interface import VppSubInterface, VppDot1QSubint, VppDot1ADSubint
9 from vpp_ip_route import IpRoute, RoutePath, MplsRoute, MplsIpBind
10
11 from scapy.packet import Raw
12 from scapy.layers.l2 import Ether, Dot1Q, ARP
13 from scapy.layers.inet import IP, UDP
14 from scapy.layers.inet6 import ICMPv6ND_NS, IPv6, UDP
15 from scapy.contrib.mpls import MPLS
16
17
18 class TestMPLS(VppTestCase):
19     """ MPLS Test Case """
20
21     @classmethod
22     def setUpClass(cls):
23         super(TestMPLS, cls).setUpClass()
24
25     def setUp(self):
26         super(TestMPLS, self).setUp()
27
28         # create 2 pg interfaces
29         self.create_pg_interfaces(range(2))
30
31         # setup both interfaces
32         # assign them different tables.
33         table_id = 0
34
35         for i in self.pg_interfaces:
36             i.admin_up()
37             i.set_table_ip4(table_id)
38             i.set_table_ip6(table_id)
39             i.config_ip4()
40             i.resolve_arp()
41             i.config_ip6()
42             i.resolve_ndp()
43             i.enable_mpls()
44             table_id += 1
45
46     def tearDown(self):
47         super(TestMPLS, self).tearDown()
48
49     # the default of 64 matches the IP packet TTL default
50     def create_stream_labelled_ip4(self, src_if, mpls_labels, mpls_ttl=255):
51         pkts = []
52         for i in range(0, 257):
53             info = self.create_packet_info(src_if.sw_if_index,
54                                            src_if.sw_if_index)
55             payload = self.info_to_payload(info)
56             p = Ether(dst=src_if.local_mac, src=src_if.remote_mac)
57
58             for ii in range(len(mpls_labels)):
59                 if ii == len(mpls_labels) - 1:
60                     p = p / MPLS(label=mpls_labels[ii], ttl=mpls_ttl, s=1)
61                 else:
62                     p = p / MPLS(label=mpls_labels[ii], ttl=mpls_ttl, s=0)
63             p = (p / IP(src=src_if.remote_ip4, dst=src_if.remote_ip4) /
64                  UDP(sport=1234, dport=1234) /
65                  Raw(payload))
66             info.data = p.copy()
67             pkts.append(p)
68         return pkts
69
70     def create_stream_ip4(self, src_if, dst_ip):
71         pkts = []
72         for i in range(0, 257):
73             info = self.create_packet_info(src_if.sw_if_index,
74                                            src_if.sw_if_index)
75             payload = self.info_to_payload(info)
76             p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
77                  IP(src=src_if.remote_ip4, dst=dst_ip) /
78                  UDP(sport=1234, dport=1234) /
79                  Raw(payload))
80             info.data = p.copy()
81             pkts.append(p)
82         return pkts
83
84     def create_stream_labelled_ip6(self, src_if, mpls_label, mpls_ttl):
85         pkts = []
86         for i in range(0, 257):
87             info = self.create_packet_info(src_if.sw_if_index,
88                                            src_if.sw_if_index)
89             payload = self.info_to_payload(info)
90             p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
91                  MPLS(label=mpls_label, ttl=mpls_ttl) /
92                  IPv6(src=src_if.remote_ip6, dst=src_if.remote_ip6) /
93                  UDP(sport=1234, dport=1234) /
94                  Raw(payload))
95             info.data = p.copy()
96             pkts.append(p)
97         return pkts
98
99     def verify_filter(self, capture, sent):
100         if not len(capture) == len(sent):
101             # filter out any IPv6 RAs from the captur
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             error("MPLS non-EOS packets popped and forwarded")
330             error(packet.show())
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 x-connect
355         #
356         route_34_neos = MplsRoute(self, 34, 0,
357                                   [RoutePath("0.0.0.0",
358                                              0xffffffff,
359                                              nh_via_label=32,
360                                              labels=[44, 46])])
361         route_34_neos.add_vpp_config()
362
363         self.vapi.cli("clear trace")
364         tx = self.create_stream_labelled_ip4(self.pg0, [34, 99])
365         self.pg0.add_stream(tx)
366
367         self.pg_enable_capture(self.pg_interfaces)
368         self.pg_start()
369
370         rx = self.pg0.get_capture()
371         # it's the 2nd (counting from 0) lael in the stack that is swapped
372         self.verify_capture_labelled(self.pg0, rx, tx, [33, 44, 46, 99], num=2)
373
374         #
375         # an recursive IP route that resolves through the recursive non-eos x-connect
376         #
377         ip_10_0_0_1 = IpRoute(self, "10.0.0.1", 32,
378                               [RoutePath("0.0.0.0",
379                                          0xffffffff,
380                                          nh_via_label=34,
381                                          labels=[55])])
382         ip_10_0_0_1.add_vpp_config()
383
384         self.vapi.cli("clear trace")
385         tx = self.create_stream_ip4(self.pg0, "10.0.0.1")
386         self.pg0.add_stream(tx)
387
388         self.pg_enable_capture(self.pg_interfaces)
389         self.pg_start()
390
391         rx = self.pg0.get_capture()
392         self.verify_capture_labelled_ip4(self.pg0, rx, tx, [33, 44, 46, 55])
393
394         ip_10_0_0_1.remove_vpp_config()
395         route_34_neos.remove_vpp_config()
396         route_34_eos.remove_vpp_config()
397         route_33_neos.remove_vpp_config()
398         route_33_eos.remove_vpp_config()
399         route_32_neos.remove_vpp_config()
400         route_32_eos.remove_vpp_config()
401
402     def test_bind(self):
403         """ MPLS Local Label Binding test """
404
405         #
406         # Add a non-recursive route with a single out label
407         #
408         route_10_0_0_1 = IpRoute(self, "10.0.0.1", 32,
409                                  [RoutePath(self.pg0.remote_ip4,
410                                             self.pg0.sw_if_index,
411                                             labels=[45])])
412         route_10_0_0_1.add_vpp_config()
413
414         # bind a local label to the route
415         binding = MplsIpBind(self, 44, "10.0.0.1", 32)
416         binding.add_vpp_config()
417
418         # non-EOS stream
419         self.vapi.cli("clear trace")
420         tx = self.create_stream_labelled_ip4(self.pg0, [44, 99])
421         self.pg0.add_stream(tx)
422
423         self.pg_enable_capture(self.pg_interfaces)
424         self.pg_start()
425
426         rx = self.pg0.get_capture()
427         self.verify_capture_labelled(self.pg0, rx, tx, [45, 99])
428
429         # EOS stream
430         self.vapi.cli("clear trace")
431         tx = self.create_stream_labelled_ip4(self.pg0, [44])
432         self.pg0.add_stream(tx)
433
434         self.pg_enable_capture(self.pg_interfaces)
435         self.pg_start()
436
437         rx = self.pg0.get_capture()
438         self.verify_capture_labelled(self.pg0, rx, tx, [45])
439
440         # IP stream
441         self.vapi.cli("clear trace")
442         tx = self.create_stream_ip4(self.pg0, "10.0.0.1")
443         self.pg0.add_stream(tx)
444
445         self.pg_enable_capture(self.pg_interfaces)
446         self.pg_start()
447
448         rx = self.pg0.get_capture()
449         self.verify_capture_labelled_ip4(self.pg0, rx, tx, [45])
450
451         #
452         # cleanup
453         #
454         binding.remove_vpp_config()
455         route_10_0_0_1.remove_vpp_config()
456
457     def test_imposition(self):
458         """ MPLS label imposition test """
459
460         #
461         # Add a non-recursive route with a single out label
462         #
463         route_10_0_0_1 = IpRoute(self, "10.0.0.1", 32,
464                                  [RoutePath(self.pg0.remote_ip4,
465                                             self.pg0.sw_if_index,
466                                             labels=[32])])
467         route_10_0_0_1.add_vpp_config()
468
469         #
470         # a stream that matches the route for 10.0.0.1
471         # PG0 is in the default table
472         #
473         self.vapi.cli("clear trace")
474         tx = self.create_stream_ip4(self.pg0, "10.0.0.1")
475         self.pg0.add_stream(tx)
476
477         self.pg_enable_capture(self.pg_interfaces)
478         self.pg_start()
479
480         rx = self.pg0.get_capture()
481         self.verify_capture_labelled_ip4(self.pg0, rx, tx, [32])
482
483         #
484         # Add a non-recursive route with a 3 out labels
485         #
486         route_10_0_0_2 = IpRoute(self, "10.0.0.2", 32,
487                                  [RoutePath(self.pg0.remote_ip4,
488                                             self.pg0.sw_if_index,
489                                             labels=[32, 33, 34])])
490         route_10_0_0_2.add_vpp_config()
491
492         #
493         # a stream that matches the route for 10.0.0.1
494         # PG0 is in the default table
495         #
496         self.vapi.cli("clear trace")
497         tx = self.create_stream_ip4(self.pg0, "10.0.0.2")
498         self.pg0.add_stream(tx)
499
500         self.pg_enable_capture(self.pg_interfaces)
501         self.pg_start()
502
503         rx = self.pg0.get_capture()
504         self.verify_capture_labelled_ip4(self.pg0, rx, tx, [32, 33, 34])
505
506         #
507         # add a recursive path, with ouput label, via the 1 label route
508         #
509         route_11_0_0_1 = IpRoute(self, "11.0.0.1", 32,
510                                  [RoutePath("10.0.0.1",
511                                             0xffffffff,
512                                             labels=[44])])
513         route_11_0_0_1.add_vpp_config()
514
515         #
516         # a stream that matches the route for 11.0.0.1, should pick up
517         # the label stack for 11.0.0.1 and 10.0.0.1
518         #
519         self.vapi.cli("clear trace")
520         tx = self.create_stream_ip4(self.pg0, "11.0.0.1")
521         self.pg0.add_stream(tx)
522
523         self.pg_enable_capture(self.pg_interfaces)
524         self.pg_start()
525
526         rx = self.pg0.get_capture()
527         self.verify_capture_labelled_ip4(self.pg0, rx, tx, [32, 44])
528
529         #
530         # add a recursive path, with 2 labels, via the 3 label route
531         #
532         route_11_0_0_2 = IpRoute(self, "11.0.0.2", 32,
533                                  [RoutePath("10.0.0.2",
534                                             0xffffffff,
535                                             labels=[44, 45])])
536         route_11_0_0_2.add_vpp_config()
537
538         #
539         # a stream that matches the route for 11.0.0.1, should pick up
540         # the label stack for 11.0.0.1 and 10.0.0.1
541         #
542         self.vapi.cli("clear trace")
543         tx = self.create_stream_ip4(self.pg0, "11.0.0.2")
544         self.pg0.add_stream(tx)
545
546         self.pg_enable_capture(self.pg_interfaces)
547         self.pg_start()
548
549         rx = self.pg0.get_capture()
550         self.verify_capture_labelled_ip4(
551             self.pg0, rx, tx, [32, 33, 34, 44, 45])
552
553         #
554         # cleanup
555         #
556         route_11_0_0_2.remove_vpp_config()
557         route_11_0_0_1.remove_vpp_config()
558         route_10_0_0_2.remove_vpp_config()
559         route_10_0_0_1.remove_vpp_config()
560
561     def test_tunnel(self):
562         """ MPLS Tunnel Tests """
563
564         #
565         # Create a tunnel with a single out label
566         #
567         nh_addr = socket.inet_pton(socket.AF_INET, self.pg0.remote_ip4)
568
569         reply = self.vapi.mpls_tunnel_add_del(0xffffffff,  # don't know the if index yet
570                                               1,  # IPv4 next-hop
571                                               nh_addr,
572                                               self.pg0.sw_if_index,
573                                               0,  # next-hop-table-id
574                                               1,  # next-hop-weight
575                                               2,  # num-out-labels,
576                                               [44, 46])
577         self.vapi.sw_interface_set_flags(reply.sw_if_index, admin_up_down=1)
578
579         #
580         # add an unlabelled route through the new tunnel
581         #
582         dest_addr = socket.inet_pton(socket.AF_INET, "10.0.0.3")
583         nh_addr = socket.inet_pton(socket.AF_INET, "0.0.0.0")
584         dest_addr_len = 32
585
586         self.vapi.ip_add_del_route(dest_addr,
587                                    dest_addr_len,
588                                    nh_addr,  # all zeros next-hop - tunnel is p2p
589                                    reply.sw_if_index,  # sw_if_index of the new tunnel
590                                    0,  # table-id
591                                    0,  # next-hop-table-id
592                                    1,  # next-hop-weight
593                                    0,  # num-out-labels,
594                                    [])  # out-label
595
596         self.vapi.cli("clear trace")
597         tx = self.create_stream_ip4(self.pg0, "10.0.0.3")
598         self.pg0.add_stream(tx)
599
600         self.pg_enable_capture(self.pg_interfaces)
601         self.pg_start()
602
603         rx = self.pg0.get_capture()
604         self.verify_capture_tunneled_ip4(self.pg0, rx, tx, [44, 46])
605
606     def test_v4_exp_null(self):
607         """ MPLS V4 Explicit NULL test """
608
609         #
610         # The first test case has an MPLS TTL of 0
611         # all packet should be dropped
612         #
613         tx = self.create_stream_labelled_ip4(self.pg0, [0], 0)
614         self.pg0.add_stream(tx)
615
616         self.pg_enable_capture(self.pg_interfaces)
617         self.pg_start()
618
619         rx = self.pg0.get_capture()
620
621         try:
622             self.assertEqual(0, len(rx))
623         except:
624             error("MPLS TTL=0 packets forwarded")
625             error(packet.show())
626             raise
627
628         #
629         # a stream with a non-zero MPLS TTL
630         # PG0 is in the default table
631         #
632         self.vapi.cli("clear trace")
633         tx = self.create_stream_labelled_ip4(self.pg0, [0])
634         self.pg0.add_stream(tx)
635
636         self.pg_enable_capture(self.pg_interfaces)
637         self.pg_start()
638
639         rx = self.pg0.get_capture()
640         self.verify_capture_ip4(self.pg0, rx, tx)
641
642         #
643         # a stream with a non-zero MPLS TTL
644         # PG1 is in table 1
645         # we are ensuring the post-pop lookup occurs in the VRF table
646         #
647         self.vapi.cli("clear trace")
648         tx = self.create_stream_labelled_ip4(self.pg1, [0])
649         self.pg1.add_stream(tx)
650
651         self.pg_enable_capture(self.pg_interfaces)
652         self.pg_start()
653
654         rx = self.pg1.get_capture()
655         self.verify_capture_ip4(self.pg0, rx, tx)
656
657     def test_v6_exp_null(self):
658         """ MPLS V6 Explicit NULL test """
659
660         #
661         # a stream with a non-zero MPLS TTL
662         # PG0 is in the default table
663         #
664         self.vapi.cli("clear trace")
665         tx = self.create_stream_labelled_ip6(self.pg0, 2, 2)
666         self.pg0.add_stream(tx)
667
668         self.pg_enable_capture(self.pg_interfaces)
669         self.pg_start()
670
671         rx = self.pg0.get_capture()
672         self.verify_capture_ip6(self.pg0, rx, tx)
673
674         #
675         # a stream with a non-zero MPLS TTL
676         # PG1 is in table 1
677         # we are ensuring the post-pop lookup occurs in the VRF table
678         #
679         self.vapi.cli("clear trace")
680         tx = self.create_stream_labelled_ip6(self.pg1, 2, 2)
681         self.pg1.add_stream(tx)
682
683         self.pg_enable_capture(self.pg_interfaces)
684         self.pg_start()
685
686         rx = self.pg1.get_capture()
687         self.verify_capture_ip6(self.pg0, rx, tx)
688
689
690 if __name__ == '__main__':
691     unittest.main(testRunner=VppTestRunner)