Sorry, I fixed pep8 warnings.

Signed-off-by: Hiroshi Yokoi <[email protected]>
---
 doc/source/library_bgp_speaker_ref.rst         |   3 +
 ryu/services/protocols/bgp/api/rtconf.py       |   9 ++
 ryu/services/protocols/bgp/bgpspeaker.py       | 194 +++++++++++++++++++++++++
 ryu/services/protocols/bgp/info_base/base.py   |  12 ++
 ryu/services/protocols/bgp/peer.py             |  74 +++++++++-
 ryu/services/protocols/bgp/rtconf/base.py      |   3 +
 ryu/services/protocols/bgp/rtconf/neighbors.py |  34 ++++-
 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 493f322..aa2e9f5 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 f5bbc44..6ddc66e 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 aa851c5..b4a273c 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 9d177c5..aaf3aa1 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 47b562d..12f8da9 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 271dab6..e7578f8 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 9ba5761..e41c078 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."""
-- 
1.8.5.2 (Apple Git-48)



------------------------------------------------------------------------------
Want fast and easy access to all the code in your enterprise? Index and
search up to 200,000 lines of code with a free copy of Black Duck
Code Sight - the same software that powers the world's largest code
search on Ohloh, the Black Duck Open Hub! Try it now.
http://p.sf.net/sfu/bds
_______________________________________________
Ryu-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/ryu-devel

Reply via email to