bgp: local preference support
add local preference support in bgp. It is possible to apply local preference to specific paths by using AttributeMap. Unfortunately this patch supports only IPv4 path and I'm going to start writing patches for other route families after this. Signed-off-by: Hiroshi Yokoi <yokoi.hiroshi@po.ntts.co.jp> Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
This commit is contained in:
parent
43ab3472ff
commit
fad987254d
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user