tests: set object_id for routes.
[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 VppObject
8 from socket import inet_pton, inet_ntop, AF_INET, AF_INET6
9 from vpp_ip import DpoProto, VppIpPrefix, INVALID_INDEX, VppIpAddressUnion, \
10     VppIpMPrefix
11 from ipaddress import ip_address, IPv4Network, IPv6Network
12
13 # from vnet/vnet/mpls/mpls_types.h
14 MPLS_IETF_MAX_LABEL = 0xfffff
15 MPLS_LABEL_INVALID = MPLS_IETF_MAX_LABEL + 1
16
17 try:
18     text_type = unicode
19 except NameError:
20     text_type = str
21
22
23 class MRouteItfFlags:
24     MFIB_ITF_FLAG_NONE = 0
25     MFIB_ITF_FLAG_NEGATE_SIGNAL = 1
26     MFIB_ITF_FLAG_ACCEPT = 2
27     MFIB_ITF_FLAG_FORWARD = 4
28     MFIB_ITF_FLAG_SIGNAL_PRESENT = 8
29     MFIB_ITF_FLAG_INTERNAL_COPY = 16
30
31
32 class MRouteEntryFlags:
33     MFIB_ENTRY_FLAG_NONE = 0
34     MFIB_ENTRY_FLAG_SIGNAL = 1
35     MFIB_ENTRY_FLAG_DROP = 2
36     MFIB_ENTRY_FLAG_CONNECTED = 4
37     MFIB_ENTRY_FLAG_INHERIT_ACCEPT = 8
38
39
40 class FibPathProto:
41     FIB_PATH_NH_PROTO_IP4 = 0
42     FIB_PATH_NH_PROTO_IP6 = 1
43     FIB_PATH_NH_PROTO_MPLS = 2
44     FIB_PATH_NH_PROTO_ETHERNET = 3
45     FIB_PATH_NH_PROTO_BIER = 4
46     FIB_PATH_NH_PROTO_NSH = 5
47
48
49 class FibPathType:
50     FIB_PATH_TYPE_NORMAL = 0
51     FIB_PATH_TYPE_LOCAL = 1
52     FIB_PATH_TYPE_DROP = 2
53     FIB_PATH_TYPE_UDP_ENCAP = 3
54     FIB_PATH_TYPE_BIER_IMP = 4
55     FIB_PATH_TYPE_ICMP_UNREACH = 5
56     FIB_PATH_TYPE_ICMP_PROHIBIT = 6
57     FIB_PATH_TYPE_SOURCE_LOOKUP = 7
58     FIB_PATH_TYPE_DVR = 8
59     FIB_PATH_TYPE_INTERFACE_RX = 9
60     FIB_PATH_TYPE_CLASSIFY = 10
61
62
63 class FibPathFlags:
64     FIB_PATH_FLAG_NONE = 0
65     FIB_PATH_FLAG_RESOLVE_VIA_ATTACHED = 1
66     FIB_PATH_FLAG_RESOLVE_VIA_HOST = 2
67
68
69 class MplsLspMode:
70     PIPE = 0
71     UNIFORM = 1
72
73
74 def ip_to_dpo_proto(addr):
75     if addr.version == 6:
76         return DpoProto.DPO_PROTO_IP6
77     else:
78         return DpoProto.DPO_PROTO_IP4
79
80
81 def address_proto(ip_addr):
82     if ip_addr.ip_addr.version is 4:
83         return FibPathProto.FIB_PATH_NH_PROTO_IP4
84     else:
85         return FibPathProto.FIB_PATH_NH_PROTO_IP6
86
87
88 def find_route(test, addr, len, table_id=0):
89     ip_addr = ip_address(text_type(addr))
90
91     if 4 is ip_addr.version:
92         routes = test.vapi.ip_route_dump(table_id, False)
93         prefix = IPv4Network("%s/%d" % (text_type(addr), len), strict=False)
94     else:
95         routes = test.vapi.ip_route_dump(table_id, True)
96         prefix = IPv6Network("%s/%d" % (text_type(addr), len), strict=False)
97
98     for e in routes:
99         if table_id == e.route.table_id \
100            and prefix == e.route.prefix:
101             return True
102     return False
103
104
105 def find_mroute(test, grp_addr, src_addr, grp_addr_len,
106                 table_id=0):
107     ip_mprefix = VppIpMPrefix(text_type(src_addr),
108                               text_type(grp_addr),
109                               grp_addr_len)
110
111     if 4 is ip_mprefix.version:
112         routes = test.vapi.ip_mroute_dump(table_id, False)
113     else:
114         routes = test.vapi.ip_mroute_dump(table_id, True)
115
116     for e in routes:
117         if table_id == e.route.table_id and ip_mprefix == e.route.prefix:
118             return True
119     return False
120
121
122 def find_mpls_route(test, table_id, label, eos_bit, paths=None):
123     dump = test.vapi.mpls_route_dump(table_id)
124     for e in dump:
125         if label == e.mr_route.mr_label \
126            and eos_bit == e.mr_route.mr_eos \
127            and table_id == e.mr_route.mr_table_id:
128             if not paths:
129                 return True
130             else:
131                 if (len(paths) != len(e.mr_route.mr_paths)):
132                     return False
133                 for i in range(len(paths)):
134                     if (paths[i] != e.mr_route.mr_paths[i]):
135                         return False
136                 return True
137     return False
138
139
140 def fib_interface_ip_prefix(test, address, length, sw_if_index):
141     ip_addr = ip_address(text_type(address))
142
143     if 4 is ip_addr.version:
144         addrs = test.vapi.ip_address_dump(sw_if_index)
145         prefix = IPv4Network("%s/%d" % (text_type(address), length),
146                              strict=False)
147     else:
148         addrs = test.vapi.ip_address_dump(sw_if_index, is_ipv6=1)
149         prefix = IPv6Network("%s/%d" % (text_type(address), length),
150                              strict=False)
151
152     # TODO: refactor this to VppIpPrefix.__eq__
153     for a in addrs:
154         if a.sw_if_index == sw_if_index and \
155            a.prefix == prefix:
156             return True
157     return False
158
159
160 class VppIpTable(VppObject):
161
162     def __init__(self,
163                  test,
164                  table_id,
165                  is_ip6=0):
166         self._test = test
167         self.table_id = table_id
168         self.is_ip6 = is_ip6
169
170     def add_vpp_config(self):
171         self._test.vapi.ip_table_add_del(is_ipv6=self.is_ip6, is_add=1,
172                                          table_id=self.table_id)
173         self._test.registry.register(self, self._test.logger)
174
175     def remove_vpp_config(self):
176         self._test.vapi.ip_table_add_del(is_ipv6=self.is_ip6, is_add=0,
177                                          table_id=self.table_id)
178
179     def query_vpp_config(self):
180         if self.table_id == 0:
181             # the default table always exists
182             return False
183         # find the default route
184         return find_route(self._test,
185                           "::" if self.is_ip6 else "0.0.0.0",
186                           0,
187                           self.table_id)
188
189     def object_id(self):
190         return ("table-%s-%d" %
191                 ("v6" if self.is_ip6 == 1 else "v4",
192                  self.table_id))
193
194
195 class VppIpInterfaceAddress(VppObject):
196
197     def __init__(self, test, intf, addr, len):
198         self._test = test
199         self.intf = intf
200         self.prefix = VppIpPrefix(addr, len)
201
202     def add_vpp_config(self):
203         self._test.vapi.sw_interface_add_del_address(
204             sw_if_index=self.intf.sw_if_index, address=self.prefix.bytes,
205             address_length=self.prefix.length, is_ipv6=self.prefix.is_ip6,
206             is_add=1)
207         self._test.registry.register(self, self._test.logger)
208
209     def remove_vpp_config(self):
210         self._test.vapi.sw_interface_add_del_address(
211             sw_if_index=self.intf.sw_if_index, address=self.prefix.bytes,
212             address_length=self.prefix.length, is_ipv6=self.prefix.is_ip6,
213             is_add=0)
214
215     def query_vpp_config(self):
216         return fib_interface_ip_prefix(self._test,
217                                        self.prefix.address,
218                                        self.prefix.length,
219                                        self.intf.sw_if_index)
220
221     def object_id(self):
222         return "interface-ip-%s-%s" % (self.intf, self.prefix)
223
224
225 class VppIpInterfaceBind(VppObject):
226
227     def __init__(self, test, intf, table):
228         self._test = test
229         self.intf = intf
230         self.table = table
231
232     def add_vpp_config(self):
233         if self.table.is_ip6:
234             self.intf.set_table_ip6(self.table.table_id)
235         else:
236             self.intf.set_table_ip4(self.table.table_id)
237         self._test.registry.register(self, self._test.logger)
238
239     def remove_vpp_config(self):
240         if 0 == self.table.table_id:
241             return
242         if self.table.is_ip6:
243             self.intf.set_table_ip6(0)
244         else:
245             self.intf.set_table_ip4(0)
246
247     def query_vpp_config(self):
248         if 0 == self.table.table_id:
249             return False
250         return self._test.vapi.sw_interface_get_table(
251             self.intf.sw_if_index,
252             self.table.is_ip6).vrf_id == self.table.table_id
253
254     def object_id(self):
255         return "interface-bind-%s-%s" % (self.intf, self.table)
256
257
258 class VppMplsLabel(object):
259     def __init__(self, value, mode=MplsLspMode.PIPE, ttl=64, exp=0):
260         self.value = value
261         self.mode = mode
262         self.ttl = ttl
263         self.exp = exp
264
265     def encode(self):
266         is_uniform = 0 if self.mode is MplsLspMode.PIPE else 1
267         return {'label': self.value,
268                 'ttl': self.ttl,
269                 'exp': self.exp,
270                 'is_uniform': is_uniform}
271
272     def __eq__(self, other):
273         if isinstance(other, self.__class__):
274             return (self.value == other.value and
275                     self.ttl == other.ttl and
276                     self.exp == other.exp and
277                     self.mode == other.mode)
278         elif hasattr(other, 'label'):
279             return (self.value == other.label and
280                     self.ttl == other.ttl and
281                     self.exp == other.exp and
282                     (self.mode == MplsLspMode.UNIFORM) == other.is_uniform)
283         else:
284             return False
285
286     def __ne__(self, other):
287         return not (self == other)
288
289
290 class VppFibPathNextHop(object):
291     def __init__(self, addr,
292                  via_label=MPLS_LABEL_INVALID,
293                  next_hop_id=INVALID_INDEX):
294         self.addr = VppIpAddressUnion(addr)
295         self.via_label = via_label
296         self.obj_id = next_hop_id
297
298     def encode(self):
299         if self.via_label is not MPLS_LABEL_INVALID:
300             return {'via_label': self.via_label}
301         if self.obj_id is not INVALID_INDEX:
302             return {'obj_id': self.obj_id}
303         else:
304             return {'address': self.addr.encode()}
305
306     def proto(self):
307         if self.via_label is MPLS_LABEL_INVALID:
308             return address_proto(self.addr)
309         else:
310             return FibPathProto.FIB_PATH_NH_PROTO_MPLS
311
312     def __eq__(self, other):
313         if not isinstance(other, self.__class__):
314             # try the other instance's __eq__.
315             return NotImplemented
316         return (self.addr == other.addr and
317                 self.via_label == other.via_label and
318                 self.obj_id == other.obj_id)
319
320
321 class VppRoutePath(object):
322
323     def __init__(
324             self,
325             nh_addr,
326             nh_sw_if_index,
327             nh_table_id=0,
328             labels=[],
329             nh_via_label=MPLS_LABEL_INVALID,
330             rpf_id=0,
331             next_hop_id=INVALID_INDEX,
332             proto=None,
333             flags=FibPathFlags.FIB_PATH_FLAG_NONE,
334             type=FibPathType.FIB_PATH_TYPE_NORMAL):
335         self.nh_itf = nh_sw_if_index
336         self.nh_table_id = nh_table_id
337         self.nh_labels = labels
338         self.weight = 1
339         self.rpf_id = rpf_id
340         self.proto = proto
341         self.flags = flags
342         self.type = type
343         self.nh = VppFibPathNextHop(nh_addr, nh_via_label, next_hop_id)
344         if proto is None:
345             self.proto = self.nh.proto()
346         else:
347             self.proto = proto
348         self.next_hop_id = next_hop_id
349
350     def encode_labels(self):
351         lstack = []
352         for l in self.nh_labels:
353             if type(l) == VppMplsLabel:
354                 lstack.append(l.encode())
355             else:
356                 lstack.append({'label': l,
357                                'ttl': 255})
358         while (len(lstack) < 16):
359             lstack.append({})
360
361         return lstack
362
363     def encode(self):
364         return {'weight': 1,
365                 'preference': 0,
366                 'table_id': self.nh_table_id,
367                 'nh': self.nh.encode(),
368                 'next_hop_id': self.next_hop_id,
369                 'sw_if_index': self.nh_itf,
370                 'rpf_id': self.rpf_id,
371                 'proto': self.proto,
372                 'type': self.type,
373                 'flags': self.flags,
374                 'n_labels': len(self.nh_labels),
375                 'label_stack': self.encode_labels()}
376
377     def __eq__(self, other):
378         if isinstance(other, self.__class__):
379             return self.nh == other.nh
380         elif hasattr(other, 'sw_if_index'):
381             # vl_api_fib_path_t
382             if (len(self.nh_labels) != other.n_labels):
383                 return False
384             for i in range(len(self.nh_labels)):
385                 if (self.nh_labels[i] != other.label_stack[i]):
386                     return False
387             return self.nh_itf == other.sw_if_index
388         else:
389             return False
390
391     def __ne__(self, other):
392         return not (self == other)
393
394
395 class VppMRoutePath(VppRoutePath):
396
397     def __init__(self, nh_sw_if_index, flags,
398                  nh=None,
399                  proto=FibPathProto.FIB_PATH_NH_PROTO_IP4,
400                  type=FibPathType.FIB_PATH_TYPE_NORMAL,
401                  bier_imp=INVALID_INDEX):
402         if not nh:
403             nh = "::" if proto is FibPathProto.FIB_PATH_NH_PROTO_IP6 \
404                  else "0.0.0.0"
405         super(VppMRoutePath, self).__init__(nh,
406                                             nh_sw_if_index,
407                                             proto=proto,
408                                             type=type,
409                                             next_hop_id=bier_imp)
410         self.nh_i_flags = flags
411         self.bier_imp = bier_imp
412
413     def encode(self):
414         return {'path': super(VppMRoutePath, self).encode(),
415                 'itf_flags': self.nh_i_flags}
416
417
418 class VppIpRoute(VppObject):
419     """
420     IP Route
421     """
422
423     def __init__(self, test, dest_addr,
424                  dest_addr_len, paths, table_id=0, register=True):
425         self._test = test
426         self.paths = paths
427         self.table_id = table_id
428         self.prefix = VppIpPrefix(dest_addr, dest_addr_len)
429         self.register = register
430         self.stats_index = None
431
432         self.encoded_paths = []
433         for path in self.paths:
434             self.encoded_paths.append(path.encode())
435
436     def __eq__(self, other):
437         if self.table_id == other.table_id and \
438            self.prefix == other.prefix:
439             return True
440         return False
441
442     def modify(self, paths):
443         self.paths = paths
444         self.encoded_paths = []
445         for path in self.paths:
446             self.encoded_paths.append(path.encode())
447
448         self._test.vapi.ip_route_add_del(route={'table_id': self.table_id,
449                                                 'prefix': self.prefix.encode(),
450                                                 'n_paths': len(
451                                                     self.encoded_paths),
452                                                 'paths': self.encoded_paths,
453                                                 },
454                                          is_add=1,
455                                          is_multipath=0)
456
457     def add_vpp_config(self):
458         r = self._test.vapi.ip_route_add_del(
459             route={'table_id': self.table_id,
460                    'prefix': self.prefix.encode(),
461                    'n_paths': len(self.encoded_paths),
462                    'paths': self.encoded_paths,
463                    },
464             is_add=1,
465             is_multipath=0)
466         self.stats_index = r.stats_index
467         if self.register:
468             self._test.registry.register(self, self._test.logger)
469
470     def remove_vpp_config(self):
471         self._test.vapi.ip_route_add_del(route={'table_id': self.table_id,
472                                                 'prefix': self.prefix.encode(),
473                                                 'n_paths': len(
474                                                     self.encoded_paths),
475                                                 'paths': self.encoded_paths,
476                                                 },
477                                          is_add=0,
478                                          is_multipath=0)
479
480     def query_vpp_config(self):
481         return find_route(self._test,
482                           self.prefix.address,
483                           self.prefix.len,
484                           self.table_id)
485
486     def object_id(self):
487         return ("%s:table-%d-%s/%d" % (
488             'ip6-route' if self.prefix.addr.version == 6 else 'ip-route',
489                 self.table_id,
490                 self.prefix.address,
491                 self.prefix.len))
492
493     def get_stats_to(self):
494         c = self._test.statistics.get_counter("/net/route/to")
495         return c[0][self.stats_index]
496
497     def get_stats_via(self):
498         c = self._test.statistics.get_counter("/net/route/via")
499         return c[0][self.stats_index]
500
501
502 class VppIpMRoute(VppObject):
503     """
504     IP Multicast Route
505     """
506
507     def __init__(self, test, src_addr, grp_addr,
508                  grp_addr_len, e_flags, paths, table_id=0,
509                  rpf_id=0):
510         self._test = test
511         self.paths = paths
512         self.table_id = table_id
513         self.e_flags = e_flags
514         self.rpf_id = rpf_id
515
516         self.prefix = VppIpMPrefix(src_addr, grp_addr, grp_addr_len)
517         self.encoded_paths = []
518         for path in self.paths:
519             self.encoded_paths.append(path.encode())
520
521     def add_vpp_config(self):
522         r = self._test.vapi.ip_mroute_add_del(self.table_id,
523                                               self.prefix.encode(),
524                                               self.e_flags,
525                                               self.rpf_id,
526                                               self.encoded_paths,
527                                               is_add=1)
528         self.stats_index = r.stats_index
529         self._test.registry.register(self, self._test.logger)
530
531     def remove_vpp_config(self):
532         self._test.vapi.ip_mroute_add_del(self.table_id,
533                                           self.prefix.encode(),
534                                           self.e_flags,
535                                           self.rpf_id,
536                                           self.encoded_paths,
537                                           is_add=0)
538
539     def update_entry_flags(self, flags):
540         self.e_flags = flags
541         self._test.vapi.ip_mroute_add_del(self.table_id,
542                                           self.prefix.encode(),
543                                           self.e_flags,
544                                           self.rpf_id,
545                                           [],
546                                           is_add=1)
547
548     def update_rpf_id(self, rpf_id):
549         self.rpf_id = rpf_id
550         self._test.vapi.ip_mroute_add_del(self.table_id,
551                                           self.prefix.encode(),
552                                           self.e_flags,
553                                           self.rpf_id,
554                                           [],
555                                           is_add=1)
556
557     def update_path_flags(self, itf, flags):
558         for p in range(len(self.paths)):
559             if self.paths[p].nh_itf == itf:
560                 self.paths[p].nh_i_flags = flags
561             self.encoded_paths[p] = self.paths[p].encode()
562             break
563
564         self._test.vapi.ip_mroute_add_del(self.table_id,
565                                           self.prefix.encode(),
566                                           self.e_flags,
567                                           self.rpf_id,
568                                           [self.encoded_paths[p]],
569                                           is_add=1,
570                                           is_multipath=0)
571
572     def query_vpp_config(self):
573         return find_mroute(self._test,
574                            self.prefix.gaddr,
575                            self.prefix.saddr,
576                            self.prefix.length,
577                            self.table_id)
578
579     def object_id(self):
580         return ("%d:(%s,%s/%d)" % (self.table_id,
581                                    self.prefix.saddr,
582                                    self.prefix.gaddr,
583                                    self.prefix.length))
584
585     def get_stats(self):
586         c = self._test.statistics.get_counter("/net/mroute")
587         return c[0][self.stats_index]
588
589
590 class VppMFibSignal(object):
591     def __init__(self, test, route, interface, packet):
592         self.route = route
593         self.interface = interface
594         self.packet = packet
595         self.test = test
596
597     def compare(self, signal):
598         self.test.assertEqual(self.interface, signal.sw_if_index)
599         self.test.assertEqual(self.route.table_id, signal.table_id)
600         self.test.assertEqual(self.route.prefix, signal.prefix)
601
602
603 class VppMplsIpBind(VppObject):
604     """
605     MPLS to IP Binding
606     """
607
608     def __init__(self, test, local_label, dest_addr, dest_addr_len,
609                  table_id=0, ip_table_id=0, is_ip6=0):
610         self._test = test
611         self.dest_addr_len = dest_addr_len
612         self.dest_addr = dest_addr
613         self.ip_addr = ip_address(text_type(dest_addr))
614         self.local_label = local_label
615         self.table_id = table_id
616         self.ip_table_id = ip_table_id
617         self.prefix = VppIpPrefix(dest_addr, dest_addr_len)
618
619     def add_vpp_config(self):
620         self._test.vapi.mpls_ip_bind_unbind(self.local_label,
621                                             self.prefix.encode(),
622                                             table_id=self.table_id,
623                                             ip_table_id=self.ip_table_id)
624         self._test.registry.register(self, self._test.logger)
625
626     def remove_vpp_config(self):
627         self._test.vapi.mpls_ip_bind_unbind(self.local_label,
628                                             self.prefix.encode(),
629                                             table_id=self.table_id,
630                                             ip_table_id=self.ip_table_id,
631                                             is_bind=0)
632
633     def query_vpp_config(self):
634         dump = self._test.vapi.mpls_route_dump(self.table_id)
635         for e in dump:
636             if self.local_label == e.mr_route.mr_label \
637                and self.table_id == e.mr_route.mr_table_id:
638                 return True
639         return False
640
641     def object_id(self):
642         return ("%d:%s binds %d:%s/%d"
643                 % (self.table_id,
644                    self.local_label,
645                    self.ip_table_id,
646                    self.dest_addr,
647                    self.dest_addr_len))
648
649
650 class VppMplsTable(VppObject):
651
652     def __init__(self,
653                  test,
654                  table_id):
655         self._test = test
656         self.table_id = table_id
657
658     def add_vpp_config(self):
659         self._test.vapi.mpls_table_add_del(
660             self.table_id,
661             is_add=1)
662         self._test.registry.register(self, self._test.logger)
663
664     def remove_vpp_config(self):
665         self._test.vapi.mpls_table_add_del(
666             self.table_id,
667             is_add=0)
668
669     def query_vpp_config(self):
670         dump = self._test.vapi.mpls_table_dump()
671         for d in dump:
672             if d.mt_table.mt_table_id == self.table_id:
673                 return True
674         return False
675
676     def object_id(self):
677         return ("table-mpls-%d" % (self.table_id))
678
679
680 class VppMplsRoute(VppObject):
681     """
682     MPLS Route/LSP
683     """
684
685     def __init__(self, test, local_label, eos_bit, paths, table_id=0,
686                  is_multicast=0,
687                  eos_proto=FibPathProto.FIB_PATH_NH_PROTO_IP4):
688         self._test = test
689         self.paths = paths
690         self.local_label = local_label
691         self.eos_bit = eos_bit
692         self.eos_proto = eos_proto
693         self.table_id = table_id
694         self.is_multicast = is_multicast
695
696     def add_vpp_config(self):
697         paths = []
698         for path in self.paths:
699             paths.append(path.encode())
700
701         r = self._test.vapi.mpls_route_add_del(self.table_id,
702                                                self.local_label,
703                                                self.eos_bit,
704                                                self.eos_proto,
705                                                self.is_multicast,
706                                                paths, 1, 0)
707         self.stats_index = r.stats_index
708         self._test.registry.register(self, self._test.logger)
709
710     def remove_vpp_config(self):
711         paths = []
712         for path in self.paths:
713             paths.append(path.encode())
714
715         self._test.vapi.mpls_route_add_del(self.table_id,
716                                            self.local_label,
717                                            self.eos_bit,
718                                            self.eos_proto,
719                                            self.is_multicast,
720                                            paths, 0, 0)
721
722     def query_vpp_config(self):
723         return find_mpls_route(self._test, self.table_id,
724                                self.local_label, self.eos_bit)
725
726     def object_id(self):
727         return ("mpls-route-%d:%s/%d"
728                 % (self.table_id,
729                    self.local_label,
730                    20 + self.eos_bit))
731
732     def get_stats_to(self):
733         c = self._test.statistics.get_counter("/net/route/to")
734         return c[0][self.stats_index]
735
736     def get_stats_via(self):
737         c = self._test.statistics.get_counter("/net/route/via")
738         return c[0][self.stats_index]