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:
Hiroshi Yokoi 2014-09-12 14:33:50 -07:00 committed by FUJITA Tomonori
parent 43ab3472ff
commit fad987254d
6 changed files with 354 additions and 1 deletions

View File

@ -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:

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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):