diff --git a/doc/source/library_bgp_speaker_ref.rst b/doc/source/library_bgp_speaker_ref.rst index 86f520ee..ac3a3be2 100644 --- a/doc/source/library_bgp_speaker_ref.rst +++ b/doc/source/library_bgp_speaker_ref.rst @@ -13,3 +13,9 @@ BGPSpeaker class .. autoclass:: ryu.services.protocols.bgp.info_base.base.PrefixFilter :members: + +.. autoclass:: ryu.services.protocols.bgp.info_base.base.ASPathFilter + :members: + +.. autoclass:: ryu.services.protocols.bgp.info_base.base.AttributeMap + :members: diff --git a/ryu/services/protocols/bgp/api/rtconf.py b/ryu/services/protocols/bgp/api/rtconf.py index c11918c7..ce989037 100644 --- a/ryu/services/protocols/bgp/api/rtconf.py +++ b/ryu/services/protocols/bgp/api/rtconf.py @@ -150,10 +150,30 @@ def set_neighbor_in_filter(neigh_ip_address, filters): return True +@RegisterWithArgChecks(name='neighbor.attribute_map.set', + req_args=[neighbors.IP_ADDRESS, + neighbors.ATTRIBUTE_MAP]) +def set_neighbor_attribute_map(neigh_ip_address, attribute_maps): + """Returns a neighbor attribute_map for given ip address if exists.""" + core = CORE_MANAGER.get_core_service() + peer = core.peer_manager.get_by_addr(neigh_ip_address) + peer.attribute_maps = attribute_maps + return True + + +@RegisterWithArgChecks(name='neighbor.attribute_map.get', + req_args=[neighbors.IP_ADDRESS]) +def get_neighbor_attribute_map(neigh_ip_address): + """Returns a neighbor attribute_map for given ip address if exists.""" + core = CORE_MANAGER.get_core_service() + ret = core.peer_manager.get_by_addr(neigh_ip_address).attribute_maps + return ret + # ============================================================================= # VRF configuration related APIs # ============================================================================= + @register(name='vrf.create') def create_vrf(**kwargs): vrf_conf = VrfConf(**kwargs) diff --git a/ryu/services/protocols/bgp/bgpspeaker.py b/ryu/services/protocols/bgp/bgpspeaker.py index 3cba1f82..9844e087 100644 --- a/ryu/services/protocols/bgp/bgpspeaker.py +++ b/ryu/services/protocols/bgp/bgpspeaker.py @@ -526,6 +526,50 @@ class BGPSpeaker(object): param['port'] = port call(func_name, **param) + def attribute_map_set(self, address, attribute_maps): + """This method sets attribute mapping to a neighbor. + attribute mapping can be used when you want to apply + attribute to BGPUpdate under specific conditions. + + ``address`` specifies the IP address of the neighbor + + ``attribute_maps`` specifies attribute_map list that are used + before paths are advertised. All the items in the list must + be an instance of AttributeMap class + + We can set AttributeMap to a neighbor as follows; + + pref_filter = PrefixFilter('192.168.103.0/30', + PrefixFilter.POLICY_PERMIT) + + attribute_map = AttributeMap([pref_filter], + AttributeMap.ATTR_LOCAL_PREF, 250) + + speaker.attribute_map_set('192.168.50.102', [attribute_map]) + + """ + + func_name = 'neighbor.attribute_map.set' + param = {} + param[neighbors.IP_ADDRESS] = address + param[neighbors.ATTRIBUTE_MAP] = attribute_maps + call(func_name, **param) + + def attribute_map_get(self, address): + """This method gets in-bound filters of the specified neighbor. + + ``address`` specifies the IP address of the neighbor. + + Returns a list object containing an instance of AttributeMap + + """ + + func_name = 'neighbor.attribute_map.get' + param = {} + param[neighbors.IP_ADDRESS] = address + attribute_maps = call(func_name, **param) + return attribute_maps + @staticmethod def _check_rf_and_normalize(prefix): """ check prefix's route_family and if the address is diff --git a/ryu/services/protocols/bgp/info_base/base.py b/ryu/services/protocols/bgp/info_base/base.py index 79bba2cb..13f02caf 100644 --- a/ryu/services/protocols/bgp/info_base/base.py +++ b/ryu/services/protocols/bgp/info_base/base.py @@ -28,6 +28,8 @@ import netaddr from ryu.lib.packet.bgp import RF_IPv4_UC from ryu.lib.packet.bgp import RouteTargetMembershipNLRI from ryu.lib.packet.bgp import BGP_ATTR_TYPE_EXTENDED_COMMUNITIES +from ryu.lib.packet.bgp import BGPPathAttributeLocalPref +from ryu.lib.packet.bgp import BGP_ATTR_TYPE_AS_PATH from ryu.services.protocols.bgp.base import OrderedDict from ryu.services.protocols.bgp.constants import VPN_TABLE @@ -984,3 +986,218 @@ class PrefixFilter(Filter): policy=self._policy, ge=self._ge, le=self._le) + + +class ASPathFilter(Filter): + """ + used to specify a prefix for AS_PATH attribute. + + We can create ASPathFilter object as follows; + + * as_path_filter = ASPathFilter(65000,policy=ASPathFilter.TOP) + + ================ ================================================== + Attribute Description + ================ ================================================== + as_number A AS number used for this filter + policy ASPathFilter.POLICY_TOP and PrefixFilter.POLICY_END, + ASPathFilter.POLICY_INCLUDE and + PrefixFilter.POLICY_NOT_INCLUDE are available. + ================ ================================================== + + Meaning of each policy is as follows; + + * POLICY_TOP : + Filter checks if the specified AS number is at the top of + AS_PATH attribute. + + * POLICY_TOP : + Filter checks is the specified AS number + is at the last of AS_PATH attribute. + + * POLICY_INCLUDE : + Filter checks if specified AS number + exists in AS_PATH attribute + + * POLICY_NOT_INCLUDE : + opposite to POLICY_INCLUDE + + + """ + + POLICY_TOP = 2 + POLICY_END = 3 + POLICY_INCLUDE = 4 + POLICY_NOT_INCLUDE = 5 + + def __init__(self, as_number, policy): + super(ASPathFilter, self).__init__(policy) + self._as_number = as_number + + def __cmp__(self, other): + return cmp(self.as_number, other.as_number) + + def __repr__(self): + policy = 'TOP' + if self._policy == self.POLICY_INCLUDE: + policy = 'INCLUDE' + elif self._policy == self.POLICY_NOT_INCLUDE: + policy = 'NOT_INCLUDE' + elif self._policy == self.POLICY_END: + policy = 'END' + + return 'ASPathFilter(as_number=%s,policy=%s)'\ + % (self._as_number, policy) + + @property + def as_number(self): + return self._as_number + + @property + def policy(self): + return self._policy + + def evaluate(self, path): + """ This method evaluates as_path list. + + Returns this object's policy and the result of matching. + If the specified AS number matches this object's AS number + according to the policy, + this method returns True as the matching result. + + ``path`` specifies the path. + + """ + + path_aspath = path.pathattr_map.get(BGP_ATTR_TYPE_AS_PATH) + path_seg_list = path_aspath.path_seg_list + path_seg = path_seg_list[0] + result = False + + LOG.debug("path_seg : %s", path_seg) + if self.policy == ASPathFilter.POLICY_TOP: + + if len(path_seg) > 0 and path_seg[0] == self._as_number: + result = True + + elif self.policy == ASPathFilter.POLICY_INCLUDE: + for aspath in path_seg: + LOG.debug("POLICY_INCLUDE as_number : %s", aspath) + if aspath == self._as_number: + result = True + break + + elif self.policy == ASPathFilter.POLICY_END: + + if len(path_seg) > 0 and path_seg[-1] == self._as_number: + result = True + + elif self.policy == ASPathFilter.POLICY_NOT_INCLUDE: + + if self._as_number not in path_seg: + result = True + + return self.policy, result + + def clone(self): + """ This method clones ASPathFilter object. + + Returns ASPathFilter object that has the same values with the + original one. + + """ + + return self.__class__(self._as_number, + policy=self._policy) + + +class AttributeMap(object): + """ + This class is used to specify an attribute to add if the path matches + filters. + We can create AttributeMap object as follows; + + pref_filter = PrefixFilter('192.168.103.0/30', + PrefixFilter.POLICY_PERMIT) + + attribute_map = AttributeMap([pref_filter], + AttributeMap.ATTR_LOCAL_PREF, 250) + + speaker.attribute_map_set('192.168.50.102', [attribute_map]) + + AttributeMap.ATTR_LOCAL_PREF means that 250 is set as a + local preference value if nlri in the path matches pref_filter. + + ASPathFilter is also available as a filter. ASPathFilter checks if AS_PATH + attribute in the path matches AS number in the filter. + + =================== ================================================== + Attribute Description + =================== ================================================== + filters A list of filter. + Each object should be a Filter class or its sub-class + attr_type A type of attribute to map on filters. Currently + AttributeMap.ATTR_LOCAL_PREF is available. + attr_value A attribute value + =================== ================================================== + + """ + + ATTR_LOCAL_PREF = '_local_pref' + + def __init__(self, filters, attr_type, attr_value): + + assert all(isinstance(f, Filter) for f in filters),\ + 'all the items in filters must be an instance of Filter sub-class' + self.filters = filters + self.attr_type = attr_type + self.attr_value = attr_value + + def evaluate(self, path): + """ This method evaluates attributes of the path. + + Returns the cause and result of matching. + Both cause and result are returned from filters + that this object contains. + + ``path`` specifies the path. + + """ + result = False + cause = None + + for f in self.filters: + + cause, result = f.evaluate(path) + if not result: + break + + return cause, result + + def get_attribute(self): + func = getattr(self, 'get' + self.attr_type) + return func() + + def get_local_pref(self): + local_pref_attr = BGPPathAttributeLocalPref(value=self.attr_value) + return local_pref_attr + + def clone(self): + """ This method clones AttributeMap object. + + Returns AttributeMap object that has the same values with the + original one. + + """ + + cloned_filters = [f.clone() for f in self.filters] + return self.__class__(cloned_filters, self.attr_type, self.attr_value) + + def __repr__(self): + + attr_type = 'LOCAL_PREF'\ + if self.attr_type == self.ATTR_LOCAL_PREF else None + + filter_string = ','.join(repr(f) for f in self.filters) + return 'AttributeMap(filters=[%s],attribute_type=%s,attribute_value=%s)'\ + % (filter_string, attr_type, self.attr_value) diff --git a/ryu/services/protocols/bgp/peer.py b/ryu/services/protocols/bgp/peer.py index 49c7151e..14618521 100644 --- a/ryu/services/protocols/bgp/peer.py +++ b/ryu/services/protocols/bgp/peer.py @@ -30,6 +30,7 @@ from ryu.services.protocols.bgp import constants as const from ryu.services.protocols.bgp.model import OutgoingRoute from ryu.services.protocols.bgp.model import SentRoute from ryu.services.protocols.bgp.info_base.base import PrefixFilter +from ryu.services.protocols.bgp.info_base.base import AttributeMap from ryu.services.protocols.bgp.model import ReceivedRoute from ryu.services.protocols.bgp.net_ctrl import NET_CONTROLLER from ryu.services.protocols.bgp.rtconf.neighbors import NeighborConfListener @@ -349,6 +350,9 @@ class Peer(Source, Sink, NeighborConfListener, Activity): # Adj-rib-out self._adj_rib_out = {} + # attribute maps + self._attribute_maps = {} + @property def remote_as(self): return self._neigh_conf.remote_as @@ -409,6 +413,29 @@ class Peer(Source, Sink, NeighborConfListener, Activity): def check_first_as(self): return self._neigh_conf.check_first_as + @property + def attribute_maps(self): + return self._attribute_maps['__orig']\ + if '__orig' in self._attribute_maps else [] + + @attribute_maps.setter + def attribute_maps(self, attribute_maps): + _attr_maps = {} + _attr_maps.setdefault('__orig', []) + + for a in attribute_maps: + cloned = a.clone() + LOG.debug("AttributeMap attr_type: %s, attr_value: %s", + cloned.attr_type, cloned.attr_value) + attr_list = _attr_maps.setdefault(cloned.attr_type, []) + attr_list.append(cloned) + + # preserve original order of attribute_maps + _attr_maps['__orig'].append(cloned) + + self._attribute_maps = _attr_maps + self.on_update_attribute_maps() + def is_mpbgp_cap_valid(self, route_family): if not self.in_established: raise ValueError('Invalid request: Peer not in established state') @@ -568,6 +595,14 @@ class Peer(Source, Sink, NeighborConfListener, Activity): sent_path.filtered = block self.enque_outgoing_msg(outgoing_route) + def on_update_attribute_maps(self): + # resend sent_route in case of filter matching + LOG.debug('on_update_attribute_maps fired') + for sent_path in self._adj_rib_out.itervalues(): + LOG.debug('resend path: %s' % sent_path) + path = sent_path.path + self.enque_outgoing_msg(OutgoingRoute(path)) + def __str__(self): return 'Peer(ip: %s, asn: %s)' % (self._neigh_conf.ip_address, self._neigh_conf.remote_as) @@ -869,8 +904,24 @@ class Peer(Source, Sink, NeighborConfListener, Activity): # LOCAL_PREF Attribute. if not self.is_ebgp_peer(): # For iBGP peers we are required to send local-pref attribute - # for connected or local prefixes. We send default local-pref. + # for connected or local prefixes. We check if the path matches + # attribute_maps and set local-pref value. + # If the path doesn't match, we set default local-pref 100. localpref_attr = BGPPathAttributeLocalPref(100) + # TODO handle VPNv4Path + if isinstance(path, Ipv4Path): + if AttributeMap.ATTR_LOCAL_PREF in self._attribute_maps: + maps = \ + self._attribute_maps[AttributeMap.ATTR_LOCAL_PREF] + for m in maps: + cause, result = m.evaluate(path) + LOG.debug( + "local_pref evaluation result:%s, cause:%s", + result, cause) + + if result: + localpref_attr = m.get_attribute() + break # COMMUNITY Attribute. community_attr = pathattr_map.get(BGP_ATTR_TYPE_COMMUNITIES) diff --git a/ryu/services/protocols/bgp/rtconf/neighbors.py b/ryu/services/protocols/bgp/rtconf/neighbors.py index 3f8957ca..2e9fa862 100644 --- a/ryu/services/protocols/bgp/rtconf/neighbors.py +++ b/ryu/services/protocols/bgp/rtconf/neighbors.py @@ -63,6 +63,7 @@ from ryu.services.protocols.bgp.utils.validation import is_valid_ipv4 from ryu.services.protocols.bgp.utils.validation import is_valid_old_asn from ryu.services.protocols.bgp.info_base.base import Filter from ryu.services.protocols.bgp.info_base.base import PrefixFilter +from ryu.services.protocols.bgp.info_base.base import AttributeMap LOG = logging.getLogger('bgpspeaker.rtconf.neighbor') @@ -79,6 +80,7 @@ IN_FILTER = 'in_filter' OUT_FILTER = 'out_filter' IS_ROUTE_SERVER_CLIENT = 'is_route_server_client' CHECK_FIRST_AS = 'check_first_as' +ATTRIBUTE_MAP = 'attribute_map' # Default value constants. DEFAULT_CAP_GR_NULL = True @@ -212,6 +214,13 @@ def valid_filter(filter_): return SUPPORTED_FILTER_VALIDATORS[filter_['type']](filter_) +def valid_attribute_map(attribute_map): + if not isinstance(attribute_map, AttributeMap): + raise ConfigTypeError(desc='Invalid AttributeMap: %s' % attribute_map) + else: + return attribute_map + + @validate(name=IN_FILTER) def validate_in_filters(filters): return [valid_filter(filter_) for filter_ in filters] @@ -222,6 +231,12 @@ def validate_out_filters(filters): return [valid_filter(filter_) for filter_ in filters] +@validate(name=ATTRIBUTE_MAP) +def validate_attribute_maps(attribute_maps): + return [valid_attribute_map(attribute_map) + for attribute_map in attribute_maps] + + @validate(name=IS_ROUTE_SERVER_CLIENT) def validate_is_route_server_client(is_route_server_client): if is_route_server_client not in (True, False):