BIER in non-MPLS netowrks
[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     def test_bier_e2e(self):
339         """ BIER end-to-end """
340
341         #
342         # Add a BIER table for sub-domain 0, set 0, and BSL 256
343         #
344         bti = VppBierTableID(0, 0, BIERLength.BIER_LEN_256)
345         bt = VppBierTable(self, bti, 77)
346         bt.add_vpp_config()
347
348         #
349         # Impostion Sets bit string 101010101....
350         #  sender 333
351         #
352         bi = VppBierImp(self, bti, 333, chr(0x5) * 32)
353         bi.add_vpp_config()
354
355         #
356         # Add a multicast route that will forward into the BIER doamin
357         #
358         route_ing_232_1_1_1 = VppIpMRoute(
359             self,
360             "0.0.0.0",
361             "232.1.1.1", 32,
362             MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
363             paths=[VppMRoutePath(self.pg0.sw_if_index,
364                                  MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
365                    VppMRoutePath(0xffffffff,
366                                  MRouteItfFlags.MFIB_ITF_FLAG_FORWARD,
367                                  proto=DpoProto.DPO_PROTO_BIER,
368                                  bier_imp=bi.bi_index)])
369         route_ing_232_1_1_1.add_vpp_config()
370
371         #
372         # disposition table 8
373         #
374         bdt = VppBierDispTable(self, 8)
375         bdt.add_vpp_config()
376
377         #
378         # BIER route in table that's for-us, resolving through
379         # disp table 8.
380         #
381         bier_route_1 = VppBierRoute(self, bti, 1,
382                                     [VppRoutePath("0.0.0.0",
383                                                   0xffffffff,
384                                                   nh_table_id=8)])
385         bier_route_1.add_vpp_config()
386
387         #
388         # An entry in the disposition table for sender 333
389         #  lookup in VRF 10
390         #
391         bier_de_1 = VppBierDispEntry(self, bdt.id, 333,
392                                      BIER_HDR_PAYLOAD.BIER_HDR_PROTO_IPV4,
393                                      "0.0.0.0", 10, rpf_id=8192)
394         bier_de_1.add_vpp_config()
395
396         #
397         # Add a multicast route that will forward the traffic
398         # post-disposition
399         #
400         route_eg_232_1_1_1 = VppIpMRoute(
401             self,
402             "0.0.0.0",
403             "232.1.1.1", 32,
404             MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
405             table_id=10,
406             paths=[VppMRoutePath(self.pg1.sw_if_index,
407                                  MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
408         route_eg_232_1_1_1.add_vpp_config()
409         route_eg_232_1_1_1.update_rpf_id(8192)
410
411         #
412         # inject a packet in VRF-0. We expect it to be BIER encapped,
413         # replicated, then hit the disposition and be forwarded
414         # out of VRF 10, i.e. on pg1
415         #
416         p = (Ether(dst=self.pg0.local_mac,
417                    src=self.pg0.remote_mac) /
418              IP(src="1.1.1.1", dst="232.1.1.1") /
419              UDP(sport=1234, dport=1234))
420
421         rx = self.send_and_expect(self.pg0, p*65, self.pg1)
422
423         #
424         # should be IP
425         #
426         self.assertEqual(rx[0][IP].src, "1.1.1.1")
427         self.assertEqual(rx[0][IP].dst, "232.1.1.1")
428
429     def test_bier_head_o_udp(self):
430         """BIER head over UDP"""
431
432         #
433         # Add a BIER table for sub-domain 1, set 0, and BSL 256
434         #
435         bti = VppBierTableID(1, 0, BIERLength.BIER_LEN_256)
436         bt = VppBierTable(self, bti, 77)
437         bt.add_vpp_config()
438
439         #
440         # 1 bit positions via 1 next hops
441         #
442         nh1 = "10.0.0.1"
443         ip_route = VppIpRoute(self, nh1, 32,
444                               [VppRoutePath(self.pg1.remote_ip4,
445                                             self.pg1.sw_if_index,
446                                             labels=[2001])])
447         ip_route.add_vpp_config()
448
449         udp_encap = VppUdpEncap(self, 4,
450                                 self.pg0.local_ip4,
451                                 nh1,
452                                 330, 8138)
453         udp_encap.add_vpp_config()
454
455         bier_route = VppBierRoute(self, bti, 1,
456                                   [VppRoutePath("0.0.0.0",
457                                                 0xFFFFFFFF,
458                                                 is_udp_encap=1,
459                                                 next_hop_id=4)])
460         bier_route.add_vpp_config()
461
462         #
463         # An imposition object with all bit-positions set
464         #
465         bi = VppBierImp(self, bti, 333, chr(0xff) * 32)
466         bi.add_vpp_config()
467
468         #
469         # Add a multicast route that will forward into the BIER doamin
470         #
471         route_ing_232_1_1_1 = VppIpMRoute(
472             self,
473             "0.0.0.0",
474             "232.1.1.1", 32,
475             MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
476             paths=[VppMRoutePath(self.pg0.sw_if_index,
477                                  MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
478                    VppMRoutePath(0xffffffff,
479                                  MRouteItfFlags.MFIB_ITF_FLAG_FORWARD,
480                                  proto=DpoProto.DPO_PROTO_BIER,
481                                  bier_imp=bi.bi_index)])
482         route_ing_232_1_1_1.add_vpp_config()
483
484         #
485         # inject a packet an IP. We expect it to be BIER and UDP encapped,
486         #
487         p = (Ether(dst=self.pg0.local_mac,
488                    src=self.pg0.remote_mac) /
489              IP(src="1.1.1.1", dst="232.1.1.1") /
490              UDP(sport=1234, dport=1234))
491
492         self.pg0.add_stream([p])
493         self.pg_enable_capture(self.pg_interfaces)
494         self.pg_start()
495
496         rx = self.pg1.get_capture(1)
497
498         #
499         # Encap Stack is, eth, IP, UDP, BIFT, BIER
500         #
501         self.assertEqual(rx[0][IP].src, self.pg0.local_ip4)
502         self.assertEqual(rx[0][IP].dst, nh1)
503         self.assertEqual(rx[0][UDP].sport, 330)
504         self.assertEqual(rx[0][UDP].dport, 8138)
505         self.assertEqual(rx[0][BIFT].bsl, 2)
506         self.assertEqual(rx[0][BIFT].sd, 1)
507         self.assertEqual(rx[0][BIFT].set, 0)
508         self.assertEqual(rx[0][BIFT].ttl, 64)
509         self.assertEqual(rx[0][BIER].length, 2)
510
511     def test_bier_tail_o_udp(self):
512         """BIER Tail over UDP"""
513
514         #
515         # Add a BIER table for sub-domain 0, set 0, and BSL 256
516         #
517         bti = VppBierTableID(1, 0, BIERLength.BIER_LEN_256)
518         bt = VppBierTable(self, bti, MPLS_LABEL_INVALID)
519         bt.add_vpp_config()
520
521         #
522         # disposition table
523         #
524         bdt = VppBierDispTable(self, 8)
525         bdt.add_vpp_config()
526
527         #
528         # BIER route in table that's for-us
529         #
530         bier_route_1 = VppBierRoute(self, bti, 1,
531                                     [VppRoutePath("0.0.0.0",
532                                                   0xffffffff,
533                                                   nh_table_id=8)])
534         bier_route_1.add_vpp_config()
535
536         #
537         # An entry in the disposition table
538         #
539         bier_de_1 = VppBierDispEntry(self, bdt.id, 99,
540                                      BIER_HDR_PAYLOAD.BIER_HDR_PROTO_IPV4,
541                                      "0.0.0.0", 0, rpf_id=8192)
542         bier_de_1.add_vpp_config()
543
544         #
545         # A multicast route to forward post BIER disposition
546         #
547         route_eg_232_1_1_1 = VppIpMRoute(
548             self,
549             "0.0.0.0",
550             "232.1.1.1", 32,
551             MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
552             paths=[VppMRoutePath(self.pg1.sw_if_index,
553                                  MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
554         route_eg_232_1_1_1.add_vpp_config()
555         route_eg_232_1_1_1.update_rpf_id(8192)
556
557         #
558         # A packet with all bits set gets spat out to BP:1
559         #
560         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
561              IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
562              UDP(sport=333, dport=8138) /
563              BIFT(sd=1, set=0, bsl=2, ttl=255) /
564              BIER(length=BIERLength.BIER_LEN_256, BFRID=99) /
565              IP(src="1.1.1.1", dst="232.1.1.1") /
566              UDP(sport=1234, dport=1234) /
567              Raw())
568
569         rx = self.send_and_expect(self.pg0, [p], self.pg1)
570
571
572 if __name__ == '__main__':
573     unittest.main(testRunner=VppTestRunner)