text: """
    IP Routes
  
    object abstractions for representing IP routes in VPP
  """
  
  from vpp_object import VppObject
  from socket import inet_pton, inet_ntop, AF_INET, AF_INET6
  from vpp_ip import DpoProto, VppIpPrefix, INVALID_INDEX, VppIpAddressUnion, \
      VppIpMPrefix
  from ipaddress import ip_address, IPv4Network, IPv6Network
  
  # from vnet/vnet/mpls/mpls_types.h
  MPLS_IETF_MAX_LABEL = 0xfffff
  MPLS_LABEL_INVALID = MPLS_IETF_MAX_LABEL + 1
  
  try:
      text_type = unicode
  except NameError:
      text_type = str
  
  
  class MRouteItfFlags:
      MFIB_ITF_FLAG_NONE = 0
      MFIB_ITF_FLAG_NEGATE_SIGNAL = 1
      MFIB_ITF_FLAG_ACCEPT = 2
      MFIB_ITF_FLAG_FORWARD = 4
      MFIB_ITF_FLAG_SIGNAL_PRESENT = 8
      MFIB_ITF_FLAG_INTERNAL_COPY = 16
  
  
  class MRouteEntryFlags:
      MFIB_ENTRY_FLAG_NONE = 0
      MFIB_ENTRY_FLAG_SIGNAL = 1
      MFIB_ENTRY_FLAG_DROP = 2
      MFIB_ENTRY_FLAG_CONNECTED = 4
      MFIB_ENTRY_FLAG_INHERIT_ACCEPT = 8
  
  
  class FibPathProto:
      FIB_PATH_NH_PROTO_IP4 = 0
      FIB_PATH_NH_PROTO_IP6 = 1
      FIB_PATH_NH_PROTO_MPLS = 2
      FIB_PATH_NH_PROTO_ETHERNET = 3
      FIB_PATH_NH_PROTO_BIER = 4
      FIB_PATH_NH_PROTO_NSH = 5
  
  
  class FibPathType:
      FIB_PATH_TYPE_NORMAL = 0
      FIB_PATH_TYPE_LOCAL = 1
      FIB_PATH_TYPE_DROP = 2
      FIB_PATH_TYPE_UDP_ENCAP = 3
      FIB_PATH_TYPE_BIER_IMP = 4
      FIB_PATH_TYPE_ICMP_UNREACH = 5
      FIB_PATH_TYPE_ICMP_PROHIBIT = 6
      FIB_PATH_TYPE_SOURCE_LOOKUP = 7
      FIB_PATH_TYPE_DVR = 8
      FIB_PATH_TYPE_INTERFACE_RX = 9
      FIB_PATH_TYPE_CLASSIFY = 10
  
  
  class FibPathFlags:
      FIB_PATH_FLAG_NONE = 0
      FIB_PATH_FLAG_RESOLVE_VIA_ATTACHED = 1
      FIB_PATH_FLAG_RESOLVE_VIA_HOST = 2
      FIB_PATH_FLAG_POP_PW_CW = 4
  
  
  class MplsLspMode:
      PIPE = 0
      UNIFORM = 1
  
  
  def ip_to_dpo_proto(addr):
      if addr.version == 6:
          return DpoProto.DPO_PROTO_IP6
      else:
          return DpoProto.DPO_PROTO_IP4
  
  
  def address_proto(ip_addr):
      if ip_addr.ip_addr.version is 4:
          return FibPathProto.FIB_PATH_NH_PROTO_IP4
      else:
          return FibPathProto.FIB_PATH_NH_PROTO_IP6
  
  
  def find_route(test, addr, len, table_id=0):
      ip_addr = ip_address(text_type(addr))
  
      if 4 is ip_addr.version:
          routes = test.vapi.ip_route_dump(table_id, False)
          prefix = IPv4Network("%s/%d" % (text_type(addr), len), strict=False)
      else:
          routes = test.vapi.ip_route_dump(table_id, True)
          prefix = IPv6Network("%s/%d" % (text_type(addr), len), strict=False)
  
      for e in routes:
          if table_id == e.route.table_id \
             and prefix == e.route.prefix:
              return True
      return False
  
  
  def find_mroute(test, grp_addr, src_addr, grp_addr_len,
                  table_id=0):
      ip_mprefix = VppIpMPrefix(text_type(src_addr),
                                text_type(grp_addr),
                                grp_addr_len)
  
      if 4 is ip_mprefix.version:
          routes = test.vapi.ip_mroute_dump(table_id, False)
      else:
          routes = test.vapi.ip_mroute_dump(table_id, True)
  
      for e in routes:
          if table_id == e.route.table_id and ip_mprefix == e.route.prefix:
              return True
      return False
  
  
  def find_mpls_route(test, table_id, label, eos_bit, paths=None):
      dump = test.vapi.mpls_route_dump(table_id)
      for e in dump:
          if label == e.mr_route.mr_label \
             and eos_bit == e.mr_route.mr_eos \
             and table_id == e.mr_route.mr_table_id:
              if not paths:
                  return True
              else:
                  if (len(paths) != len(e.mr_route.mr_paths)):
                      return False
                  for i in range(len(paths)):
                      if (paths[i] != e.mr_route.mr_paths[i]):
                          return False
                  return True
      return False
  
  
  def fib_interface_ip_prefix(test, address, length, sw_if_index):
      ip_addr = ip_address(text_type(address))
  
      if 4 is ip_addr.version:
          addrs = test.vapi.ip_address_dump(sw_if_index)
          prefix = IPv4Network("%s/%d" % (text_type(address), length),
                               strict=False)
      else:
          addrs = test.vapi.ip_address_dump(sw_if_index, is_ipv6=1)
          prefix = IPv6Network("%s/%d" % (text_type(address), length),
                               strict=False)
  
      # TODO: refactor this to VppIpPrefix.__eq__
      for a in addrs:
          if a.sw_if_index == sw_if_index and \
             a.prefix.network == prefix:
              return True
      return False
  
  
  class VppIpTable(VppObject):
  
      def __init__(self,
                   test,
                   table_id,
                   is_ip6=0):
          self._test = test
          self.table_id = table_id
          self.is_ip6 = is_ip6
  
      def add_vpp_config(self):
          self._test.vapi.ip_table_add_del(is_ipv6=self.is_ip6, is_add=1,
                                           table_id=self.table_id)
          self._test.registry.register(self, self._test.logger)
  
      def remove_vpp_config(self):
          self._test.vapi.ip_table_add_del(is_ipv6=self.is_ip6, is_add=0,
                                           table_id=self.table_id)
  
      def query_vpp_config(self):
          if self.table_id == 0:
              # the default table always exists
              return False
          # find the default route
          return find_route(self._test,
                            "::" if self.is_ip6 else "0.0.0.0",
                            0,
                            self.table_id)
  
      def object_id(self):
          return ("table-%s-%d" %
                  ("v6" if self.is_ip6 == 1 else "v4",
                   self.table_id))
  
  
  class VppIpInterfaceAddress(VppObject):
  
      def __init__(self, test, intf, addr, len):
          self._test = test
          self.intf = intf
          self.prefix = VppIpPrefix(addr, len)
  
      def add_vpp_config(self):
          self._test.vapi.sw_interface_add_del_address(
              sw_if_index=self.intf.sw_if_index, address=self.prefix.bytes,
              address_length=self.prefix.length, is_ipv6=self.prefix.is_ip6,
              is_add=1)
          self._test.registry.register(self, self._test.logger)
  
      def remove_vpp_config(self):
          self._test.vapi.sw_interface_add_del_address(
              sw_if_index=self.intf.sw_if_index, address=self.prefix.bytes,
              address_length=self.prefix.length, is_ipv6=self.prefix.is_ip6,
              is_add=0)
  
      def query_vpp_config(self):
          return fib_interface_ip_prefix(self._test,
                                         self.prefix.address,
                                         self.prefix.length,
                                         self.intf.sw_if_index)
  
      def object_id(self):
          return "interface-ip-%s-%s" % (self.intf, self.prefix)
  
  
  class VppIpInterfaceBind(VppObject):
  
      def __init__(self, test, intf, table):
          self._test = test
          self.intf = intf
          self.table = table
  
      def add_vpp_config(self):
          if self.table.is_ip6:
              self.intf.set_table_ip6(self.table.table_id)
          else:
              self.intf.set_table_ip4(self.table.table_id)
          self._test.registry.register(self, self._test.logger)
  
      def remove_vpp_config(self):
          if 0 == self.table.table_id:
              return
          if self.table.is_ip6:
              self.intf.set_table_ip6(0)
          else:
              self.intf.set_table_ip4(0)
  
      def query_vpp_config(self):
          if 0 == self.table.table_id:
              return False
          return self._test.vapi.sw_interface_get_table(
              self.intf.sw_if_index,
              self.table.is_ip6).vrf_id == self.table.table_id
  
      def object_id(self):
          return "interface-bind-%s-%s" % (self.intf, self.table)
  
  
  class VppMplsLabel(object):
      def __init__(self, value, mode=MplsLspMode.PIPE, ttl=64, exp=0):
          self.value = value
          self.mode = mode
          self.ttl = ttl
          self.exp = exp
  
      def encode(self):
          is_uniform = 0 if self.mode is MplsLspMode.PIPE else 1
          return {'label': self.value,
                  'ttl': self.ttl,
                  'exp': self.exp,
                  'is_uniform': is_uniform}
  
      def __eq__(self, other):
          if isinstance(other, self.__class__):
              return (self.value == other.value and
                      self.ttl == other.ttl and
                      self.exp == other.exp and
                      self.mode == other.mode)
          elif hasattr(other, 'label'):
              return (self.value == other.label and
                      self.ttl == other.ttl and
                      self.exp == other.exp and
                      (self.mode == MplsLspMode.UNIFORM) == other.is_uniform)
          else:
              return False
  
      def __ne__(self, other):
          return not (self == other)
  
  
  class VppFibPathNextHop(object):
      def __init__(self, addr,
                   via_label=MPLS_LABEL_INVALID,
                   next_hop_id=INVALID_INDEX):
          self.addr = VppIpAddressUnion(addr)
          self.via_label = via_label
          self.obj_id = next_hop_id
  
      def encode(self):
          if self.via_label is not MPLS_LABEL_INVALID:
              return {'via_label': self.via_label}
          if self.obj_id is not INVALID_INDEX:
              return {'obj_id': self.obj_id}
          else:
              return {'address': self.addr.encode()}
  
      def proto(self):
          if self.via_label is MPLS_LABEL_INVALID:
              return address_proto(self.addr)
          else:
              return FibPathProto.FIB_PATH_NH_PROTO_MPLS
  
      def __eq__(self, other):
          if not isinstance(other, self.__class__):
              # try the other instance's __eq__.
              return NotImplemented
          return (self.addr == other.addr and
                  self.via_label == other.via_label and
                  self.obj_id == other.obj_id)
  
  
  class VppRoutePath(object):
  
      def __init__(
              self,
              nh_addr,
              nh_sw_if_index,
              nh_table_id=0,
              labels=[],
              nh_via_label=MPLS_LABEL_INVALID,
              rpf_id=0,
              next_hop_id=INVALID_INDEX,
              proto=None,
              flags=FibPathFlags.FIB_PATH_FLAG_NONE,
              type=FibPathType.FIB_PATH_TYPE_NORMAL):
          self.nh_itf = nh_sw_if_index
          self.nh_table_id = nh_table_id
          self.nh_labels = labels
          self.weight = 1
          self.rpf_id = rpf_id
          self.proto = proto
          self.flags = flags
          self.type = type
          self.nh = VppFibPathNextHop(nh_addr, nh_via_label, next_hop_id)
          if proto is None:
              self.proto = self.nh.proto()
          else:
              self.proto = proto
          self.next_hop_id = next_hop_id
  
      def encode_labels(self):
          lstack = []
          for l in self.nh_labels:
              if type(l) == VppMplsLabel:
                  lstack.append(l.encode())
              else:
                  lstack.append({'label': l,
                                 'ttl': 255})
          while (len(lstack) < 16):
              lstack.append({})
  
          return lstack
  
      def encode(self):
          return {'weight': 1,
                  'preference': 0,
                  'table_id': self.nh_table_id,
                  'nh': self.nh.encode(),
                  'next_hop_id': self.next_hop_id,
                  'sw_if_index': self.nh_itf,
                  'rpf_id': self.rpf_id,
                  'proto': self.proto,
                  'type': self.type,
                  'flags': self.flags,
                  'n_labels': len(self.nh_labels),
                  'label_stack': self.encode_labels()}
  
      def __eq__(self, other):
          if isinstance(other, self.__class__):
              return self.nh == other.nh
          elif hasattr(other, 'sw_if_index'):
              # vl_api_fib_path_t
              if (len(self.nh_labels) != other.n_labels):
                  return False
              for i in range(len(self.nh_labels)):
                  if (self.nh_labels[i] != other.label_stack[i]):
                      return False
              return self.nh_itf == other.sw_if_index
          else:
              return False
  
      def __ne__(self, other):
          return not (self == other)
  
  
  class VppMRoutePath(VppRoutePath):
  
      def __init__(self, nh_sw_if_index, flags,
                   nh=None,
                   proto=FibPathProto.FIB_PATH_NH_PROTO_IP4,
                   type=FibPathType.FIB_PATH_TYPE_NORMAL,
                   bier_imp=INVALID_INDEX):
          if not nh:
              nh = "::" if proto is FibPathProto.FIB_PATH_NH_PROTO_IP6 \
                   else "0.0.0.0"
          super(VppMRoutePath, self).__init__(nh,
                                              nh_sw_if_index,
                                              proto=proto,
                                              type=type,
                                              next_hop_id=bier_imp)
          self.nh_i_flags = flags
          self.bier_imp = bier_imp
  
      def encode(self):
          return {'path': super(VppMRoutePath, self).encode(),
                  'itf_flags': self.nh_i_flags}
  
  
  class VppIpRoute(VppObject):
      """
      IP Route
      """
  
      def __init__(self, test, dest_addr,
                   dest_addr_len, paths, table_id=0, register=True):
          self._test = test
          self.paths = paths
          self.table_id = table_id
          self.prefix = VppIpPrefix(dest_addr, dest_addr_len)
          self.register = register
          self.stats_index = None
          self.modified = False
  
          self.encoded_paths = []
          for path in self.paths:
              self.encoded_paths.append(path.encode())
  
      def __eq__(self, other):
          if self.table_id == other.table_id and \
             self.prefix == other.prefix:
              return True
          return False
  
      def modify(self, paths):
          self.paths = paths
          self.encoded_paths = []
          for path in self.paths:
              self.encoded_paths.append(path.encode())
          self.modified = True
  
          self._test.vapi.ip_route_add_del(route={'table_id': self.table_id,
                                                  'prefix': self.prefix.encode(),
                                                  'n_paths': len(
                                                      self.encoded_paths),
                                                  'paths': self.encoded_paths,
                                                  },
                                           is_add=1,
                                           is_multipath=0)
  
      def add_vpp_config(self):
          r = self._test.vapi.ip_route_add_del(
              route={'table_id': self.table_id,
                     'prefix': self.prefix.encode(),
                     'n_paths': len(self.encoded_paths),
                     'paths': self.encoded_paths,
                     },
              is_add=1,
              is_multipath=0)
          self.stats_index = r.stats_index
          if self.register:
              self._test.registry.register(self, self._test.logger)
  
      def remove_vpp_config(self):
          # there's no need to issue different deletes for modified routes
          # we do this only to test the two different ways to delete routes
          # eiter by passing all the paths to remove and mutlipath=1 or
          # passing no paths and multipath=0
          if self.modified:
              self._test.vapi.ip_route_add_del(
                  route={'table_id': self.table_id,
                         'prefix': self.prefix.encode(),
                         'n_paths': len(
                             self.encoded_paths),
                         'paths': self.encoded_paths},
                  is_add=0,
                  is_multipath=1)
          else:
              self._test.vapi.ip_route_add_del(
                  route={'table_id': self.table_id,
                         'prefix': self.prefix.encode(),
                         'n_paths': 0},
                  is_add=0,
                  is_multipath=0)
  
      def query_vpp_config(self):
          return find_route(self._test,
                            self.prefix.address,
                            self.prefix.len,
                            self.table_id)
  
      def object_id(self):
          return ("%s:table-%d-%s/%d" % (
              'ip6-route' if self.prefix.addr.version == 6 else 'ip-route',
                  self.table_id,
                  self.prefix.address,
                  self.prefix.len))
  
      def get_stats_to(self):
          c = self._test.statistics.get_counter("/net/route/to")
          return c[0][self.stats_index]
  
      def get_stats_via(self):
          c = self._test.statistics.get_counter("/net/route/via")
          return c[0][self.stats_index]
  
  
  class VppIpMRoute(VppObject):
      """
      IP Multicast Route
      """
  
      def __init__(self, test, src_addr, grp_addr,
                   grp_addr_len, e_flags, paths, table_id=0,
                   rpf_id=0):
          self._test = test
          self.paths = paths
          self.table_id = table_id
          self.e_flags = e_flags
          self.rpf_id = rpf_id
  
          self.prefix = VppIpMPrefix(src_addr, grp_addr, grp_addr_len)
          self.encoded_paths = []
          for path in self.paths:
              self.encoded_paths.append(path.encode())
  
      def add_vpp_config(self):
          r = self._test.vapi.ip_mroute_add_del(self.table_id,
                                                self.prefix.encode(),
                                                self.e_flags,
                                                self.rpf_id,
                                                self.encoded_paths,
                                                is_add=1)
          self.stats_index = r.stats_index
          self._test.registry.register(self, self._test.logger)
  
      def remove_vpp_config(self):
          self._test.vapi.ip_mroute_add_del(self.table_id,
                                            self.prefix.encode(),
                                            self.e_flags,
                                            self.rpf_id,
                                            self.encoded_paths,
                                            is_add=0)
  
      def update_entry_flags(self, flags):
          self.e_flags = flags
          self._test.vapi.ip_mroute_add_del(self.table_id,
                                            self.prefix.encode(),
                                            self.e_flags,
                                            self.rpf_id,
                                            [],
                                            is_add=1)
  
      def update_rpf_id(self, rpf_id):
          self.rpf_id = rpf_id
          self._test.vapi.ip_mroute_add_del(self.table_id,
                                            self.prefix.encode(),
                                            self.e_flags,
                                            self.rpf_id,
                                            [],
                                            is_add=1)
  
      def update_path_flags(self, itf, flags):
          for p in range(len(self.paths)):
              if self.paths[p].nh_itf == itf:
                  self.paths[p].nh_i_flags = flags
              self.encoded_paths[p] = self.paths[p].encode()
              break
  
          self._test.vapi.ip_mroute_add_del(self.table_id,
                                            self.prefix.encode(),
                                            self.e_flags,
                                            self.rpf_id,
                                            [self.encoded_paths[p]],
                                            is_add=1,
                                            is_multipath=0)
  
      def query_vpp_config(self):
          return find_mroute(self._test,
                             self.prefix.gaddr,
                             self.prefix.saddr,
                             self.prefix.length,
                             self.table_id)
  
      def object_id(self):
          return ("%d:(%s,%s/%d)" % (self.table_id,
                                     self.prefix.saddr,
                                     self.prefix.gaddr,
                                     self.prefix.length))
  
      def get_stats(self):
          c = self._test.statistics.get_counter("/net/mroute")
          return c[0][self.stats_index]
  
  
  class VppMFibSignal(object):
      def __init__(self, test, route, interface, packet):
          self.route = route
          self.interface = interface
          self.packet = packet
          self.test = test
  
      def compare(self, signal):
          self.test.assertEqual(self.interface, signal.sw_if_index)
          self.test.assertEqual(self.route.table_id, signal.table_id)
          self.test.assertEqual(self.route.prefix, signal.prefix)
  
  
  class VppMplsIpBind(VppObject):
      """
      MPLS to IP Binding
      """
  
      def __init__(self, test, local_label, dest_addr, dest_addr_len,
                   table_id=0, ip_table_id=0, is_ip6=0):
          self._test = test
          self.dest_addr_len = dest_addr_len
          self.dest_addr = dest_addr
          self.ip_addr = ip_address(text_type(dest_addr))
          self.local_label = local_label
          self.table_id = table_id
          self.ip_table_id = ip_table_id
          self.prefix = VppIpPrefix(dest_addr, dest_addr_len)
  
      def add_vpp_config(self):
          self._test.vapi.mpls_ip_bind_unbind(self.local_label,
                                              self.prefix.encode(),
                                              table_id=self.table_id,
                                              ip_table_id=self.ip_table_id)
          self._test.registry.register(self, self._test.logger)
  
      def remove_vpp_config(self):
          self._test.vapi.mpls_ip_bind_unbind(self.local_label,
                                              self.prefix.encode(),
                                              table_id=self.table_id,
                                              ip_table_id=self.ip_table_id,
                                              is_bind=0)
  
      def query_vpp_config(self):
          dump = self._test.vapi.mpls_route_dump(self.table_id)
          for e in dump:
              if self.local_label == e.mr_route.mr_label \
                 and self.table_id == e.mr_route.mr_table_id:
                  return True
          return False
  
      def object_id(self):
          return ("%d:%s binds %d:%s/%d"
                  % (self.table_id,
                     self.local_label,
                     self.ip_table_id,
                     self.dest_addr,
                     self.dest_addr_len))
  
  
  class VppMplsTable(VppObject):
  
      def __init__(self,
                   test,
                   table_id):
          self._test = test
          self.table_id = table_id
  
      def add_vpp_config(self):
          self._test.vapi.mpls_table_add_del(
              self.table_id,
              is_add=1)
          self._test.registry.register(self, self._test.logger)
  
      def remove_vpp_config(self):
          self._test.vapi.mpls_table_add_del(
              self.table_id,
              is_add=0)
  
      def query_vpp_config(self):
          dump = self._test.vapi.mpls_table_dump()
          for d in dump:
              if d.mt_table.mt_table_id == self.table_id:
                  return True
          return False
  
      def object_id(self):
          return ("table-mpls-%d" % (self.table_id))
  
  
  class VppMplsRoute(VppObject):
      """
      MPLS Route/LSP
      """
  
      def __init__(self, test, local_label, eos_bit, paths, table_id=0,
                   is_multicast=0,
                   eos_proto=FibPathProto.FIB_PATH_NH_PROTO_IP4):
          self._test = test
          self.paths = paths
          self.local_label = local_label
          self.eos_bit = eos_bit
          self.eos_proto = eos_proto
          self.table_id = table_id
          self.is_multicast = is_multicast
  
      def add_vpp_config(self):
          paths = []
          for path in self.paths:
              paths.append(path.encode())
  
          r = self._test.vapi.mpls_route_add_del(self.table_id,
                                                 self.local_label,
                                                 self.eos_bit,
                                                 self.eos_proto,
                                                 self.is_multicast,
                                                 paths, 1, 0)
          self.stats_index = r.stats_index
          self._test.registry.register(self, self._test.logger)
  
      def remove_vpp_config(self):
          paths = []
          for path in self.paths:
              paths.append(path.encode())
  
          self._test.vapi.mpls_route_add_del(self.table_id,
                                             self.local_label,
                                             self.eos_bit,
                                             self.eos_proto,
                                             self.is_multicast,
                                             paths, 0, 0)
  
      def query_vpp_config(self):
          return find_mpls_route(self._test, self.table_id,
                                 self.local_label, self.eos_bit)
  
      def object_id(self):
          return ("mpls-route-%d:%s/%d"
                  % (self.table_id,
                     self.local_label,
                     20 + self.eos_bit))
  
      def get_stats_to(self):
          c = self._test.statistics.get_counter("/net/route/to")
          return c[0][self.stats_index]
  
      def get_stats_via(self):
          c = self._test.statistics.get_counter("/net/route/via")
          return c[0][self.stats_index]
  