summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--doc/source/library_bgp_speaker_ref.rst3
-rw-r--r--ryu/services/protocols/bgp/api/rtconf.py9
-rw-r--r--ryu/services/protocols/bgp/bgpspeaker.py194
-rw-r--r--ryu/services/protocols/bgp/info_base/base.py12
-rw-r--r--ryu/services/protocols/bgp/peer.py74
-rw-r--r--ryu/services/protocols/bgp/rtconf/base.py3
-rw-r--r--ryu/services/protocols/bgp/rtconf/neighbors.py34
7 files changed, 323 insertions, 6 deletions
diff --git a/doc/source/library_bgp_speaker_ref.rst b/doc/source/library_bgp_speaker_ref.rst
index 493f322e..aa2e9f55 100644
--- a/doc/source/library_bgp_speaker_ref.rst
+++ b/doc/source/library_bgp_speaker_ref.rst
@@ -10,3 +10,6 @@ BGPSpeaker class
.. autoclass:: ryu.services.protocols.bgp.bgpspeaker.EventPrefix
:members:
+
+.. autoclass:: ryu.services.protocols.bgp.bgpspeaker.PrefixList
+ :members:
diff --git a/ryu/services/protocols/bgp/api/rtconf.py b/ryu/services/protocols/bgp/api/rtconf.py
index f5bbc446..6ddc66e4 100644
--- a/ryu/services/protocols/bgp/api/rtconf.py
+++ b/ryu/services/protocols/bgp/api/rtconf.py
@@ -78,6 +78,9 @@ def update_neighbor(neigh_ip_address, changes):
if k == neighbors.ENABLED:
rets.append(update_neighbor_enabled(neigh_ip_address, v))
+ if k == neighbors.OUT_FILTER:
+ rets.append(_update_outfilter(neigh_ip_address, v))
+
return all(rets)
@@ -88,6 +91,12 @@ def _update_med(neigh_ip_address, value):
return True
+def _update_outfilter(neigh_ip_address, value):
+ neigh_conf = _get_neighbor_conf(neigh_ip_address)
+ neigh_conf.out_filter = value
+ return True
+
+
@RegisterWithArgChecks(name='neighbor.delete',
req_args=[neighbors.IP_ADDRESS])
def delete_neighbor(neigh_ip_address):
diff --git a/ryu/services/protocols/bgp/bgpspeaker.py b/ryu/services/protocols/bgp/bgpspeaker.py
index aa851c53..b4a273c9 100644
--- a/ryu/services/protocols/bgp/bgpspeaker.py
+++ b/ryu/services/protocols/bgp/bgpspeaker.py
@@ -52,7 +52,14 @@ from ryu.services.protocols.bgp.rtconf.neighbors import DEFAULT_CAP_MBGP_VPNV4
from ryu.services.protocols.bgp.rtconf.neighbors import DEFAULT_CAP_MBGP_VPNV6
from ryu.services.protocols.bgp.rtconf.neighbors import PEER_NEXT_HOP
from ryu.services.protocols.bgp.rtconf.neighbors import PASSWORD
+from ryu.services.protocols.bgp.rtconf.neighbors import OUT_FILTER
from ryu.services.protocols.bgp.application import RyuBGPSpeaker
+from netaddr.ip import IPAddress, IPNetwork
+from ryu.lib.packet.bgp import RF_IPv4_UC, RF_IPv6_UC
+
+
+OUT_FILTER_RF_IPv4_UC = RF_IPv4_UC
+OUT_FILTER_RF_IPv6_UC = RF_IPv6_UC
class EventPrefix(object):
@@ -80,6 +87,131 @@ class EventPrefix(object):
self.is_withdraw = is_withdraw
+class PrefixList(object):
+ """
+ used to specify a prefix for out-filter.
+
+ We can create PrefixList object as follows.
+
+ prefix_list = PrefixList('10.5.111.0/24', policy=PrefixList.POLICY_PERMIT)
+
+ ================ ==================================================
+ Attribute Description
+ ================ ==================================================
+ prefix A prefix used for out-filter
+ policy PrefixList.POLICY.PERMIT or PrefixList.POLICY_DENY
+ ge Prefix length that will be applied out-filter.
+ ge means greater than or equal.
+ le Prefix length that will be applied out-filter.
+ le means less than or equal.
+ ================ ==================================================
+
+
+ For example, when PrefixList object is created as follows:
+
+ * p = PrefixList('10.5.111.0/24',
+ policy=PrefixList.POLICY_DENY,
+ ge=26, le=28)
+
+
+ prefixes which match 10.5.111.0/24 and its length matches
+ from 26 to 28 will be filtered and stopped to send to neighbor
+ because of POLICY_DENY. If you specify POLICY_PERMIT,
+ the path is sent to neighbor.
+
+ If you don't want to send prefixes 10.5.111.64/26 and 10.5.111.32/27
+ and 10.5.111.16/28, and allow to send other 10.5.111.0's prefixes,
+ you can do it by specifying as follows;
+
+ * p = PrefixList('10.5.111.0/24',
+ policy=PrefixList.POLICY_DENY,
+ ge=26, le=28).
+
+ """
+ POLICY_DENY = 0
+ POLICY_PERMIT = 1
+
+ def __init__(self, prefix, policy=POLICY_PERMIT, ge=None, le=None):
+ self._prefix = prefix
+ self._policy = policy
+ self._network = IPNetwork(prefix)
+ self._ge = ge
+ self._le = le
+
+ def __cmp__(self, other):
+ return cmp(self.prefix, other.prefix)
+
+ def __repr__(self):
+ policy = 'PERMIT' \
+ if self._policy == self.POLICY_PERMIT else 'DENY'
+
+ return 'PrefixList(prefix=%s,policy=%s,ge=%s,le=%s)'\
+ % (self._prefix, policy, self._ge, self._le)
+
+ @property
+ def prefix(self):
+ return self._prefix
+
+ @property
+ def policy(self):
+ return self._policy
+
+ @property
+ def ge(self):
+ return self._ge
+
+ @property
+ def le(self):
+ return self._le
+
+ def evaluate(self, prefix):
+ """ This method evaluates the prefix.
+
+ Returns this object's policy and the result of matching.
+ If the specified prefix matches this object's prefix and
+ ge and le condition,
+ this method returns True as the matching result.
+
+ ``prefix`` specifies the prefix. prefix must be string.
+
+ """
+
+ result = False
+ length = prefix.length
+ net = IPNetwork(prefix.formatted_nlri_str)
+
+ if net in self._network:
+ if self._ge is None and self._le is None:
+ result = True
+
+ elif self._ge is None and self._le:
+ if length <= self._le:
+ result = True
+
+ elif self._ge and self._le is None:
+ if self._ge <= length:
+ result = True
+
+ elif self._ge and self._le:
+ if self._ge <= length <= self._le:
+ result = True
+
+ return self.policy, result
+
+ def clone(self):
+ """ This method clones PrefixList object.
+
+ Returns PrefixList object that has the same values with the
+ original one.
+
+ """
+
+ return PrefixList(self.prefix,
+ policy=self._policy,
+ ge=self._ge,
+ le=self._le)
+
+
class BGPSpeaker(object):
def __init__(self, as_number, router_id,
bgp_server_port=DEFAULT_BGP_SERVER_PORT,
@@ -319,3 +451,65 @@ class BGPSpeaker(object):
show = {}
show['params'] = ['rib', family]
return call('operator.show', **show)
+
+ def out_filter_set(self, address, prefix_lists,
+ route_family=OUT_FILTER_RF_IPv4_UC):
+ """ This method sets out-filter to neighbor.
+
+ ``address`` specifies the IP address of the peer.
+
+ ``prefix_lists`` specifies prefix list to filter path advertisement.
+ This parameter must be list that has PrefixList objects.
+
+ ``route_family`` specifies the route family for out-filter.
+ This parameter must be bgpspeaker.OUT_FILTER_RF_IPv4_UC or
+ bgpspeaker.OUT_FILTER_RF_IPv6_UC.
+
+
+ If you want to define out-filter that send only a particular
+ prefix to neighbor, prefix_lists can be created as follows;
+
+ p = PrefixList('10.5.111.0/24', policy=PrefixList.POLICY_PERMIT)
+
+ all = PrefixList('0.0.0.0/0', policy=PrefixList.POLICY_DENY)
+
+ pList = [p, all]
+
+ self.bgpspeaker.out_filter_set(neighbor_address, pList)
+
+ NOTE:
+ out-filter evaluates prefixes in the order of PrefixList in the pList.
+
+ """
+
+ assert route_family in (OUT_FILTER_RF_IPv4_UC,
+ OUT_FILTER_RF_IPv6_UC),\
+ "route family must be IPv4 or IPv6"
+
+ if prefix_lists is None:
+ prefix_lists = []
+
+ func_name = 'neighbor.update'
+ prefix_value = {'prefix_lists': prefix_lists,
+ 'route_family': route_family}
+ filter_param = {neighbors.OUT_FILTER: prefix_value}
+
+ param = {}
+ param[neighbors.IP_ADDRESS] = address
+ param[neighbors.CHANGES] = filter_param
+ call(func_name, **param)
+
+ def out_filter_get(self, address):
+ """ This method gets out-filter setting from the specified neighbor.
+
+ ``address`` specifies the IP address of the peer.
+
+ Returns list object that has PrefixList objects.
+
+ """
+
+ func_name = 'neighbor.get'
+ param = {}
+ param[neighbors.IP_ADDRESS] = address
+ settings = call(func_name, **param)
+ return settings[OUT_FILTER]
diff --git a/ryu/services/protocols/bgp/info_base/base.py b/ryu/services/protocols/bgp/info_base/base.py
index 9d177c59..aaf3aa14 100644
--- a/ryu/services/protocols/bgp/info_base/base.py
+++ b/ryu/services/protocols/bgp/info_base/base.py
@@ -632,6 +632,18 @@ class Destination(object):
def _get_num_withdraws(self):
return len(self._withdraw_list)
+ def sent_routes_by_peer(self, peer):
+ """get sent routes corresponding to specified peer.
+
+ Returns SentRoute list.
+ """
+ result = []
+ for route in self._sent_routes.values():
+ if route.sent_peer == peer:
+ result.append(route)
+
+ return result
+
class Path(object):
"""Represents a way of reaching an IP destination.
diff --git a/ryu/services/protocols/bgp/peer.py b/ryu/services/protocols/bgp/peer.py
index 47b562d0..12f8da96 100644
--- a/ryu/services/protocols/bgp/peer.py
+++ b/ryu/services/protocols/bgp/peer.py
@@ -29,6 +29,7 @@ from ryu.services.protocols.bgp.base import SUPPORTED_GLOBAL_RF
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.bgpspeaker import PrefixList
from ryu.services.protocols.bgp.net_ctrl import NET_CONTROLLER
from ryu.services.protocols.bgp.rtconf.neighbors import NeighborConfListener
from ryu.services.protocols.bgp.signals.emit import BgpSignalBus
@@ -440,6 +441,48 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
for af in negotiated_afs:
self._fire_route_refresh(af)
+ def on_update_out_filter(self, conf_evt):
+ LOG.debug('on_update_out_filter fired')
+ event_value = conf_evt.value
+ prefix_lists = event_value['prefix_lists']
+ rf = event_value['route_family']
+
+ table = self._core_service.\
+ table_manager.get_global_table_by_route_family(rf)
+ for destination in table.itervalues():
+ LOG.debug('dest : %s' % destination)
+ sent_routes = destination.sent_routes_by_peer(self)
+ if len(sent_routes) == 0:
+ continue
+
+ for sent_route in sent_routes:
+ nlri = sent_route.path.nlri
+ nlri_str = nlri.formatted_nlri_str
+ send_withdraw = False
+ for pl in prefix_lists:
+ policy, result = pl.evaluate(nlri)
+
+ if policy == PrefixList.POLICY_PERMIT and result:
+ send_withdraw = False
+ break
+ elif policy == PrefixList.POLICY_DENY and result:
+ send_withdraw = True
+ break
+
+ outgoing_route = None
+ if send_withdraw:
+ # send withdraw routes that have already been sent
+ withdraw_clone = sent_route.path.clone(for_withdrawal=True)
+ outgoing_route = OutgoingRoute(withdraw_clone)
+ LOG.debug('send withdraw %s because of out filter'
+ % nlri_str)
+ else:
+ outgoing_route = OutgoingRoute(sent_route.path,
+ for_route_refresh=True)
+ LOG.debug('resend path : %s' % nlri_str)
+
+ self.enque_outgoing_msg(outgoing_route)
+
def __str__(self):
return 'Peer(ip: %s, asn: %s)' % (self._neigh_conf.ip_address,
self._neigh_conf.remote_as)
@@ -483,12 +526,35 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
Also, checks if any policies prevent sending this message.
Populates Adj-RIB-out with corresponding `SentRoute`.
"""
+
+ # evaluate prefix list
+ rf = outgoing_route.path.route_family
+ allow_to_send = True
+ if rf in (RF_IPv4_UC, RF_IPv6_UC):
+ prefix_lists = self._neigh_conf.out_filter
+
+ if not outgoing_route.path.is_withdraw:
+ for prefix_list in prefix_lists:
+ nlri = outgoing_route.path.nlri
+ policy, is_matched = prefix_list.evaluate(nlri)
+ if policy == PrefixList.POLICY_PERMIT and is_matched:
+ allow_to_send = True
+ break
+ elif policy == PrefixList.POLICY_DENY and is_matched:
+ allow_to_send = False
+ blocked_cause = prefix_list.prefix + ' - DENY'
+ break
+
# TODO(PH): optimized by sending several prefixes per update.
# Construct and send update message.
- update_msg = self._construct_update(outgoing_route)
- self._protocol.send(update_msg)
- # Collect update statistics.
- self.state.incr(PeerCounterNames.SENT_UPDATES)
+ if allow_to_send:
+ update_msg = self._construct_update(outgoing_route)
+ self._protocol.send(update_msg)
+ # Collect update statistics.
+ self.state.incr(PeerCounterNames.SENT_UPDATES)
+ else:
+ LOG.debug('prefix : %s is not sent by filter : %s'
+ % (nlri, blocked_cause))
# We have to create sent_route for every OutgoingRoute which is
# not a withdraw or was for route-refresh msg.
diff --git a/ryu/services/protocols/bgp/rtconf/base.py b/ryu/services/protocols/bgp/rtconf/base.py
index 271dab63..e7578f87 100644
--- a/ryu/services/protocols/bgp/rtconf/base.py
+++ b/ryu/services/protocols/bgp/rtconf/base.py
@@ -63,6 +63,9 @@ MULTI_EXIT_DISC = 'multi_exit_disc'
# Extended community attribute route origin.
SITE_OF_ORIGINS = 'site_of_origins'
+# OUT FILTER
+OUT_FILTER = 'out_filter'
+
# Constants related to errors.
CONF_NAME = 'conf_name'
CONF_VALUE = 'conf_value'
diff --git a/ryu/services/protocols/bgp/rtconf/neighbors.py b/ryu/services/protocols/bgp/rtconf/neighbors.py
index 9ba57616..e41c078c 100644
--- a/ryu/services/protocols/bgp/rtconf/neighbors.py
+++ b/ryu/services/protocols/bgp/rtconf/neighbors.py
@@ -59,6 +59,7 @@ from ryu.services.protocols.bgp.rtconf.base import SITE_OF_ORIGINS
from ryu.services.protocols.bgp.rtconf.base import validate
from ryu.services.protocols.bgp.rtconf.base import validate_med
from ryu.services.protocols.bgp.rtconf.base import validate_soo_list
+from ryu.services.protocols.bgp.rtconf.base import OUT_FILTER
from ryu.services.protocols.bgp.utils.validation import is_valid_ipv4
from ryu.services.protocols.bgp.utils.validation import is_valid_old_asn
@@ -73,6 +74,7 @@ LOCAL_ADDRESS = 'local_address'
LOCAL_PORT = 'local_port'
PEER_NEXT_HOP = 'next_hop'
PASSWORD = 'password'
+OUT_FILTER = 'out_filter'
# Default value constants.
DEFAULT_CAP_GR_NULL = True
@@ -102,7 +104,7 @@ def validate_enabled(enabled):
@validate(name=CHANGES)
def validate_changes(changes):
for k, v in changes.iteritems():
- if k not in (MULTI_EXIT_DISC, ENABLED):
+ if k not in (MULTI_EXIT_DISC, ENABLED, OUT_FILTER):
raise ConfigValueError(desc="Unknown field to change: %s" % k)
if k == MULTI_EXIT_DISC:
@@ -169,8 +171,10 @@ class NeighborConf(ConfWithId, ConfWithStats):
UPDATE_ENABLED_EVT = 'update_enabled_evt'
UPDATE_MED_EVT = 'update_med_evt'
+ UPDATE_OUT_FILTER_EVT = 'update_out_filter_evt'
- VALID_EVT = frozenset([UPDATE_ENABLED_EVT, UPDATE_MED_EVT])
+ VALID_EVT = frozenset([UPDATE_ENABLED_EVT, UPDATE_MED_EVT,
+ UPDATE_OUT_FILTER_EVT])
REQUIRED_SETTINGS = frozenset([REMOTE_AS, IP_ADDRESS])
OPTIONAL_SETTINGS = frozenset([CAP_REFRESH,
CAP_ENHANCED_REFRESH,
@@ -245,6 +249,9 @@ class NeighborConf(ConfWithId, ConfWithStats):
self._settings[RTC_AS] = \
compute_optional_conf(RTC_AS, default_rt_as, **kwargs)
+ # out filter configuration
+ self._settings[OUT_FILTER] = []
+
# Since ConfWithId' default values use str(self) and repr(self), we
# call super method after we have initialized other settings.
super(NeighborConf, self)._init_opt_settings(**kwargs)
@@ -372,6 +379,23 @@ class NeighborConf(ConfWithId, ConfWithStats):
def rtc_as(self):
return self._settings[RTC_AS]
+ @property
+ def out_filter(self):
+ return self._settings[OUT_FILTER]
+
+ @out_filter.setter
+ def out_filter(self, value):
+ self._settings[OUT_FILTER] = []
+ prefix_lists = value['prefix_lists']
+ for prefix_list in prefix_lists:
+ # copy PrefixList object and put it in the _settings
+ self._settings[OUT_FILTER].append(prefix_list.clone())
+
+ LOG.debug('set out-filter : %s' % prefix_lists)
+
+ # check sent_route
+ self._notify_listeners(NeighborConf.UPDATE_OUT_FILTER_EVT, value)
+
def exceeds_max_prefix_allowed(self, prefix_count):
allowed_max = self._settings[MAX_PREFIXES]
does_exceed = False
@@ -515,6 +539,8 @@ class NeighborConfListener(ConfWithIdListener, ConfWithStatsListener):
self.on_update_enabled)
neigh_conf.add_listener(NeighborConf.UPDATE_MED_EVT,
self.on_update_med)
+ neigh_conf.add_listener(NeighborConf.UPDATE_OUT_FILTER_EVT,
+ self.on_update_out_filter)
@abstractmethod
def on_update_enabled(self, evt):
@@ -523,6 +549,10 @@ class NeighborConfListener(ConfWithIdListener, ConfWithStatsListener):
def on_update_med(self, evt):
raise NotImplementedError('This method should be overridden.')
+ @abstractmethod
+ def on_update_out_filter(self, evt):
+ raise NotImplementedError('This method should be overridden.')
+
class NeighborsConfListener(BaseConfListener):
"""Base listener for change events to neighbor configuration container."""