1 # Copyright (C) 2014 Nippon Telegraph and Telephone Corporation.
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
16 Defines some model classes related BGP.
18 These class include types used in saving information sent/received over BGP
22 from abc import ABCMeta
23 from abc import abstractmethod
30 from ryu.lib.packet.bgp import RF_IPv4_UC
31 from ryu.lib.packet.bgp import RouteTargetMembershipNLRI
32 from ryu.lib.packet.bgp import BGP_ATTR_TYPE_EXTENDED_COMMUNITIES
33 from ryu.lib.packet.bgp import BGPPathAttributeLocalPref
34 from ryu.lib.packet.bgp import BGP_ATTR_TYPE_AS_PATH
36 from ryu.services.protocols.bgp.base import OrderedDict
37 from ryu.services.protocols.bgp.constants import VPN_TABLE
38 from ryu.services.protocols.bgp.constants import VRF_TABLE
39 from ryu.services.protocols.bgp.model import OutgoingRoute
40 from ryu.services.protocols.bgp.processor import BPR_ONLY_PATH
41 from ryu.services.protocols.bgp.processor import BPR_UNKNOWN
44 LOG = logging.getLogger('bgpspeaker.info_base.base')
47 @six.add_metaclass(ABCMeta)
49 """A container for holding information about destination/prefixes.
51 Routing information base for a particular afi/safi.
52 This is a base class which should be sub-classed for different route
53 family. A table can be uniquely identified by (Route Family, Scope Id).
55 ROUTE_FAMILY = RF_IPv4_UC
57 def __init__(self, scope_id, core_service, signal_bus):
58 self._destinations = dict()
59 # Scope in which this table exists.
60 # If this table represents the VRF, then this could be a VPN ID.
61 # For global/VPN tables this should be None
62 self._scope_id = scope_id
63 self._signal_bus = signal_bus
64 self._core_service = core_service
67 def route_family(self):
68 return self.__class__.ROUTE_FAMILY
71 def core_service(self):
72 return self._core_service
79 def _create_dest(self, nlri):
80 """Creates destination specific for this table.
81 Returns destination that stores information of paths to *nlri*.
83 raise NotImplementedError()
86 return iter(self._destinations.values())
88 def insert(self, path):
89 self._validate_path(path)
90 self._validate_nlri(path.nlri)
92 updated_dest = self._insert_withdraw(path)
94 updated_dest = self._insert_path(path)
97 def insert_sent_route(self, sent_route):
98 self._validate_path(sent_route.path)
99 dest = self._get_or_create_dest(sent_route.path.nlri)
100 dest.add_sent_route(sent_route)
102 def _insert_path(self, path):
103 """Add new path to destination identified by given prefix.
105 assert path.is_withdraw is False
106 dest = self._get_or_create_dest(path.nlri)
107 # Add given path to matching Dest.
108 dest.add_new_path(path)
109 # Return updated destination.
112 def _insert_withdraw(self, path):
113 """Appends given path to withdraw list of Destination for given prefix.
115 assert path.is_withdraw is True
116 dest = self._get_or_create_dest(path.nlri)
117 # Add given path to matching destination.
118 dest.add_withdraw(path)
119 # Return updated destination.
122 def cleanup_paths_for_peer(self, peer):
123 """Remove old paths from whose source is `peer`
125 Old paths have source version number that is less than current peer
126 version number. Also removes sent paths to this peer.
128 LOG.debug('Cleaning paths from table %s for peer %s', self, peer)
129 for dest in self.values():
130 # Remove paths learned from this source
131 paths_deleted = dest.remove_old_paths_from_source(peer)
132 # Remove sent paths to this peer
133 had_sent = dest.remove_sent_route(peer)
135 LOG.debug('Removed sent route %s for %s', dest.nlri, peer)
136 # If any paths are removed we enqueue respective destination for
139 self._signal_bus.dest_changed(dest)
141 def clean_uninteresting_paths(self, interested_rts):
142 """Cleans table of any path that do not have any RT in common
143 with `interested_rts`.
145 - `interested_rts`: (set) of RT that are of interest/that need to
148 LOG.debug('Cleaning table %s for given interested RTs %s',
149 self, interested_rts)
150 uninteresting_dest_count = 0
151 for dest in self.values():
153 dest.withdraw_uninteresting_paths(interested_rts)
155 self._signal_bus.dest_changed(dest)
156 uninteresting_dest_count += 1
157 return uninteresting_dest_count
159 def delete_dest_by_nlri(self, nlri):
160 """Deletes the destination identified by given prefix.
162 Returns the deleted destination if a match is found. If not match is
165 self._validate_nlri(nlri)
166 dest = self._get_dest(nlri)
168 self._destinations.pop(dest)
171 def delete_dest(self, dest):
172 del self._destinations[self._table_key(dest.nlri)]
174 def _validate_nlri(self, nlri):
175 """Validated *nlri* is the type that this table stores/supports.
177 if not nlri or not (nlri.ROUTE_FAMILY == self.route_family):
178 raise ValueError('Invalid Vpnv4 prefix given.')
180 def _validate_path(self, path):
181 """Check if given path is an instance of *Path*.
183 Raises ValueError if given is not a instance of *Path*.
185 if not path or not (path.route_family == self.route_family):
186 raise ValueError('Invalid path. Expected instance of'
187 ' Vpnv4 route family path, got %s.' % path)
189 def _get_or_create_dest(self, nlri):
190 table_key = self._table_key(nlri)
191 dest = self._destinations.get(table_key)
192 # If destination for given prefix does not exist we create it.
194 dest = self._create_dest(nlri)
195 self._destinations[table_key] = dest
198 def _get_dest(self, nlri):
199 table_key = self._table_key(nlri)
200 dest = self._destinations.get(table_key)
203 def is_for_vrf(self):
204 """Returns true if this table instance represents a VRF.
206 return self.scope_id is not None
209 return 'Table(scope_id: %s, rf: %s)' % (self.scope_id,
213 def _table_key(self, nlri):
214 """Return a key that will uniquely identify this NLRI inside
217 raise NotImplementedError()
220 class NonVrfPathProcessingMixin(object):
221 """Mixin reacting to best-path selection algorithm on main table
222 level. Intended to use with "Destination" subclasses.
223 Applies to most of Destinations except for VrfDest
224 because they are processed at VRF level, so different logic applies.
228 self._core_service = None # not assigned yet
229 self._known_path_list = []
231 def _best_path_lost(self):
232 self._best_path = None
234 if self._sent_routes:
235 # We have to send update-withdraw to all peers to whom old best
237 for sent_route in self._sent_routes.values():
238 sent_path = sent_route.path
239 withdraw_clone = sent_path.clone(for_withdrawal=True)
240 outgoing_route = OutgoingRoute(withdraw_clone)
241 sent_route.sent_peer.enque_outgoing_msg(outgoing_route)
242 LOG.debug('Sending withdrawal to %s for %s',
243 sent_route.sent_peer, outgoing_route)
245 # Have to clear sent_route list for this destination as
246 # best path is removed.
247 self._sent_routes = {}
249 def _new_best_path(self, new_best_path):
250 old_best_path = self._best_path
251 self._best_path = new_best_path
252 LOG.debug('New best path selected for destination %s', self)
254 # If old best path was withdrawn
255 if (old_best_path and
256 old_best_path not in self._known_path_list and
258 # Have to clear sent_route list for this destination as
259 # best path is removed.
260 self._sent_routes = {}
262 # Communicate that we have new best path to all qualifying
264 pm = self._core_service.peer_manager
265 pm.comm_new_best_to_bgp_peers(new_best_path)
267 # withdraw old best path
268 if old_best_path and self._sent_routes:
269 for sent_route in self._sent_routes.values():
270 sent_path = sent_route.path
271 withdraw_clone = sent_path.clone(for_withdrawal=True)
272 outgoing_route = OutgoingRoute(withdraw_clone)
273 sent_route.sent_peer.enque_outgoing_msg(outgoing_route)
274 LOG.debug('Sending withdrawal to %s for %s',
275 sent_route.sent_peer, outgoing_route)
276 self._sent_routes = {}
279 @six.add_metaclass(ABCMeta)
280 class Destination(object):
281 """State about a particular destination.
283 For example, an IP prefix. This is the data-structure that is hung of the
284 a routing information base table *Table*.
287 ROUTE_FAMILY = RF_IPv4_UC
289 def __init__(self, table, nlri):
290 # Validate arguments.
291 if table.route_family != self.__class__.ROUTE_FAMILY:
292 raise ValueError('Table and destination route family '
295 # Back-pointer to the table that contains this destination.
298 self._core_service = table.core_service
302 # List of all known processed paths,
303 self._known_path_list = []
305 # List of new un-processed paths.
306 self._new_path_list = []
308 # Pointer to best-path. One from the the known paths.
309 self._best_path = None
311 # Reason current best path was chosen as best path.
312 self._best_path_reason = None
314 # List of withdrawn paths.
315 self._withdraw_list = []
317 # List of SentRoute objects. This is the Adj-Rib-Out for this
318 # destination. (key/value: peer/sent_route)
319 self._sent_routes = {}
321 # This is an (optional) list of paths that were created as a
322 # result of exporting this route to other tables.
323 # self.exported_paths = None
325 # Automatically generated
327 # On work queue for BGP processor.
328 # self.next_dest_to_process
329 # self.prev_dest_to_process
332 def route_family(self):
333 return self.__class__.ROUTE_FAMILY
341 return self._nlri.formatted_nlri_str
345 return self._best_path
348 def best_path_reason(self):
349 return self._best_path_reason
352 def known_path_list(self):
353 return self._known_path_list[:]
356 def sent_routes(self):
357 return list(self._sent_routes.values())
359 def add_new_path(self, new_path):
360 self._validate_path(new_path)
361 self._new_path_list.append(new_path)
363 def add_withdraw(self, withdraw):
364 self._validate_path(withdraw)
365 self._withdraw_list.append(withdraw)
367 def add_sent_route(self, sent_route):
368 self._sent_routes[sent_route.sent_peer] = sent_route
370 def remove_sent_route(self, peer):
371 if self.was_sent_to(peer):
372 del self._sent_routes[peer]
376 def was_sent_to(self, peer):
377 if peer in self._sent_routes.keys():
382 """Calculate best path for this destination.
384 A destination is processed when known paths to this destination has
385 changed. We might have new paths or withdrawals of last known paths.
386 Removes withdrawals and adds new learned paths from known path list.
387 Uses bgp best-path calculation algorithm on new list of known paths to
388 choose new best-path. Communicates best-path to core service.
390 LOG.debug('Processing destination: %s', self)
391 new_best_path, reason = self._process_paths()
392 self._best_path_reason = reason
394 if self._best_path == new_best_path:
397 if new_best_path is None:
399 assert not self._known_path_list, repr(self._known_path_list)
400 return self._best_path_lost()
402 return self._new_best_path(new_best_path)
405 def _best_path_lost(self):
406 raise NotImplementedError()
409 def _new_best_path(self, new_best_path):
410 raise NotImplementedError()
413 def _validate_path(cls, path):
414 if not path or path.route_family != cls.ROUTE_FAMILY:
416 'Invalid path. Expected %s path got %s' %
417 (cls.ROUTE_FAMILY, path)
422 if not self._known_path_list and not self._best_path:
423 self._remove_dest_from_table()
425 def _remove_dest_from_table(self):
426 self._table.delete_dest(self)
428 def remove_old_paths_from_source(self, source):
429 """Removes known old paths from *source*.
431 Returns *True* if any of the known paths were found to be old and
434 assert(source and hasattr(source, 'version_num'))
436 # Iterate over the paths in reverse order as we want to delete paths
437 # whose source is this peer.
438 source_ver_num = source.version_num
439 for path_idx in range(len(self._known_path_list) - 1, -1, -1):
440 path = self._known_path_list[path_idx]
441 if (path.source == source and
442 path.source_version_num < source_ver_num):
443 # If this peer is source of any paths, remove those path.
444 del(self._known_path_list[path_idx])
445 removed_paths.append(path)
448 def withdraw_if_sent_to(self, peer):
449 """Sends a withdraw for this destination to given `peer`.
451 Check the records if we indeed advertise this destination to given peer
452 and if so, creates a withdraw for advertised route and sends it to the
455 - `peer`: (Peer) peer to send withdraw to
457 from ryu.services.protocols.bgp.peer import Peer
458 if not isinstance(peer, Peer):
459 raise TypeError('Currently we only support sending withdrawal'
460 ' to instance of peer')
461 sent_route = self._sent_routes.pop(peer, None)
465 sent_path = sent_route.path
466 withdraw_clone = sent_path.clone(for_withdrawal=True)
467 outgoing_route = OutgoingRoute(withdraw_clone)
468 sent_route.sent_peer.enque_outgoing_msg(outgoing_route)
471 def _process_paths(self):
472 """Calculates best-path among known paths for this destination.
477 Modifies destination's state related to stored paths. Removes withdrawn
478 paths from known paths. Also, adds new paths to known paths.
480 # First remove the withdrawn paths.
481 # Note: If we want to support multiple paths per destination we may
482 # have to maintain sent-routes per path.
483 self._remove_withdrawals()
485 # Have to select best-path from available paths and new paths.
486 # If we do not have any paths, then we no longer have best path.
487 if not self._known_path_list and len(self._new_path_list) == 1:
488 # If we do not have any old but one new path
489 # it becomes best path.
490 self._known_path_list.append(self._new_path_list[0])
491 del(self._new_path_list[0])
492 return self._known_path_list[0], BPR_ONLY_PATH
494 # If we have a new version of old/known path we use it and delete old
496 self._remove_old_paths()
498 # Collect all new paths into known paths.
499 self._known_path_list.extend(self._new_path_list)
501 # Clear new paths as we copied them.
502 del(self._new_path_list[:])
504 # If we do not have any paths to this destination, then we do not have
506 if not self._known_path_list:
507 return None, BPR_UNKNOWN
509 # Compute new best path
510 current_best_path, reason = self._compute_best_known_path()
511 return current_best_path, reason
513 def _remove_withdrawals(self):
514 """Removes withdrawn paths.
517 We may have disproportionate number of withdraws compared to know paths
518 since not all paths get installed into the table due to bgp policy and
519 we can receive withdraws for such paths and withdrawals may not be
520 stopped by the same policies.
523 LOG.debug('Removing %s withdrawals', len(self._withdraw_list))
525 # If we have no withdrawals, we have nothing to do.
526 if not self._withdraw_list:
529 # If we have some withdrawals and no know-paths, it means it is safe to
530 # delete these withdraws.
531 if not self._known_path_list:
532 LOG.debug('Found %s withdrawals for path(s) that did not get'
533 ' installed.', len(self._withdraw_list))
534 del(self._withdraw_list[:])
537 # If we have some known paths and some withdrawals, we find matches and
541 # Match all withdrawals from destination paths.
542 for withdraw in self._withdraw_list:
544 for path in self._known_path_list:
545 # We have a match if the source are same.
546 if path.source == withdraw.source:
549 w_matches.add(withdraw)
550 # One withdraw can remove only one path.
552 # We do no have any match for this withdraw.
554 LOG.debug('No matching path for withdraw found, may be path '
555 'was not installed into table: %s',
557 # If we have partial match.
558 if len(matches) != len(self._withdraw_list):
559 LOG.debug('Did not find match for some withdrawals. Number of '
560 'matches(%s), number of withdrawals (%s)',
561 len(matches), len(self._withdraw_list))
563 # Clear matching paths and withdrawals.
564 for match in matches:
565 self._known_path_list.remove(match)
566 for w_match in w_matches:
567 self._withdraw_list.remove(w_match)
569 def _remove_old_paths(self):
570 """Identifies which of known paths are old and removes them.
572 Known paths will no longer have paths whose new version is present in
575 new_paths = self._new_path_list
576 known_paths = self._known_path_list
577 for new_path in new_paths:
579 for path in known_paths:
580 # Here we just check if source is same and not check if path
581 # version num. as new_paths are implicit withdrawal of old
582 # paths and when doing RouteRefresh (not EnhancedRouteRefresh)
583 # we get same paths again.
584 if new_path.source == path.source:
585 old_paths.append(path)
588 for old_path in old_paths:
589 known_paths.remove(old_path)
590 LOG.debug('Implicit withdrawal of old path, since we have'
591 ' learned new path from same source: %s', old_path)
593 def _compute_best_known_path(self):
594 """Computes the best path among known paths.
596 Returns current best path among `known_paths`.
598 if not self._known_path_list:
599 from ryu.services.protocols.bgp.processor import BgpProcessorError
600 raise BgpProcessorError(desc='Need at-least one known path to'
601 ' compute best path')
603 # We pick the first path as current best path. This helps in breaking
604 # tie between two new paths learned in one cycle for which best-path
605 # calculation steps lead to tie.
606 current_best_path = self._known_path_list[0]
607 best_path_reason = BPR_ONLY_PATH
608 for next_path in self._known_path_list[1:]:
609 from ryu.services.protocols.bgp.processor import compute_best_path
610 # Compare next path with current best path.
611 new_best_path, reason = \
612 compute_best_path(self._core_service.asn, current_best_path,
614 best_path_reason = reason
615 if new_best_path is not None:
616 current_best_path = new_best_path
618 return current_best_path, best_path_reason
620 def withdraw_uninteresting_paths(self, interested_rts):
621 """Withdraws paths that are no longer interesting.
623 For all known paths that do not have any route target in common with
624 given `interested_rts` we add a corresponding withdraw.
626 Returns True if we added any withdraws.
628 add_withdraws = False
629 for path in self._known_path_list:
630 if not path.has_rts_in(interested_rts):
631 self.withdraw_path(path)
635 def withdraw_path(self, path):
636 if path not in self.known_path_list:
637 raise ValueError("Path not known, no need to withdraw")
638 withdraw = path.clone(for_withdrawal=True)
639 self._withdraw_list.append(withdraw)
642 return {'table': str(self._table),
643 'nlri': str(self._nlri),
644 'paths': self._known_path_list[:],
645 'withdraws': self._get_num_withdraws()}
648 return ('Destination(table: %s, nlri: %s, paths: %s, withdraws: %s,'
649 ' new paths: %s)' % (self._table, str(self._nlri),
650 len(self._known_path_list),
651 len(self._withdraw_list),
652 len(self._new_path_list)))
654 def _get_num_valid_paths(self):
655 return len(self._known_path_list)
657 def _get_num_withdraws(self):
658 return len(self._withdraw_list)
660 def sent_routes_by_peer(self, peer):
661 """get sent routes corresponding to specified peer.
663 Returns SentRoute list.
666 for route in self._sent_routes.values():
667 if route.sent_peer == peer:
672 def __lt__(self, other):
673 return str(self) < str(other)
675 def __le__(self, other):
676 return str(self) <= str(other)
678 def __eq__(self, other):
679 return str(self) == str(other)
681 def __ne__(self, other):
682 return str(self) != str(other)
684 def __gt__(self, other):
685 return str(self) > str(other)
687 def __ge__(self, other):
688 return str(self) >= str(other)
691 @six.add_metaclass(ABCMeta)
693 """Represents a way of reaching an IP destination.
695 Also contains other meta-data given to us by a specific source (such as a
698 __slots__ = ('_source', '_path_attr_map', '_nlri', '_source_version_num',
699 '_exported_from', '_nexthop', 'next_path', 'prev_path',
700 '_is_withdraw', 'med_set_by_target_neighbor')
701 ROUTE_FAMILY = RF_IPv4_UC
703 def __init__(self, source, nlri, src_ver_num, pattrs=None, nexthop=None,
704 is_withdraw=False, med_set_by_target_neighbor=False):
705 """Initializes Ipv4 path.
707 If this path is not a withdraw, then path attribute and nexthop both
710 - `source`: (Peer/str) source of this path.
711 - `nlri`: (Vpnv4) Nlri instance for Vpnv4 route family.
712 - `src_ver_num`: (int) version number of *source* when this path
714 - `pattrs`: (OrderedDict) various path attributes for this path.
715 - `nexthop`: (str) nexthop advertised for this path.
716 - `is_withdraw`: (bool) True if this represents a withdrawal.
718 self.med_set_by_target_neighbor = med_set_by_target_neighbor
719 if nlri.ROUTE_FAMILY != self.__class__.ROUTE_FAMILY:
720 raise ValueError('NLRI and Path route families do not'
722 (nlri.ROUTE_FAMILY, self.__class__.ROUTE_FAMILY))
724 # Currently paths injected directly into VRF has only one source
725 # src_peer can be None to denote NC else has to be instance of Peer.
726 # Paths can be exported from one VRF and then imported into another
727 # VRF, in such cases it source is denoted as string VPN_TABLE.
728 if not (source is None or
729 hasattr(source, 'version_num') or
730 source in (VRF_TABLE, VPN_TABLE)):
731 raise ValueError('Invalid or Unsupported source for path: %s' %
734 # If this path is not a withdraw path, than it should have path-
735 # attributes and nexthop.
736 if not is_withdraw and not (pattrs and nexthop):
737 raise ValueError('Need to provide nexthop and patattrs '
738 'for path that is not a withdraw.')
740 # The entity (peer) that gave us this path.
741 self._source = source
743 # Path attribute of this path.
745 self._path_attr_map = copy(pattrs)
747 self._path_attr_map = OrderedDict()
749 # NLRI that this path represents.
752 # If given nlri is withdrawn.
753 self._is_withdraw = is_withdraw
755 # @see Source.version_num
756 self._source_version_num = src_ver_num
758 self._nexthop = nexthop
760 # Automatically generated.
765 # The Destination from which this path was exported, if any.
766 self._exported_from = None
769 def source_version_num(self):
770 return self._source_version_num
777 def route_family(self):
778 return self.__class__.ROUTE_FAMILY
786 return self._nlri.formatted_nlri_str
789 def is_withdraw(self):
790 return self._is_withdraw
793 def pathattr_map(self):
794 return copy(self._path_attr_map)
800 def get_pattr(self, pattr_type, default=None):
801 """Returns path attribute of given type.
803 Returns None if we do not attribute of type *pattr_type*.
805 return self._path_attr_map.get(pattr_type, default)
807 def clone(self, for_withdrawal=False):
809 if not for_withdrawal:
810 pathattrs = self.pathattr_map
811 clone = self.__class__(
814 self.source_version_num,
816 nexthop=self.nexthop,
817 is_withdraw=for_withdrawal
822 extcomm_attr = self._path_attr_map.get(
823 BGP_ATTR_TYPE_EXTENDED_COMMUNITIES)
824 if extcomm_attr is None:
827 rts = extcomm_attr.rt_list
830 def has_rts_in(self, interested_rts):
831 """Returns True if this `Path` has any `ExtCommunity` attribute
832 route target common with `interested_rts`.
834 assert isinstance(interested_rts, set)
835 curr_rts = self.get_rts()
836 # Add default RT to path RTs so that we match interest for peers who
837 # advertised default RT
838 curr_rts.append(RouteTargetMembershipNLRI.DEFAULT_RT)
840 return not interested_rts.isdisjoint(curr_rts)
843 return self._source is None
845 def has_nexthop(self):
846 return self._nexthop and self._nexthop not in ('0.0.0.0', '::')
850 'Path(source: %s, nlri: %s, source ver#: %s, '
851 'path attrs.: %s, nexthop: %s, is_withdraw: %s)' %
853 self._source, self._nlri, self._source_version_num,
854 self._path_attr_map, self._nexthop, self._is_withdraw
859 return ('Path(%s, %s, %s, %s, %s, %s)' % (
860 self._source, self._nlri, self._source_version_num,
861 self._path_attr_map, self._nexthop, self._is_withdraw))
864 @six.add_metaclass(ABCMeta)
865 class Filter(object):
866 """Represents a general filter for in-bound and out-bound filter
868 ================ ==================================================
869 Attribute Description
870 ================ ==================================================
871 policy Filter.POLICY_PERMIT or Filter.POLICY_DENY
872 ================ ==================================================
875 ROUTE_FAMILY = RF_IPv4_UC
880 def __init__(self, policy=POLICY_DENY):
881 self._policy = policy
888 def evaluate(self, path):
889 """ This method evaluates the path.
891 Returns this object's policy and the result of matching.
892 If the specified prefix matches this object's prefix and
894 this method returns True as the matching result.
896 ``path`` specifies the path. prefix must be string.
898 raise NotImplementedError()
902 """ This method clones Filter object.
904 Returns Filter object that has the same values with the original one.
906 raise NotImplementedError()
909 @functools.total_ordering
910 class PrefixFilter(Filter):
912 Used to specify a prefix for filter.
914 We can create PrefixFilter object as follows::
916 prefix_filter = PrefixFilter('10.5.111.0/24',
917 policy=PrefixFilter.POLICY_PERMIT)
919 ================ ==================================================
920 Attribute Description
921 ================ ==================================================
922 prefix A prefix used for this filter
923 policy One of the following values.
925 | PrefixFilter.POLICY.PERMIT
926 | PrefixFilter.POLICY_DENY
927 ge Prefix length that will be applied to this filter.
928 ge means greater than or equal.
929 le Prefix length that will be applied to this filter.
930 le means less than or equal.
931 ================ ==================================================
933 For example, when PrefixFilter object is created as follows::
935 p = PrefixFilter('10.5.111.0/24',
936 policy=PrefixFilter.POLICY_DENY,
939 Prefixes which match 10.5.111.0/24 and its length matches
940 from 26 to 28 will be filtered.
941 When this filter is used as an out-filter, it will stop sending
942 the path to neighbor because of POLICY_DENY.
943 When this filter is used as in-filter, it will stop importing the path
944 to the global rib because of POLICY_DENY.
945 If you specify POLICY_PERMIT, the path is sent to neighbor or imported to
948 If you don't want to send prefixes 10.5.111.64/26 and 10.5.111.32/27
949 and 10.5.111.16/28, and allow to send other 10.5.111.0's prefixes,
950 you can do it by specifying as follows::
952 p = PrefixFilter('10.5.111.0/24',
953 policy=PrefixFilter.POLICY_DENY,
957 def __init__(self, prefix, policy, ge=None, le=None):
958 super(PrefixFilter, self).__init__(policy)
959 self._prefix = prefix
960 self._network = netaddr.IPNetwork(prefix)
964 def __lt__(self, other):
965 return self._network < other._network
967 def __eq__(self, other):
968 return self._network == other._network
972 if self._policy == self.POLICY_PERMIT else 'DENY'
974 return 'PrefixFilter(prefix=%s,policy=%s,ge=%s,le=%s)'\
975 % (self._prefix, policy, self._ge, self._le)
993 def evaluate(self, path):
994 """ This method evaluates the prefix.
996 Returns this object's policy and the result of matching.
997 If the specified prefix matches this object's prefix and
999 this method returns True as the matching result.
1001 ``path`` specifies the path that has prefix.
1006 length = nlri.length
1007 net = netaddr.IPNetwork(nlri.prefix)
1009 if net in self._network:
1010 if self._ge is None and self._le is None:
1013 elif self._ge is None and self._le:
1014 if length <= self._le:
1017 elif self._ge and self._le is None:
1018 if self._ge <= length:
1021 elif self._ge and self._le:
1022 if self._ge <= length <= self._le:
1025 return self.policy, result
1028 """ This method clones PrefixFilter object.
1030 Returns PrefixFilter object that has the same values with the
1034 return self.__class__(self.prefix,
1035 policy=self._policy,
1040 @functools.total_ordering
1041 class ASPathFilter(Filter):
1043 Used to specify a prefix for AS_PATH attribute.
1045 We can create ASPathFilter object as follows::
1047 as_path_filter = ASPathFilter(65000,policy=ASPathFilter.TOP)
1049 ================ ==================================================
1050 Attribute Description
1051 ================ ==================================================
1052 as_number A AS number used for this filter
1053 policy One of the following values.
1055 | ASPathFilter.POLICY_TOP
1056 | ASPathFilter.POLICY_END
1057 | ASPathFilter.POLICY_INCLUDE
1058 | ASPathFilter.POLICY_NOT_INCLUDE
1059 ================ ==================================================
1061 Meaning of each policy is as follows:
1063 ================== ==================================================
1065 ================== ==================================================
1066 POLICY_TOP Filter checks if the specified AS number
1067 is at the top of AS_PATH attribute.
1068 POLICY_END Filter checks is the specified AS number
1069 is at the last of AS_PATH attribute.
1070 POLICY_INCLUDE Filter checks if specified AS number exists
1071 in AS_PATH attribute.
1072 POLICY_NOT_INCLUDE Opposite to POLICY_INCLUDE.
1073 ================== ==================================================
1079 POLICY_NOT_INCLUDE = 5
1081 def __init__(self, as_number, policy):
1082 super(ASPathFilter, self).__init__(policy)
1083 self._as_number = as_number
1085 def __lt__(self, other):
1086 return self.as_number < other.as_number
1088 def __eq__(self, other):
1089 return self.as_number == other.as_number
1093 if self._policy == self.POLICY_INCLUDE:
1095 elif self._policy == self.POLICY_NOT_INCLUDE:
1096 policy = 'NOT_INCLUDE'
1097 elif self._policy == self.POLICY_END:
1100 return 'ASPathFilter(as_number=%s,policy=%s)'\
1101 % (self._as_number, policy)
1104 def as_number(self):
1105 return self._as_number
1111 def evaluate(self, path):
1112 """ This method evaluates as_path list.
1114 Returns this object's policy and the result of matching.
1115 If the specified AS number matches this object's AS number
1116 according to the policy,
1117 this method returns True as the matching result.
1119 ``path`` specifies the path.
1122 path_aspath = path.pathattr_map.get(BGP_ATTR_TYPE_AS_PATH)
1123 path_seg_list = path_aspath.path_seg_list
1125 path_seg = path_seg_list[0]
1130 LOG.debug("path_seg : %s", path_seg)
1131 if self.policy == ASPathFilter.POLICY_TOP:
1133 if len(path_seg) > 0 and path_seg[0] == self._as_number:
1136 elif self.policy == ASPathFilter.POLICY_INCLUDE:
1137 for aspath in path_seg:
1138 LOG.debug("POLICY_INCLUDE as_number : %s", aspath)
1139 if aspath == self._as_number:
1143 elif self.policy == ASPathFilter.POLICY_END:
1145 if len(path_seg) > 0 and path_seg[-1] == self._as_number:
1148 elif self.policy == ASPathFilter.POLICY_NOT_INCLUDE:
1150 if self._as_number not in path_seg:
1153 return self.policy, result
1156 """ This method clones ASPathFilter object.
1158 Returns ASPathFilter object that has the same values with the
1162 return self.__class__(self._as_number,
1163 policy=self._policy)
1166 class AttributeMap(object):
1168 This class is used to specify an attribute to add if the path matches
1170 We can create AttributeMap object as follows::
1172 pref_filter = PrefixFilter('192.168.103.0/30',
1173 PrefixFilter.POLICY_PERMIT)
1175 attribute_map = AttributeMap([pref_filter],
1176 AttributeMap.ATTR_LOCAL_PREF, 250)
1178 speaker.attribute_map_set('192.168.50.102', [attribute_map])
1180 AttributeMap.ATTR_LOCAL_PREF means that 250 is set as a
1181 local preference value if nlri in the path matches pref_filter.
1183 ASPathFilter is also available as a filter. ASPathFilter checks if AS_PATH
1184 attribute in the path matches AS number in the filter.
1186 =================== ==================================================
1187 Attribute Description
1188 =================== ==================================================
1189 filters A list of filter.
1190 Each object should be a Filter class or its sub-class
1191 attr_type A type of attribute to map on filters. Currently
1192 AttributeMap.ATTR_LOCAL_PREF is available.
1193 attr_value A attribute value
1194 =================== ==================================================
1197 ATTR_LOCAL_PREF = '_local_pref'
1199 def __init__(self, filters, attr_type, attr_value):
1201 assert all(isinstance(f, Filter) for f in filters),\
1202 'all the items in filters must be an instance of Filter sub-class'
1203 self.filters = filters
1204 self.attr_type = attr_type
1205 self.attr_value = attr_value
1207 def evaluate(self, path):
1208 """ This method evaluates attributes of the path.
1210 Returns the cause and result of matching.
1211 Both cause and result are returned from filters
1212 that this object contains.
1214 ``path`` specifies the path.
1219 for f in self.filters:
1221 cause, result = f.evaluate(path)
1225 return cause, result
1227 def get_attribute(self):
1228 func = getattr(self, 'get' + self.attr_type)
1231 def get_local_pref(self):
1232 local_pref_attr = BGPPathAttributeLocalPref(value=self.attr_value)
1233 return local_pref_attr
1236 """ This method clones AttributeMap object.
1238 Returns AttributeMap object that has the same values with the
1242 cloned_filters = [f.clone() for f in self.filters]
1243 return self.__class__(cloned_filters, self.attr_type, self.attr_value)
1247 attr_type = 'LOCAL_PREF'\
1248 if self.attr_type == self.ATTR_LOCAL_PREF else None
1250 filter_string = ','.join(repr(f) for f in self.filters)
1251 return ('AttributeMap(filters=[%s],'
1252 'attribute_type=%s,'
1253 'attribute_value=%s)' % (filter_string,