514265cb5b5f28ddf38b8373ffddc73cc8e382b5
[vpp.git] / test / test_bier.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 VppIpRoute, VppRoutePath, VppMplsRoute, \
8     VppMplsTable, VppIpMRoute, VppMRoutePath, VppIpTable, \
9     MRouteEntryFlags, MRouteItfFlags, MPLS_LABEL_INVALID, DpoProto
10 from vpp_bier import *
11 from vpp_udp_encap import *
12
13 from scapy.packet import Raw
14 from scapy.layers.l2 import Ether
15 from scapy.layers.inet import IP, UDP, ICMP
16 from scapy.layers.inet6 import IPv6
17 from scapy.contrib.mpls import MPLS
18 from scapy.contrib.bier import *
19
20
21 class TestBFIB(VppTestCase):
22     """ BIER FIB Test Case """
23
24     def test_bfib(self):
25         """ BFIB Unit Tests """
26         error = self.vapi.cli("test bier")
27
28         if error:
29             self.logger.critical(error)
30         self.assertEqual(error.find("Failed"), -1)
31
32
33 class TestBier(VppTestCase):
34     """ BIER Test Case """
35
36     def setUp(self):
37         super(TestBier, self).setUp()
38
39         # create 2 pg interfaces
40         self.create_pg_interfaces(range(3))
41
42         # create the default MPLS table
43         self.tables = []
44         tbl = VppMplsTable(self, 0)
45         tbl.add_vpp_config()
46         self.tables.append(tbl)
47
48         tbl = VppIpTable(self, 10)
49         tbl.add_vpp_config()
50         self.tables.append(tbl)
51
52         # setup both interfaces
53         for i in self.pg_interfaces:
54             if i == self.pg2:
55                 i.set_table_ip4(10)
56             i.admin_up()
57             i.config_ip4()
58             i.resolve_arp()
59             i.enable_mpls()
60
61     def tearDown(self):
62         for i in self.pg_interfaces:
63             i.disable_mpls()
64             i.unconfig_ip4()
65             i.set_table_ip4(0)
66             i.admin_down()
67         super(TestBier, self).tearDown()
68
69     def send_and_assert_no_replies(self, intf, pkts, remark):
70         intf.add_stream(pkts)
71         self.pg_enable_capture(self.pg_interfaces)
72         self.pg_start()
73         for i in self.pg_interfaces:
74             i.assert_nothing_captured(remark=remark)
75
76     def send_and_expect(self, input, pkts, output):
77         self.vapi.cli("trace add bier-mpls-lookup 10")
78         input.add_stream(pkts)
79         self.pg_enable_capture(self.pg_interfaces)
80         self.pg_start()
81         rx = output.get_capture(len(pkts))
82         return rx
83
84     def test_bier_midpoint(self):
85         """BIER midpoint"""
86
87         #
88         # Add a BIER table for sub-domain 0, set 0, and BSL 256
89         #
90         bti = VppBierTableID(0, 0, BIERLength.BIER_LEN_256)
91         bt = VppBierTable(self, bti, 77)
92         bt.add_vpp_config()
93
94         #
95         # A packet with no bits set gets dropped
96         #
97         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
98              MPLS(label=77, ttl=255) /
99              BIER(length=BIERLength.BIER_LEN_256,
100                   BitString=chr(0)*64) /
101              IPv6(src=self.pg0.remote_ip6, dst=self.pg0.remote_ip6) /
102              UDP(sport=1234, dport=1234) /
103              Raw())
104         pkts = [p]
105
106         self.send_and_assert_no_replies(self.pg0, pkts,
107                                         "Empty Bit-String")
108
109         #
110         # Add a BIER route for each bit-position in the table via a different
111         # next-hop. Testing whether the BIER walk and replicate forwarding
112         # function works for all bit posisitons.
113         #
114         nh_routes = []
115         bier_routes = []
116         for i in range(1, 256):
117             nh = "10.0.%d.%d" % (i / 255, i % 255)
118             nh_routes.append(VppIpRoute(self, nh, 32,
119                                         [VppRoutePath(self.pg1.remote_ip4,
120                                                       self.pg1.sw_if_index,
121                                                       labels=[2000+i])]))
122             nh_routes[-1].add_vpp_config()
123
124             bier_routes.append(VppBierRoute(self, bti, i,
125                                             [VppRoutePath(nh, 0xffffffff,
126                                                           labels=[100+i])]))
127             bier_routes[-1].add_vpp_config()
128
129         #
130         # A packet with all bits set gets spat out to BP:1
131         #
132         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
133              MPLS(label=77, ttl=255) /
134              BIER(length=BIERLength.BIER_LEN_256) /
135              IPv6(src=self.pg0.remote_ip6, dst=self.pg0.remote_ip6) /
136              UDP(sport=1234, dport=1234) /
137              Raw())
138         pkts = [p]
139
140         self.pg0.add_stream(pkts)
141         self.pg_enable_capture(self.pg_interfaces)
142         self.pg_start()
143
144         rx = self.pg1.get_capture(255)
145
146         for rxp in rx:
147             #
148             # The packets are not required to be sent in bit-position order
149             # when we setup the routes above we used the bit-position to
150             # construct the out-label. so use that here to determine the BP
151             #
152             olabel = rxp[MPLS]
153             bp = olabel.label - 2000
154
155             blabel = olabel[MPLS].payload
156             self.assertEqual(blabel.label, 100+bp)
157             self.assertEqual(blabel.ttl, 254)
158
159             bier_hdr = blabel[MPLS].payload
160
161             self.assertEqual(bier_hdr.id, 5)
162             self.assertEqual(bier_hdr.version, 0)
163             self.assertEqual(bier_hdr.length, BIERLength.BIER_LEN_256)
164             self.assertEqual(bier_hdr.entropy, 0)
165             self.assertEqual(bier_hdr.OAM, 0)
166             self.assertEqual(bier_hdr.RSV, 0)
167             self.assertEqual(bier_hdr.DSCP, 0)
168             self.assertEqual(bier_hdr.Proto, 5)
169
170             # The bit-string should consist only of the BP given by i.
171             i = 0
172             bitstring = ""
173             bpi = bp - 1
174             while (i < bpi/8):
175                 bitstring = chr(0) + bitstring
176                 i += 1
177             bitstring = chr(1 << bpi % 8) + bitstring
178
179             while len(bitstring) < 32:
180                 bitstring = chr(0) + bitstring
181
182             self.assertEqual(len(bitstring), len(bier_hdr.BitString))
183             self.assertEqual(bitstring, bier_hdr.BitString)
184
185     def test_bier_head(self):
186         """BIER head"""
187
188         #
189         # Add a BIER table for sub-domain 0, set 0, and BSL 256
190         #
191         bti = VppBierTableID(0, 0, BIERLength.BIER_LEN_256)
192         bt = VppBierTable(self, bti, 77)
193         bt.add_vpp_config()
194
195         #
196         # 2 bit positions via two next hops
197         #
198         nh1 = "10.0.0.1"
199         nh2 = "10.0.0.2"
200         ip_route_1 = VppIpRoute(self, nh1, 32,
201                                 [VppRoutePath(self.pg1.remote_ip4,
202                                               self.pg1.sw_if_index,
203                                               labels=[2001])])
204         ip_route_2 = VppIpRoute(self, nh2, 32,
205                                 [VppRoutePath(self.pg1.remote_ip4,
206                                               self.pg1.sw_if_index,
207                                               labels=[2002])])
208         ip_route_1.add_vpp_config()
209         ip_route_2.add_vpp_config()
210
211         bier_route_1 = VppBierRoute(self, bti, 1,
212                                     [VppRoutePath(nh1, 0xffffffff,
213                                                   labels=[101])])
214         bier_route_2 = VppBierRoute(self, bti, 2,
215                                     [VppRoutePath(nh2, 0xffffffff,
216                                                   labels=[102])])
217         bier_route_1.add_vpp_config()
218         bier_route_2.add_vpp_config()
219
220         #
221         # An imposition object with both bit-positions set
222         #
223         bi = VppBierImp(self, bti, 333, chr(0x3) * 32)
224         bi.add_vpp_config()
225
226         #
227         # Add a multicast route that will forward into the BIER doamin
228         #
229         route_ing_232_1_1_1 = VppIpMRoute(
230             self,
231             "0.0.0.0",
232             "232.1.1.1", 32,
233             MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
234             paths=[VppMRoutePath(self.pg0.sw_if_index,
235                                  MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
236                    VppMRoutePath(0xffffffff,
237                                  MRouteItfFlags.MFIB_ITF_FLAG_FORWARD,
238                                  proto=DpoProto.DPO_PROTO_BIER,
239                                  bier_imp=bi.bi_index)])
240         route_ing_232_1_1_1.add_vpp_config()
241
242         #
243         # inject an IP packet. We expect it to be BIER encapped and
244         # replicated.
245         #
246         p = (Ether(dst=self.pg0.local_mac,
247                    src=self.pg0.remote_mac) /
248              IP(src="1.1.1.1", dst="232.1.1.1") /
249              UDP(sport=1234, dport=1234))
250
251         self.pg0.add_stream([p])
252         self.pg_enable_capture(self.pg_interfaces)
253         self.pg_start()
254
255         rx = self.pg1.get_capture(2)
256
257         #
258         # Encap Stack is; eth, MPLS, MPLS, BIER
259         #
260         igp_mpls = rx[0][MPLS]
261         self.assertEqual(igp_mpls.label, 2001)
262         self.assertEqual(igp_mpls.ttl, 64)
263         self.assertEqual(igp_mpls.s, 0)
264         bier_mpls = igp_mpls[MPLS].payload
265         self.assertEqual(bier_mpls.label, 101)
266         self.assertEqual(bier_mpls.ttl, 64)
267         self.assertEqual(bier_mpls.s, 1)
268         self.assertEqual(rx[0][BIER].length, 2)
269
270         igp_mpls = rx[1][MPLS]
271         self.assertEqual(igp_mpls.label, 2002)
272         self.assertEqual(igp_mpls.ttl, 64)
273         self.assertEqual(igp_mpls.s, 0)
274         bier_mpls = igp_mpls[MPLS].payload
275         self.assertEqual(bier_mpls.label, 102)
276         self.assertEqual(bier_mpls.ttl, 64)
277         self.assertEqual(bier_mpls.s, 1)
278         self.assertEqual(rx[0][BIER].length, 2)
279
280     def test_bier_tail(self):
281         """BIER Tail"""
282
283         #
284         # Add a BIER table for sub-domain 0, set 0, and BSL 256
285         #
286         bti = VppBierTableID(0, 0, BIERLength.BIER_LEN_256)
287         bt = VppBierTable(self, bti, 77)
288         bt.add_vpp_config()
289
290         #
291         # disposition table
292         #
293         bdt = VppBierDispTable(self, 8)
294         bdt.add_vpp_config()
295
296         #
297         # BIER route in table that's for-us
298         #
299         bier_route_1 = VppBierRoute(self, bti, 1,
300                                     [VppRoutePath("0.0.0.0",
301                                                   0xffffffff,
302                                                   nh_table_id=8)])
303         bier_route_1.add_vpp_config()
304
305         #
306         # An entry in the disposition table
307         #
308         bier_de_1 = VppBierDispEntry(self, bdt.id, 99,
309                                      BIER_HDR_PAYLOAD.BIER_HDR_PROTO_IPV4,
310                                      "0.0.0.0", 0, rpf_id=8192)
311         bier_de_1.add_vpp_config()
312
313         #
314         # A multicast route to forward post BIER disposition
315         #
316         route_eg_232_1_1_1 = VppIpMRoute(
317             self,
318             "0.0.0.0",
319             "232.1.1.1", 32,
320             MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
321             paths=[VppMRoutePath(self.pg1.sw_if_index,
322                                  MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
323         route_eg_232_1_1_1.add_vpp_config()
324         route_eg_232_1_1_1.update_rpf_id(8192)
325
326         #
327         # A packet with all bits set gets spat out to BP:1
328         #
329         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
330              MPLS(label=77, ttl=255) /
331              BIER(length=BIERLength.BIER_LEN_256, BFRID=99) /
332              IP(src="1.1.1.1", dst="232.1.1.1") /
333              UDP(sport=1234, dport=1234) /
334              Raw())
335
336         self.send_and_expect(self.pg0, [p], self.pg1)
337
338         #
339         # A packet that does not match the Disposition entry gets dropped
340         #
341         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
342              MPLS(label=77, ttl=255) /
343              BIER(length=BIERLength.BIER_LEN_256, BFRID=77) /
344              IP(src="1.1.1.1", dst="232.1.1.1") /
345              UDP(sport=1234, dport=1234) /
346              Raw())
347         self.send_and_assert_no_replies(self.pg0, p*2,
348                                         "no matching disposition entry")
349
350         #
351         # Add the default route to the disposition table
352         #
353         bier_de_2 = VppBierDispEntry(self, bdt.id, 0,
354                                      BIER_HDR_PAYLOAD.BIER_HDR_PROTO_IPV4,
355                                      "0.0.0.0", 0, rpf_id=8192)
356         bier_de_2.add_vpp_config()
357
358         #
359         # now the previous packet is forwarded
360         #
361         self.send_and_expect(self.pg0, [p], self.pg1)
362
363     def test_bier_e2e(self):
364         """ BIER end-to-end """
365
366         #
367         # Add a BIER table for sub-domain 0, set 0, and BSL 256
368         #
369         bti = VppBierTableID(0, 0, BIERLength.BIER_LEN_256)
370         bt = VppBierTable(self, bti, 77)
371         bt.add_vpp_config()
372
373         #
374         # Impostion Sets bit string 101010101....
375         #  sender 333
376         #
377         bi = VppBierImp(self, bti, 333, chr(0x5) * 32)
378         bi.add_vpp_config()
379
380         #
381         # Add a multicast route that will forward into the BIER doamin
382         #
383         route_ing_232_1_1_1 = VppIpMRoute(
384             self,
385             "0.0.0.0",
386             "232.1.1.1", 32,
387             MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
388             paths=[VppMRoutePath(self.pg0.sw_if_index,
389                                  MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
390                    VppMRoutePath(0xffffffff,
391                                  MRouteItfFlags.MFIB_ITF_FLAG_FORWARD,
392                                  proto=DpoProto.DPO_PROTO_BIER,
393                                  bier_imp=bi.bi_index)])
394         route_ing_232_1_1_1.add_vpp_config()
395
396         #
397         # disposition table 8
398         #
399         bdt = VppBierDispTable(self, 8)
400         bdt.add_vpp_config()
401
402         #
403         # BIER route in table that's for-us, resolving through
404         # disp table 8.
405         #
406         bier_route_1 = VppBierRoute(self, bti, 1,
407                                     [VppRoutePath("0.0.0.0",
408                                                   0xffffffff,
409                                                   nh_table_id=8)])
410         bier_route_1.add_vpp_config()
411
412         #
413         # An entry in the disposition table for sender 333
414         #  lookup in VRF 10
415         #
416         bier_de_1 = VppBierDispEntry(self, bdt.id, 333,
417                                      BIER_HDR_PAYLOAD.BIER_HDR_PROTO_IPV4,
418                                      "0.0.0.0", 10, rpf_id=8192)
419         bier_de_1.add_vpp_config()
420
421         #
422         # Add a multicast route that will forward the traffic
423         # post-disposition
424         #
425         route_eg_232_1_1_1 = VppIpMRoute(
426             self,
427             "0.0.0.0",
428             "232.1.1.1", 32,
429             MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
430             table_id=10,
431             paths=[VppMRoutePath(self.pg1.sw_if_index,
432                                  MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
433         route_eg_232_1_1_1.add_vpp_config()
434         route_eg_232_1_1_1.update_rpf_id(8192)
435
436         #
437         # inject a packet in VRF-0. We expect it to be BIER encapped,
438         # replicated, then hit the disposition and be forwarded
439         # out of VRF 10, i.e. on pg1
440         #
441         p = (Ether(dst=self.pg0.local_mac,
442                    src=self.pg0.remote_mac) /
443              IP(src="1.1.1.1", dst="232.1.1.1") /
444              UDP(sport=1234, dport=1234))
445
446         rx = self.send_and_expect(self.pg0, p*65, self.pg1)
447
448         #
449         # should be IP
450         #
451         self.assertEqual(rx[0][IP].src, "1.1.1.1")
452         self.assertEqual(rx[0][IP].dst, "232.1.1.1")
453
454     def test_bier_head_o_udp(self):
455         """BIER head over UDP"""
456
457         #
458         # Add a BIER table for sub-domain 1, set 0, and BSL 256
459         #
460         bti = VppBierTableID(1, 0, BIERLength.BIER_LEN_256)
461         bt = VppBierTable(self, bti, 77)
462         bt.add_vpp_config()
463
464         #
465         # 1 bit positions via 1 next hops
466         #
467         nh1 = "10.0.0.1"
468         ip_route = VppIpRoute(self, nh1, 32,
469                               [VppRoutePath(self.pg1.remote_ip4,
470                                             self.pg1.sw_if_index,
471                                             labels=[2001])])
472         ip_route.add_vpp_config()
473
474         udp_encap = VppUdpEncap(self, 4,
475                                 self.pg0.local_ip4,
476                                 nh1,
477                                 330, 8138)
478         udp_encap.add_vpp_config()
479
480         bier_route = VppBierRoute(self, bti, 1,
481                                   [VppRoutePath("0.0.0.0",
482                                                 0xFFFFFFFF,
483                                                 is_udp_encap=1,
484                                                 next_hop_id=4)])
485         bier_route.add_vpp_config()
486
487         #
488         # An imposition object with all bit-positions set
489         #
490         bi = VppBierImp(self, bti, 333, chr(0xff) * 32)
491         bi.add_vpp_config()
492
493         #
494         # Add a multicast route that will forward into the BIER doamin
495         #
496         route_ing_232_1_1_1 = VppIpMRoute(
497             self,
498             "0.0.0.0",
499             "232.1.1.1", 32,
500             MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
501             paths=[VppMRoutePath(self.pg0.sw_if_index,
502                                  MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
503                    VppMRoutePath(0xffffffff,
504                                  MRouteItfFlags.MFIB_ITF_FLAG_FORWARD,
505                                  proto=DpoProto.DPO_PROTO_BIER,
506                                  bier_imp=bi.bi_index)])
507         route_ing_232_1_1_1.add_vpp_config()
508
509         #
510         # inject a packet an IP. We expect it to be BIER and UDP encapped,
511         #
512         p = (Ether(dst=self.pg0.local_mac,
513                    src=self.pg0.remote_mac) /
514              IP(src="1.1.1.1", dst="232.1.1.1") /
515              UDP(sport=1234, dport=1234))
516
517         self.pg0.add_stream([p])
518         self.pg_enable_capture(self.pg_interfaces)
519         self.pg_start()
520
521         rx = self.pg1.get_capture(1)
522
523         #
524         # Encap Stack is, eth, IP, UDP, BIFT, BIER
525         #
526         self.assertEqual(rx[0][IP].src, self.pg0.local_ip4)
527         self.assertEqual(rx[0][IP].dst, nh1)
528         self.assertEqual(rx[0][UDP].sport, 330)
529         self.assertEqual(rx[0][UDP].dport, 8138)
530         self.assertEqual(rx[0][BIFT].bsl, 2)
531         self.assertEqual(rx[0][BIFT].sd, 1)
532         self.assertEqual(rx[0][BIFT].set, 0)
533         self.assertEqual(rx[0][BIFT].ttl, 64)
534         self.assertEqual(rx[0][BIER].length, 2)
535
536     def test_bier_tail_o_udp(self):
537         """BIER Tail over UDP"""
538
539         #
540         # Add a BIER table for sub-domain 0, set 0, and BSL 256
541         #
542         bti = VppBierTableID(1, 0, BIERLength.BIER_LEN_256)
543         bt = VppBierTable(self, bti, MPLS_LABEL_INVALID)
544         bt.add_vpp_config()
545
546         #
547         # disposition table
548         #
549         bdt = VppBierDispTable(self, 8)
550         bdt.add_vpp_config()
551
552         #
553         # BIER route in table that's for-us
554         #
555         bier_route_1 = VppBierRoute(self, bti, 1,
556                                     [VppRoutePath("0.0.0.0",
557                                                   0xffffffff,
558                                                   nh_table_id=8)])
559         bier_route_1.add_vpp_config()
560
561         #
562         # An entry in the disposition table
563         #
564         bier_de_1 = VppBierDispEntry(self, bdt.id, 99,
565                                      BIER_HDR_PAYLOAD.BIER_HDR_PROTO_IPV4,
566                                      "0.0.0.0", 0, rpf_id=8192)
567         bier_de_1.add_vpp_config()
568
569         #
570         # A multicast route to forward post BIER disposition
571         #
572         route_eg_232_1_1_1 = VppIpMRoute(
573             self,
574             "0.0.0.0",
575             "232.1.1.1", 32,
576             MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
577             paths=[VppMRoutePath(self.pg1.sw_if_index,
578                                  MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
579         route_eg_232_1_1_1.add_vpp_config()
580         route_eg_232_1_1_1.update_rpf_id(8192)
581
582         #
583         # A packet with all bits set gets spat out to BP:1
584         #
585         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
586              IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
587              UDP(sport=333, dport=8138) /
588              BIFT(sd=1, set=0, bsl=2, ttl=255) /
589              BIER(length=BIERLength.BIER_LEN_256, BFRID=99) /
590              IP(src="1.1.1.1", dst="232.1.1.1") /
591              UDP(sport=1234, dport=1234) /
592              Raw())
593
594         rx = self.send_and_expect(self.pg0, [p], self.pg1)
595
596
597 if __name__ == '__main__':
598     unittest.main(testRunner=VppTestRunner)