diff options
-rw-r--r-- | doc/source/library_bgp_speaker_ref.rst | 3 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/api/rtconf.py | 9 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/bgpspeaker.py | 194 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/info_base/base.py | 12 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/peer.py | 74 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/rtconf/base.py | 3 | ||||
-rw-r--r-- | 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 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.""" |