make test: fix missing log/packet messages
[vpp.git] / test / test_mpls.py
1 #!/usr/bin/env python
2
3 import unittest
4
5 from framework import VppTestCase, VppTestRunner
6 from vpp_sub_interface import VppSubInterface, VppDot1QSubint, VppDot1ADSubint
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
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             self.logger.error("MPLS TTL=0 packets forwarded")
625             self.logger.error(ppp("", rx))
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)