IP route update fix when multipath and drop set
[vpp.git] / test / vpp_ip_route.py
1 """
2   IP Routes
3
4   object abstractions for representing IP routes in VPP
5 """
6
7 from vpp_object import *
8 from socket import inet_pton, inet_ntop, AF_INET, AF_INET6
9
10 # from vnet/vnet/mpls/mpls_types.h
11 MPLS_IETF_MAX_LABEL = 0xfffff
12 MPLS_LABEL_INVALID = MPLS_IETF_MAX_LABEL + 1
13
14
15 class MRouteItfFlags:
16     MFIB_ITF_FLAG_NONE = 0
17     MFIB_ITF_FLAG_NEGATE_SIGNAL = 1
18     MFIB_ITF_FLAG_ACCEPT = 2
19     MFIB_ITF_FLAG_FORWARD = 4
20     MFIB_ITF_FLAG_SIGNAL_PRESENT = 8
21     MFIB_ITF_FLAG_INTERNAL_COPY = 16
22
23
24 class MRouteEntryFlags:
25     MFIB_ENTRY_FLAG_NONE = 0
26     MFIB_ENTRY_FLAG_SIGNAL = 1
27     MFIB_ENTRY_FLAG_DROP = 2
28     MFIB_ENTRY_FLAG_CONNECTED = 4
29     MFIB_ENTRY_FLAG_INHERIT_ACCEPT = 8
30
31
32 class DpoProto:
33     DPO_PROTO_IP4 = 0
34     DPO_PROTO_IP6 = 1
35     DPO_PROTO_MPLS = 2
36     DPO_PROTO_ETHERNET = 3
37     DPO_PROTO_BIER = 4
38     DPO_PROTO_NSH = 5
39
40
41 class MplsLspMode:
42     PIPE = 0
43     UNIFORM = 1
44
45
46 def find_route(test, ip_addr, len, table_id=0, inet=AF_INET):
47     if inet == AF_INET:
48         s = 4
49         routes = test.vapi.ip_fib_dump()
50     else:
51         s = 16
52         routes = test.vapi.ip6_fib_dump()
53
54     route_addr = inet_pton(inet, ip_addr)
55     for e in routes:
56         if route_addr == e.address[:s] \
57            and len == e.address_length \
58            and table_id == e.table_id:
59             return True
60     return False
61
62
63 def find_mroute(test, grp_addr, src_addr, grp_addr_len,
64                 table_id=0, inet=AF_INET):
65     if inet == AF_INET:
66         s = 4
67         routes = test.vapi.ip_mfib_dump()
68     else:
69         s = 16
70         routes = test.vapi.ip6_mfib_dump()
71     gaddr = inet_pton(inet, grp_addr)
72     saddr = inet_pton(inet, src_addr)
73     for e in routes:
74         if gaddr == e.grp_address[:s] \
75            and grp_addr_len == e.address_length \
76            and saddr == e.src_address[:s] \
77            and table_id == e.table_id:
78             return True
79     return False
80
81
82 class VppIpTable(VppObject):
83
84     def __init__(self,
85                  test,
86                  table_id,
87                  is_ip6=0):
88         self._test = test
89         self.table_id = table_id
90         self.is_ip6 = is_ip6
91
92     def add_vpp_config(self):
93         self._test.vapi.ip_table_add_del(
94             self.table_id,
95             is_ipv6=self.is_ip6,
96             is_add=1)
97         self._test.registry.register(self, self._test.logger)
98
99     def remove_vpp_config(self):
100         self._test.vapi.ip_table_add_del(
101             self.table_id,
102             is_ipv6=self.is_ip6,
103             is_add=0)
104
105     def query_vpp_config(self):
106         # find the default route
107         return find_route(self._test,
108                           "::" if self.is_ip6 else "0.0.0.0",
109                           0,
110                           self.table_id,
111                           inet=AF_INET6 if self.is_ip6 == 1 else AF_INET)
112
113     def __str__(self):
114         return self.object_id()
115
116     def object_id(self):
117         return ("table-%s-%d" %
118                 ("v6" if self.is_ip6 == 1 else "v4",
119                  self.table_id))
120
121
122 class VppMplsLabel(object):
123     def __init__(self, value, mode=MplsLspMode.PIPE, ttl=64, exp=0):
124         self.value = value
125         self.mode = mode
126         self.ttl = ttl
127         self.exp = exp
128
129     def encode(self):
130         is_uniform = 0 if self.mode is MplsLspMode.PIPE else 1
131         return {'label': self.value,
132                 'ttl': self.ttl,
133                 'exp': self.exp,
134                 'is_uniform': is_uniform}
135
136
137 class VppRoutePath(object):
138
139     def __init__(
140             self,
141             nh_addr,
142             nh_sw_if_index,
143             nh_table_id=0,
144             labels=[],
145             nh_via_label=MPLS_LABEL_INVALID,
146             rpf_id=0,
147             is_interface_rx=0,
148             is_resolve_host=0,
149             is_resolve_attached=0,
150             is_source_lookup=0,
151             is_udp_encap=0,
152             is_dvr=0,
153             next_hop_id=0xffffffff,
154             proto=DpoProto.DPO_PROTO_IP4):
155         self.nh_itf = nh_sw_if_index
156         self.nh_table_id = nh_table_id
157         self.nh_via_label = nh_via_label
158         self.nh_labels = labels
159         self.weight = 1
160         self.rpf_id = rpf_id
161         self.proto = proto
162         if self.proto is DpoProto.DPO_PROTO_IP6:
163             self.nh_addr = inet_pton(AF_INET6, nh_addr)
164         elif self.proto is DpoProto.DPO_PROTO_IP4:
165             self.nh_addr = inet_pton(AF_INET, nh_addr)
166         else:
167             self.nh_addr = inet_pton(AF_INET6, "::")
168         self.is_resolve_host = is_resolve_host
169         self.is_resolve_attached = is_resolve_attached
170         self.is_interface_rx = is_interface_rx
171         self.is_source_lookup = is_source_lookup
172         self.is_rpf_id = 0
173         if rpf_id != 0:
174             self.is_rpf_id = 1
175             self.nh_itf = rpf_id
176         self.is_udp_encap = is_udp_encap
177         self.next_hop_id = next_hop_id
178         self.is_dvr = is_dvr
179
180     def encode_labels(self):
181         lstack = []
182         for l in self.nh_labels:
183             if type(l) == VppMplsLabel:
184                 lstack.append(l.encode())
185             else:
186                 lstack.append({'label': l,
187                                'ttl': 255})
188         return lstack
189
190     def encode(self):
191         return {'next_hop': self.nh_addr,
192                 'weight': 1,
193                 'afi': 0,
194                 'preference': 0,
195                 'table_id': self.nh_table_id,
196                 'next_hop_id': self.next_hop_id,
197                 'sw_if_index': self.nh_itf,
198                 'afi': self.proto,
199                 'is_udp_encap': self.is_udp_encap,
200                 'n_labels': len(self.nh_labels),
201                 'label_stack': self.encode_labels()}
202
203
204 class VppMRoutePath(VppRoutePath):
205
206     def __init__(self, nh_sw_if_index, flags,
207                  nh=None,
208                  proto=DpoProto.DPO_PROTO_IP4,
209                  bier_imp=0):
210         if not nh:
211             nh = "::" if proto is DpoProto.DPO_PROTO_IP6 else "0.0.0.0"
212         super(VppMRoutePath, self).__init__(nh,
213                                             nh_sw_if_index,
214                                             proto=proto)
215         self.nh_i_flags = flags
216         self.bier_imp = bier_imp
217
218
219 class VppIpRoute(VppObject):
220     """
221     IP Route
222     """
223
224     def __init__(self, test, dest_addr,
225                  dest_addr_len, paths, table_id=0, is_ip6=0, is_local=0,
226                  is_unreach=0, is_prohibit=0, is_drop=0):
227         self._test = test
228         self.paths = paths
229         self.dest_addr_len = dest_addr_len
230         self.table_id = table_id
231         self.is_ip6 = is_ip6
232         self.is_local = is_local
233         self.is_unreach = is_unreach
234         self.is_prohibit = is_prohibit
235         self.is_drop = is_drop
236         self.dest_addr_p = dest_addr
237         if is_ip6:
238             self.dest_addr = inet_pton(AF_INET6, dest_addr)
239         else:
240             self.dest_addr = inet_pton(AF_INET, dest_addr)
241
242     def modify(self, paths, is_local=0,
243                is_unreach=0, is_prohibit=0):
244         self.paths = paths
245         self.is_local = is_local
246         self.is_unreach = is_unreach
247         self.is_prohibit = is_prohibit
248
249     def add_vpp_config(self):
250         if self.is_local or self.is_unreach or \
251            self.is_prohibit or self.is_drop:
252             self._test.vapi.ip_add_del_route(
253                 self.dest_addr,
254                 self.dest_addr_len,
255                 inet_pton(AF_INET6, "::"),
256                 0xffffffff,
257                 is_local=self.is_local,
258                 is_unreach=self.is_unreach,
259                 is_prohibit=self.is_prohibit,
260                 is_drop=self.is_drop,
261                 table_id=self.table_id,
262                 is_ipv6=self.is_ip6)
263         else:
264             for path in self.paths:
265                 lstack = path.encode_labels()
266
267                 self._test.vapi.ip_add_del_route(
268                     self.dest_addr,
269                     self.dest_addr_len,
270                     path.nh_addr,
271                     path.nh_itf,
272                     table_id=self.table_id,
273                     next_hop_out_label_stack=lstack,
274                     next_hop_n_out_labels=len(lstack),
275                     next_hop_via_label=path.nh_via_label,
276                     next_hop_table_id=path.nh_table_id,
277                     next_hop_id=path.next_hop_id,
278                     is_ipv6=self.is_ip6,
279                     is_dvr=path.is_dvr,
280                     is_resolve_host=path.is_resolve_host,
281                     is_resolve_attached=path.is_resolve_attached,
282                     is_source_lookup=path.is_source_lookup,
283                     is_udp_encap=path.is_udp_encap,
284                     is_multipath=1 if len(self.paths) > 1 else 0)
285         self._test.registry.register(self, self._test.logger)
286
287     def remove_vpp_config(self):
288         if self.is_local or self.is_unreach or \
289            self.is_prohibit or self.is_drop:
290             self._test.vapi.ip_add_del_route(
291                 self.dest_addr,
292                 self.dest_addr_len,
293                 inet_pton(AF_INET6, "::"),
294                 0xffffffff,
295                 is_local=self.is_local,
296                 is_unreach=self.is_unreach,
297                 is_prohibit=self.is_prohibit,
298                 is_add=0,
299                 table_id=self.table_id,
300                 is_ipv6=self.is_ip6)
301         else:
302             for path in self.paths:
303                 self._test.vapi.ip_add_del_route(
304                     self.dest_addr,
305                     self.dest_addr_len,
306                     path.nh_addr,
307                     path.nh_itf,
308                     table_id=self.table_id,
309                     next_hop_table_id=path.nh_table_id,
310                     next_hop_via_label=path.nh_via_label,
311                     next_hop_id=path.next_hop_id,
312                     is_add=0,
313                     is_udp_encap=path.is_udp_encap,
314                     is_ipv6=self.is_ip6,
315                     is_dvr=path.is_dvr)
316
317     def query_vpp_config(self):
318         return find_route(self._test,
319                           self.dest_addr_p,
320                           self.dest_addr_len,
321                           self.table_id,
322                           inet=AF_INET6 if self.is_ip6 == 1 else AF_INET)
323
324     def __str__(self):
325         return self.object_id()
326
327     def object_id(self):
328         return ("%d:%s/%d"
329                 % (self.table_id,
330                    self.dest_addr_p,
331                    self.dest_addr_len))
332
333
334 class VppIpMRoute(VppObject):
335     """
336     IP Multicast Route
337     """
338
339     def __init__(self, test, src_addr, grp_addr,
340                  grp_addr_len, e_flags, paths, table_id=0,
341                  rpf_id=0, is_ip6=0):
342         self._test = test
343         self.paths = paths
344         self.grp_addr_len = grp_addr_len
345         self.table_id = table_id
346         self.e_flags = e_flags
347         self.is_ip6 = is_ip6
348         self.rpf_id = rpf_id
349
350         self.grp_addr_p = grp_addr
351         self.src_addr_p = src_addr
352         if is_ip6:
353             self.grp_addr = inet_pton(AF_INET6, grp_addr)
354             self.src_addr = inet_pton(AF_INET6, src_addr)
355         else:
356             self.grp_addr = inet_pton(AF_INET, grp_addr)
357             self.src_addr = inet_pton(AF_INET, src_addr)
358
359     def add_vpp_config(self):
360         for path in self.paths:
361             self._test.vapi.ip_mroute_add_del(self.src_addr,
362                                               self.grp_addr,
363                                               self.grp_addr_len,
364                                               self.e_flags,
365                                               path.proto,
366                                               path.nh_itf,
367                                               path.nh_addr,
368                                               path.nh_i_flags,
369                                               bier_imp=path.bier_imp,
370                                               rpf_id=self.rpf_id,
371                                               table_id=self.table_id,
372                                               is_ipv6=self.is_ip6)
373         self._test.registry.register(self, self._test.logger)
374
375     def remove_vpp_config(self):
376         for path in self.paths:
377             self._test.vapi.ip_mroute_add_del(self.src_addr,
378                                               self.grp_addr,
379                                               self.grp_addr_len,
380                                               self.e_flags,
381                                               path.proto,
382                                               path.nh_itf,
383                                               path.nh_addr,
384                                               path.nh_i_flags,
385                                               table_id=self.table_id,
386                                               bier_imp=path.bier_imp,
387                                               is_add=0,
388                                               is_ipv6=self.is_ip6)
389
390     def update_entry_flags(self, flags):
391         self.e_flags = flags
392         self._test.vapi.ip_mroute_add_del(self.src_addr,
393                                           self.grp_addr,
394                                           self.grp_addr_len,
395                                           self.e_flags,
396                                           0,
397                                           0xffffffff,
398                                           "",
399                                           0,
400                                           table_id=self.table_id,
401                                           is_ipv6=self.is_ip6)
402
403     def update_rpf_id(self, rpf_id):
404         self.rpf_id = rpf_id
405         self._test.vapi.ip_mroute_add_del(self.src_addr,
406                                           self.grp_addr,
407                                           self.grp_addr_len,
408                                           self.e_flags,
409                                           0,
410                                           0xffffffff,
411                                           "",
412                                           0,
413                                           rpf_id=self.rpf_id,
414                                           table_id=self.table_id,
415                                           is_ipv6=self.is_ip6)
416
417     def update_path_flags(self, itf, flags):
418         for path in self.paths:
419             if path.nh_itf == itf:
420                 path.nh_i_flags = flags
421                 break
422         self._test.vapi.ip_mroute_add_del(self.src_addr,
423                                           self.grp_addr,
424                                           self.grp_addr_len,
425                                           self.e_flags,
426                                           path.proto,
427                                           path.nh_itf,
428                                           path.nh_addr,
429                                           path.nh_i_flags,
430                                           table_id=self.table_id,
431                                           is_ipv6=self.is_ip6)
432
433     def query_vpp_config(self):
434         return find_mroute(self._test,
435                            self.grp_addr_p,
436                            self.src_addr_p,
437                            self.grp_addr_len,
438                            self.table_id,
439                            inet=AF_INET6 if self.is_ip6 == 1 else AF_INET)
440
441     def __str__(self):
442         return self.object_id()
443
444     def object_id(self):
445         if self.is_ip6:
446             return ("%d:(%s,%s/%d)"
447                     % (self.table_id,
448                        inet_ntop(AF_INET6, self.src_addr),
449                        inet_ntop(AF_INET6, self.grp_addr),
450                        self.grp_addr_len))
451         else:
452             return ("%d:(%s,%s/%d)"
453                     % (self.table_id,
454                        inet_ntop(AF_INET, self.src_addr),
455                        inet_ntop(AF_INET, self.grp_addr),
456                        self.grp_addr_len))
457
458
459 class VppMFibSignal(object):
460     def __init__(self, test, route, interface, packet):
461         self.route = route
462         self.interface = interface
463         self.packet = packet
464         self.test = test
465
466     def compare(self, signal):
467         self.test.assertEqual(self.interface, signal.sw_if_index)
468         self.test.assertEqual(self.route.table_id, signal.table_id)
469         self.test.assertEqual(self.route.grp_addr_len,
470                               signal.grp_address_len)
471         for i in range(self.route.grp_addr_len / 8):
472             self.test.assertEqual(self.route.grp_addr[i],
473                                   signal.grp_address[i])
474         if (self.route.grp_addr_len > 32):
475             for i in range(4):
476                 self.test.assertEqual(self.route.src_addr[i],
477                                       signal.src_address[i])
478
479
480 class VppMplsIpBind(VppObject):
481     """
482     MPLS to IP Binding
483     """
484
485     def __init__(self, test, local_label, dest_addr, dest_addr_len,
486                  table_id=0, ip_table_id=0, is_ip6=0):
487         self._test = test
488         self.dest_addr_len = dest_addr_len
489         self.dest_addr = dest_addr
490         self.local_label = local_label
491         self.table_id = table_id
492         self.ip_table_id = ip_table_id
493         self.is_ip6 = is_ip6
494         if is_ip6:
495             self.dest_addrn = inet_pton(AF_INET6, dest_addr)
496         else:
497             self.dest_addrn = inet_pton(AF_INET, dest_addr)
498
499     def add_vpp_config(self):
500         self._test.vapi.mpls_ip_bind_unbind(self.local_label,
501                                             self.dest_addrn,
502                                             self.dest_addr_len,
503                                             table_id=self.table_id,
504                                             ip_table_id=self.ip_table_id,
505                                             is_ip4=(self.is_ip6 == 0))
506         self._test.registry.register(self, self._test.logger)
507
508     def remove_vpp_config(self):
509         self._test.vapi.mpls_ip_bind_unbind(self.local_label,
510                                             self.dest_addrn,
511                                             self.dest_addr_len,
512                                             table_id=self.table_id,
513                                             ip_table_id=self.ip_table_id,
514                                             is_bind=0,
515                                             is_ip4=(self.is_ip6 == 0))
516
517     def query_vpp_config(self):
518         dump = self._test.vapi.mpls_fib_dump()
519         for e in dump:
520             if self.local_label == e.label \
521                and self.table_id == e.table_id:
522                 return True
523         return False
524
525     def __str__(self):
526         return self.object_id()
527
528     def object_id(self):
529         return ("%d:%s binds %d:%s/%d"
530                 % (self.table_id,
531                    self.local_label,
532                    self.ip_table_id,
533                    self.dest_addr,
534                    self.dest_addr_len))
535
536
537 class VppMplsTable(VppObject):
538
539     def __init__(self,
540                  test,
541                  table_id):
542         self._test = test
543         self.table_id = table_id
544
545     def add_vpp_config(self):
546         self._test.vapi.mpls_table_add_del(
547             self.table_id,
548             is_add=1)
549         self._test.registry.register(self, self._test.logger)
550
551     def remove_vpp_config(self):
552         self._test.vapi.mpls_table_add_del(
553             self.table_id,
554             is_add=0)
555
556     def query_vpp_config(self):
557         # find the default route
558         dump = self._test.vapi.mpls_fib_dump()
559         if len(dump):
560             return True
561         return False
562
563     def __str__(self):
564         return self.object_id()
565
566     def object_id(self):
567         return ("table-mpls-%d" % (self.table_id))
568
569
570 class VppMplsRoute(VppObject):
571     """
572     MPLS Route/LSP
573     """
574
575     def __init__(self, test, local_label, eos_bit, paths, table_id=0,
576                  is_multicast=0):
577         self._test = test
578         self.paths = paths
579         self.local_label = local_label
580         self.eos_bit = eos_bit
581         self.table_id = table_id
582         self.is_multicast = is_multicast
583
584     def add_vpp_config(self):
585         is_multipath = len(self.paths) > 1
586         for path in self.paths:
587             lstack = path.encode_labels()
588
589             self._test.vapi.mpls_route_add_del(
590                 self.local_label,
591                 self.eos_bit,
592                 path.proto,
593                 path.nh_addr,
594                 path.nh_itf,
595                 is_multicast=self.is_multicast,
596                 is_multipath=is_multipath,
597                 table_id=self.table_id,
598                 is_interface_rx=path.is_interface_rx,
599                 is_rpf_id=path.is_rpf_id,
600                 next_hop_out_label_stack=lstack,
601                 next_hop_n_out_labels=len(lstack),
602                 next_hop_via_label=path.nh_via_label,
603                 next_hop_table_id=path.nh_table_id)
604         self._test.registry.register(self, self._test.logger)
605
606     def remove_vpp_config(self):
607         for path in self.paths:
608             self._test.vapi.mpls_route_add_del(self.local_label,
609                                                self.eos_bit,
610                                                path.proto,
611                                                path.nh_addr,
612                                                path.nh_itf,
613                                                is_rpf_id=path.is_rpf_id,
614                                                table_id=self.table_id,
615                                                is_add=0)
616
617     def query_vpp_config(self):
618         dump = self._test.vapi.mpls_fib_dump()
619         for e in dump:
620             if self.local_label == e.label \
621                and self.eos_bit == e.eos_bit \
622                and self.table_id == e.table_id:
623                 return True
624         return False
625
626     def __str__(self):
627         return self.object_id()
628
629     def object_id(self):
630         return ("%d:%s/%d"
631                 % (self.table_id,
632                    self.local_label,
633                    20+self.eos_bit))