1# Copyright (C) 2014 Nippon Telegraph and Telephone Corporation.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#    http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12# implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16"""
17 Defines base data types and models required specifically for VRF support.
18"""
19
20import abc
21import logging
22import six
23
24from ryu.lib.packet.bgp import BGP_ATTR_TYPE_ORIGIN
25from ryu.lib.packet.bgp import BGP_ATTR_TYPE_AS_PATH
26from ryu.lib.packet.bgp import BGP_ATTR_TYPE_EXTENDED_COMMUNITIES
27from ryu.lib.packet.bgp import BGP_ATTR_TYEP_PMSI_TUNNEL_ATTRIBUTE
28from ryu.lib.packet.bgp import BGP_ATTR_TYPE_MULTI_EXIT_DISC
29from ryu.lib.packet.bgp import BGPPathAttributeOrigin
30from ryu.lib.packet.bgp import BGPPathAttributeAsPath
31from ryu.lib.packet.bgp import EvpnEthernetSegmentNLRI
32from ryu.lib.packet.bgp import BGPPathAttributeExtendedCommunities
33from ryu.lib.packet.bgp import BGPPathAttributeMultiExitDisc
34from ryu.lib.packet.bgp import BGPEncapsulationExtendedCommunity
35from ryu.lib.packet.bgp import BGPEvpnEsiLabelExtendedCommunity
36from ryu.lib.packet.bgp import BGPEvpnEsImportRTExtendedCommunity
37from ryu.lib.packet.bgp import BGPPathAttributePmsiTunnel
38from ryu.lib.packet.bgp import PmsiTunnelIdIngressReplication
39from ryu.lib.packet.bgp import RF_L2_EVPN
40from ryu.lib.packet.bgp import EvpnMacIPAdvertisementNLRI
41from ryu.lib.packet.bgp import EvpnIpPrefixNLRI
42from ryu.lib.packet.safi import (
43    IP_FLOWSPEC,
44    VPN_FLOWSPEC,
45)
46
47from ryu.services.protocols.bgp.base import OrderedDict
48from ryu.services.protocols.bgp.constants import VPN_TABLE
49from ryu.services.protocols.bgp.constants import VRF_TABLE
50from ryu.services.protocols.bgp.info_base.base import Destination
51from ryu.services.protocols.bgp.info_base.base import Path
52from ryu.services.protocols.bgp.info_base.base import Table
53from ryu.services.protocols.bgp.utils.bgp import create_rt_extended_community
54from ryu.services.protocols.bgp.utils.stats import LOCAL_ROUTES
55from ryu.services.protocols.bgp.utils.stats import REMOTE_ROUTES
56from ryu.services.protocols.bgp.utils.stats import RESOURCE_ID
57from ryu.services.protocols.bgp.utils.stats import RESOURCE_NAME
58
59LOG = logging.getLogger('bgpspeaker.info_base.vrf')
60
61
62@six.add_metaclass(abc.ABCMeta)
63class VrfTable(Table):
64    """Virtual Routing and Forwarding information base.
65     Keeps destination imported to given vrf in represents.
66     """
67
68    ROUTE_FAMILY = None
69    VPN_ROUTE_FAMILY = None
70    NLRI_CLASS = None
71    VRF_PATH_CLASS = None
72    VRF_DEST_CLASS = None
73
74    def __init__(self, vrf_conf, core_service, signal_bus):
75        Table.__init__(self, vrf_conf.route_dist, core_service, signal_bus)
76        self._vrf_conf = vrf_conf
77        self._import_maps = []
78        self.init_import_maps(vrf_conf.import_maps)
79
80    def init_import_maps(self, import_maps):
81        LOG.debug(
82            "Initializing import maps (%s) for %r", import_maps, self
83        )
84        del self._import_maps[:]
85        importmap_manager = self._core_service.importmap_manager
86        for name in import_maps:
87            import_map = importmap_manager.get_import_map_by_name(name)
88            if import_map is None:
89                raise KeyError('No import map with name %s' % name)
90            self._import_maps.append(import_map)
91
92    @property
93    def import_rts(self):
94        return self._vrf_conf.import_rts
95
96    @property
97    def vrf_conf(self):
98        return self._vrf_conf
99
100    def _table_key(self, nlri):
101        """Return a key that will uniquely identify this NLRI inside
102        this table.
103        """
104        # Note: We use `prefix` representation of the NLRI, because
105        # BGP route can be identified without the route distinguisher
106        # value in the VRF space.
107        return nlri.prefix
108
109    def _create_dest(self, nlri):
110        return self.VRF_DEST_CLASS(self, nlri)
111
112    def append_import_map(self, import_map):
113        self._import_maps.append(import_map)
114
115    def remove_import_map(self, import_map):
116        self._import_maps.remove(import_map)
117
118    def get_stats_summary_dict(self):
119        """Returns count of local and remote paths."""
120
121        remote_route_count = 0
122        local_route_count = 0
123        for dest in self.values():
124            for path in dest.known_path_list:
125                if (hasattr(path.source, 'version_num') or
126                        path.source == VPN_TABLE):
127                    remote_route_count += 1
128                else:
129                    local_route_count += 1
130        return {RESOURCE_ID: self._vrf_conf.id,
131                RESOURCE_NAME: self._vrf_conf.name,
132                REMOTE_ROUTES: remote_route_count,
133                LOCAL_ROUTES: local_route_count}
134
135    def import_vpn_paths_from_table(self, vpn_table, import_rts=None):
136        for vpn_dest in vpn_table.values():
137            vpn_path = vpn_dest.best_path
138            if not vpn_path:
139                continue
140
141            if import_rts is None:
142                import_rts = set(self.import_rts)
143            else:
144                import_rts = set(import_rts)
145
146            path_rts = vpn_path.get_rts()
147            if import_rts.intersection(path_rts):
148                # TODO(PH): When (re-)implementing extranet, check what should
149                # be the label reported back to NC for local paths coming from
150                # other VRFs.
151                self.import_vpn_path(vpn_path)
152
153    def import_vpn_path(self, vpn_path):
154        """Imports `vpnv(4|6)_path` into `vrf(4|6)_table` or `evpn_path`
155        into vrfevpn_table`.
156
157        :Parameters:
158            - `vpn_path`: (Path) VPN path that will be cloned and imported
159            into VRF.
160        Note: Does not do any checking if this import is valid.
161        """
162        assert vpn_path.route_family == self.VPN_ROUTE_FAMILY
163        # If source of given vpnv4 path is NC we import it to given VRF
164        # table because of extranet setting. Hence we identify source of
165        # EXTRANET prefixes as VRF_TABLE, else VPN_TABLE.
166        source = vpn_path.source
167        if not source:
168            source = VRF_TABLE
169
170        if self.VPN_ROUTE_FAMILY == RF_L2_EVPN:
171            # Because NLRI class is the same if the route family is EVPN,
172            # we re-use the NLRI instance.
173            vrf_nlri = vpn_path.nlri
174        elif self.ROUTE_FAMILY.safi in [IP_FLOWSPEC, VPN_FLOWSPEC]:
175            vrf_nlri = self.NLRI_CLASS(rules=vpn_path.nlri.rules)
176        else:  # self.VPN_ROUTE_FAMILY in [RF_IPv4_VPN, RF_IPv6_VPN]
177            # Copy NLRI instance
178            ip, masklen = vpn_path.nlri.prefix.split('/')
179            vrf_nlri = self.NLRI_CLASS(length=int(masklen), addr=ip)
180
181        vrf_path = self.VRF_PATH_CLASS(
182            puid=self.VRF_PATH_CLASS.create_puid(
183                vpn_path.nlri.route_dist,
184                vpn_path.nlri.prefix),
185            source=source,
186            nlri=vrf_nlri,
187            src_ver_num=vpn_path.source_version_num,
188            pattrs=vpn_path.pathattr_map,
189            nexthop=vpn_path.nexthop,
190            is_withdraw=vpn_path.is_withdraw,
191            label_list=getattr(vpn_path.nlri, 'label_list', None),
192        )
193        if self._is_vrf_path_already_in_table(vrf_path):
194            return None
195
196        if self._is_vrf_path_filtered_out_by_import_maps(vrf_path):
197            return None
198        else:
199            vrf_dest = self.insert(vrf_path)
200            self._signal_bus.dest_changed(vrf_dest)
201
202    def _is_vrf_path_filtered_out_by_import_maps(self, vrf_path):
203        for import_map in self._import_maps:
204            if import_map.match(vrf_path):
205                return True
206
207        return False
208
209    def _is_vrf_path_already_in_table(self, vrf_path):
210        dest = self._get_dest(vrf_path.nlri)
211        if dest is None:
212            return False
213        return vrf_path in dest.known_path_list
214
215    def apply_import_maps(self):
216        changed_dests = []
217        for dest in self.values():
218            assert isinstance(dest, VrfDest)
219            for import_map in self._import_maps:
220                for path in dest.known_path_list:
221                    if import_map.match(path):
222                        dest.withdraw_path(path)
223                        changed_dests.append(dest)
224        return changed_dests
225
226    def insert_vrf_path(self, nlri, next_hop=None,
227                        gen_lbl=False, is_withdraw=False, **kwargs):
228        assert nlri
229        pattrs = None
230        label_list = []
231        vrf_conf = self.vrf_conf
232        if not is_withdraw:
233            table_manager = self._core_service.table_manager
234            if gen_lbl and next_hop:
235                # Label per next_hop demands we use a different label
236                # per next_hop. Here connected interfaces are advertised per
237                # VRF.
238                label_key = (vrf_conf.route_dist, next_hop)
239                nh_label = table_manager.get_nexthop_label(label_key)
240                if not nh_label:
241                    nh_label = table_manager.get_next_vpnv4_label()
242                    table_manager.set_nexthop_label(label_key, nh_label)
243                label_list.append(nh_label)
244
245            elif gen_lbl:
246                # If we do not have next_hop, get a new label.
247                label_list.append(table_manager.get_next_vpnv4_label())
248
249            # Set MPLS labels with the generated labels
250            if gen_lbl and isinstance(nlri, EvpnMacIPAdvertisementNLRI):
251                nlri.mpls_labels = label_list[:2]
252            elif gen_lbl and isinstance(nlri, EvpnIpPrefixNLRI):
253                nlri.mpls_label = label_list[0]
254
255            # Create a dictionary for path-attrs.
256            pattrs = OrderedDict()
257
258            # MpReachNlri and/or MpUnReachNlri attribute info. is contained
259            # in the path. Hence we do not add these attributes here.
260            from ryu.services.protocols.bgp.core import EXPECTED_ORIGIN
261
262            pattrs[BGP_ATTR_TYPE_ORIGIN] = BGPPathAttributeOrigin(
263                EXPECTED_ORIGIN)
264            pattrs[BGP_ATTR_TYPE_AS_PATH] = BGPPathAttributeAsPath([])
265            communities = []
266
267            # Set ES-Import Route Target
268            if isinstance(nlri, EvpnEthernetSegmentNLRI):
269                subtype = 2
270                es_import = nlri.esi.mac_addr
271                communities.append(BGPEvpnEsImportRTExtendedCommunity(
272                                   subtype=subtype,
273                                   es_import=es_import))
274
275            for rt in vrf_conf.export_rts:
276                communities.append(create_rt_extended_community(rt, 2))
277            for soo in vrf_conf.soo_list:
278                communities.append(create_rt_extended_community(soo, 3))
279
280            # Set Tunnel Encapsulation Attribute
281            tunnel_type = kwargs.get('tunnel_type', None)
282            if tunnel_type:
283                communities.append(
284                    BGPEncapsulationExtendedCommunity.from_str(tunnel_type))
285
286            # Set ESI Label Extended Community
287            redundancy_mode = kwargs.get('redundancy_mode', None)
288            if redundancy_mode is not None:
289                subtype = 1
290                flags = 0
291
292                from ryu.services.protocols.bgp.api.prefix import (
293                    REDUNDANCY_MODE_SINGLE_ACTIVE)
294                if redundancy_mode == REDUNDANCY_MODE_SINGLE_ACTIVE:
295                    flags |= BGPEvpnEsiLabelExtendedCommunity.SINGLE_ACTIVE_BIT
296
297                vni = kwargs.get('vni', None)
298                if vni is not None:
299                    communities.append(BGPEvpnEsiLabelExtendedCommunity(
300                        subtype=subtype,
301                        flags=flags,
302                        vni=vni))
303                else:
304                    communities.append(BGPEvpnEsiLabelExtendedCommunity(
305                                       subtype=subtype,
306                                       flags=flags,
307                                       mpls_label=label_list[0]))
308
309            pattrs[BGP_ATTR_TYPE_EXTENDED_COMMUNITIES] = \
310                BGPPathAttributeExtendedCommunities(communities=communities)
311            if vrf_conf.multi_exit_disc:
312                pattrs[BGP_ATTR_TYPE_MULTI_EXIT_DISC] = \
313                    BGPPathAttributeMultiExitDisc(vrf_conf.multi_exit_disc)
314
315            # Set PMSI Tunnel Attribute
316            pmsi_tunnel_type = kwargs.get('pmsi_tunnel_type', None)
317            if pmsi_tunnel_type is not None:
318                from ryu.services.protocols.bgp.api.prefix import (
319                    PMSI_TYPE_INGRESS_REP)
320                if pmsi_tunnel_type == PMSI_TYPE_INGRESS_REP:
321                    tunnel_id = PmsiTunnelIdIngressReplication(
322                        tunnel_endpoint_ip=self._core_service.router_id)
323                else:  # pmsi_tunnel_type == PMSI_TYPE_NO_TUNNEL_INFO
324                    tunnel_id = None
325                pattrs[BGP_ATTR_TYEP_PMSI_TUNNEL_ATTRIBUTE] = \
326                    BGPPathAttributePmsiTunnel(pmsi_flags=0,
327                                               tunnel_type=pmsi_tunnel_type,
328                                               tunnel_id=tunnel_id,
329                                               vni=kwargs.get('vni', None))
330
331        puid = self.VRF_PATH_CLASS.create_puid(
332            vrf_conf.route_dist, nlri.prefix)
333
334        path = self.VRF_PATH_CLASS(
335            puid, None, nlri, 0, pattrs=pattrs,
336            nexthop=next_hop, label_list=label_list,
337            is_withdraw=is_withdraw
338        )
339
340        # Insert the path into VRF table, get affected destination so that we
341        # can process it further.
342        eff_dest = self.insert(path)
343        # Enqueue the eff_dest for further processing.
344        self._signal_bus.dest_changed(eff_dest)
345        return label_list
346
347    def clean_uninteresting_paths(self, interested_rts=None):
348        if interested_rts is None:
349            interested_rts = set(self.vrf_conf.import_rts)
350        return super(VrfTable, self).clean_uninteresting_paths(interested_rts)
351
352
353@six.add_metaclass(abc.ABCMeta)
354class VrfDest(Destination):
355    """Base class for VRF destination."""
356
357    def __init__(self, table, nlri):
358        super(VrfDest, self).__init__(table, nlri)
359        self._route_dist = self._table.vrf_conf.route_dist
360
361    @property
362    def nlri_str(self):
363        # Returns `prefix` without the route distinguisher value, because
364        # a destination in VRF space can be identified without the route
365        # distinguisher.
366        return self._nlri.prefix
367
368    def _best_path_lost(self):
369        # Have to send update messages for withdraw of best-path to Network
370        # controller or Global table.
371        old_best_path = self._best_path
372        self._best_path = None
373
374        if old_best_path is None:
375            return
376
377        if old_best_path.source is not None:
378            # Send update-withdraw msg. to Sink. Create withdraw path
379            # out of old best path and queue it into flexinet sinks.
380            old_best_path = old_best_path.clone(for_withdrawal=True)
381            self._core_service.update_flexinet_peers(old_best_path,
382                                                     self._route_dist)
383        else:
384            # Create withdraw-path out of old best path.
385            gpath = old_best_path.clone_to_vpn(self._route_dist,
386                                               for_withdrawal=True)
387            # Insert withdraw into global table and enqueue the destination
388            # for further processing.
389            tm = self._core_service.table_manager
390            tm.learn_path(gpath)
391
392    def _new_best_path(self, best_path):
393        LOG.debug('New best path selected for destination %s', self)
394
395        old_best_path = self._best_path
396        assert (best_path != old_best_path)
397        self._best_path = best_path
398        # Distribute new best-path to flexinet-peers.
399        if best_path.source is not None:
400            # Since route-refresh just causes the version number to
401            # go up and this changes best-path, we check if new-
402            # best-path is really different than old-best-path that
403            # warrants sending update to flexinet peers.
404
405            def really_diff():
406                old_labels = old_best_path.label_list
407                new_labels = best_path.label_list
408                return old_best_path.nexthop != best_path.nexthop \
409                    or set(old_labels) != set(new_labels)
410
411            if not old_best_path or (old_best_path and really_diff()):
412                # Create OutgoingRoute and queue it into NC sink.
413                self._core_service.update_flexinet_peers(
414                    best_path, self._route_dist
415                )
416        else:
417            # If NC is source, we create new path and insert into global
418            # table.
419            gpath = best_path.clone_to_vpn(self._route_dist)
420            tm = self._core_service.table_manager
421            tm.learn_path(gpath)
422            LOG.debug('VRF table %s has new best path: %s',
423                      self._route_dist, self.best_path)
424
425    def _remove_withdrawals(self):
426        """Removes withdrawn paths.
427
428        Note:
429        We may have disproportionate number of withdraws compared to know paths
430        since not all paths get installed into the table due to bgp policy and
431        we can receive withdraws for such paths and withdrawals may not be
432        stopped by the same policies.
433        """
434
435        LOG.debug('Removing %s withdrawals', len(self._withdraw_list))
436
437        # If we have not withdrawals, we have nothing to do.
438        if not self._withdraw_list:
439            return
440
441        # If we have some withdrawals and no know-paths, it means it is safe to
442        # delete these withdraws.
443        if not self._known_path_list:
444            LOG.debug('Found %s withdrawals for path(s) that did not get'
445                      ' installed.', len(self._withdraw_list))
446            del (self._withdraw_list[:])
447            return
448
449        # If we have some known paths and some withdrawals, we find matches and
450        # delete them first.
451        matches = []
452        w_matches = []
453        # Match all withdrawals from destination paths.
454        for withdraw in self._withdraw_list:
455            match = None
456            for path in self._known_path_list:
457                # We have a match if the source are same.
458                if path.puid == withdraw.puid:
459                    match = path
460                    matches.append(path)
461                    w_matches.append(withdraw)
462                    # One withdraw can remove only one path.
463                    break
464                # We do no have any match for this withdraw.
465            if not match:
466                LOG.debug('No matching path for withdraw found, may be path '
467                          'was not installed into table: %s',
468                          withdraw)
469            # If we have partial match.
470        if len(matches) != len(self._withdraw_list):
471            LOG.debug('Did not find match for some withdrawals. Number of '
472                      'matches(%s), number of withdrawals (%s)',
473                      len(matches), len(self._withdraw_list))
474
475        # Clear matching paths and withdrawals.
476        for match in matches:
477            self._known_path_list.remove(match)
478        for w_match in w_matches:
479            self._withdraw_list.remove(w_match)
480
481    def _remove_old_paths(self):
482        """Identifies which of known paths are old and removes them.
483
484        Known paths will no longer have paths whose new version is present in
485        new paths.
486        """
487        new_paths = self._new_path_list
488        known_paths = self._known_path_list
489        for new_path in new_paths:
490            old_paths = []
491            for path in known_paths:
492                # Here we just check if source is same and not check if path
493                # version num. as new_paths are implicit withdrawal of old
494                # paths and when doing RouteRefresh (not EnhancedRouteRefresh)
495                # we get same paths again.
496                if new_path.puid == path.puid:
497                    old_paths.append(path)
498                    break
499
500            for old_path in old_paths:
501                known_paths.remove(old_path)
502                LOG.debug('Implicit withdrawal of old path, since we have'
503                          ' learned new path from same source: %s', old_path)
504
505    def _validate_path(self, path):
506        if not path or not hasattr(path, 'label_list'):
507            raise ValueError('Invalid value of path. Expected type '
508                             'with attribute label_list got %s' % path)
509
510
511@six.add_metaclass(abc.ABCMeta)
512class VrfPath(Path):
513    """Represents a way of reaching an IP destination with a VPN.
514    """
515    __slots__ = ('_label_list', '_puid')
516
517    ROUTE_FAMILY = None
518    VPN_PATH_CLASS = None
519    VPN_NLRI_CLASS = None
520
521    def __init__(self, puid, source, nlri, src_ver_num,
522                 pattrs=None, nexthop=None,
523                 is_withdraw=False, label_list=None):
524        """Initializes a Vrf path.
525
526            Parameters:
527                - `puid`: (str) path ID, identifies VPN path from which this
528                VRF path was imported.
529                - `label_list`: (list) List of labels for this path.
530            Note: other parameters are as documented in super class.
531        """
532        if self.ROUTE_FAMILY.safi in [IP_FLOWSPEC, VPN_FLOWSPEC]:
533            nexthop = '0.0.0.0'
534
535        Path.__init__(self, source, nlri, src_ver_num, pattrs, nexthop,
536                      is_withdraw)
537        if label_list is None:
538            label_list = []
539        self._label_list = label_list
540        self._puid = puid
541
542    @property
543    def puid(self):
544        return self._puid
545
546    @property
547    def origin_rd(self):
548        tokens = self.puid.split(':')
549        return tokens[0] + ':' + tokens[1]
550
551    @property
552    def label_list(self):
553        return self._label_list[:]
554
555    @property
556    def nlri_str(self):
557        # Returns `prefix` without the route distinguisher value, because
558        # a destination in VRF space can be identified without the route
559        # distinguisher.
560        return self._nlri.prefix
561
562    @staticmethod
563    def create_puid(route_dist, ip_prefix):
564        assert route_dist and ip_prefix
565        return str(route_dist) + ':' + ip_prefix
566
567    def clone(self, for_withdrawal=False):
568        pathattrs = None
569        if not for_withdrawal:
570            pathattrs = self.pathattr_map
571
572        clone = self.__class__(
573            self.puid,
574            self._source,
575            self.nlri,
576            self.source_version_num,
577            pattrs=pathattrs,
578            nexthop=self.nexthop,
579            is_withdraw=for_withdrawal,
580            label_list=self.label_list
581        )
582        return clone
583
584    def clone_to_vpn(self, route_dist, for_withdrawal=False):
585        if self.ROUTE_FAMILY == RF_L2_EVPN:
586            # Because NLRI class is the same if the route family is EVPN,
587            # we re-use the NLRI instance.
588            vpn_nlri = self._nlri
589
590        elif self.ROUTE_FAMILY.safi in [IP_FLOWSPEC, VPN_FLOWSPEC]:
591            vpn_nlri = self.VPN_NLRI_CLASS(route_dist=route_dist,
592                                           rules=self.nlri.rules)
593
594        else:  # self.ROUTE_FAMILY in [RF_IPv4_UC, RF_IPv6_UC]
595            ip, masklen = self._nlri.prefix.split('/')
596            vpn_nlri = self.VPN_NLRI_CLASS(length=int(masklen),
597                                           addr=ip,
598                                           labels=self.label_list,
599                                           route_dist=route_dist)
600
601        pathattrs = None
602        if not for_withdrawal:
603            pathattrs = self.pathattr_map
604
605        vpnv_path = self.VPN_PATH_CLASS(
606            source=self.source,
607            nlri=vpn_nlri,
608            src_ver_num=self.source_version_num,
609            pattrs=pathattrs,
610            nexthop=self.nexthop,
611            is_withdraw=for_withdrawal)
612
613        return vpnv_path
614
615    def __eq__(self, b_path):
616        if not isinstance(b_path, self.__class__):
617            return False
618        if not self.route_family == b_path.route_family:
619            return False
620        if not self.puid == b_path.puid:
621            return False
622        if not self.label_list == b_path.label_list:
623            return False
624        if not self.nexthop == b_path.nexthop:
625            return False
626        if not self.pathattr_map == b_path.pathattr_map:
627            return False
628
629        return True
630
631
632class ImportMap(object):
633    def match(self, vrf_path):
634        raise NotImplementedError()
635
636
637class VrfNlriImportMap(ImportMap):
638    VRF_PATH_CLASS = None
639    NLRI_CLASS = None
640
641    def __init__(self, prefix):
642        assert self.VRF_PATH_CLASS is not None
643        assert self.NLRI_CLASS is not None
644        self._nlri = self.NLRI_CLASS(prefix)
645
646    def match(self, vrf_path):
647        if vrf_path.route_family != self.VRF_PATH_CLASS.ROUTE_FAMILY:
648            LOG.error(
649                "vrf_paths route_family does not match importmaps"
650                "route_family. Applied to wrong table?")
651            return False
652
653        return vrf_path.nlri == self._nlri
654
655
656class VrfRtImportMap(ImportMap):
657    def __init__(self, rt):
658        self._rt = rt
659
660    def match(self, vrf_path):
661        extcomm = vrf_path.pathattr_map.get(BGP_ATTR_TYPE_EXTENDED_COMMUNITIES)
662        return extcomm is not None and self._rt in extcomm.rt_list
663