I added out-filter function to BGP. It is possible to control the advertisement of the prefix according to neighbors. Currently this function supports only IPv4.
Signed-off-by: Hiroshi Yokoi <yokoi.hiro...@po.ntts.co.jp> --- ryu/services/protocols/bgp/api/rtconf.py | 7 ++++ ryu/services/protocols/bgp/bgpspeaker.py | 25 +++++++++++ ryu/services/protocols/bgp/info_base/base.py | 11 +++++ ryu/services/protocols/bgp/model.py | 51 +++++++++++++++++++++- ryu/services/protocols/bgp/peer.py | 58 +++++++++++++++++++++++++- ryu/services/protocols/bgp/rtconf/base.py | 3 ++ ryu/services/protocols/bgp/rtconf/neighbors.py | 40 +++++++++++++++++- 7 files changed, 191 insertions(+), 4 deletions(-) diff --git a/ryu/services/protocols/bgp/api/rtconf.py b/ryu/services/protocols/bgp/api/rtconf.py index f5bbc44..28e70f3 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) @@ -87,6 +90,10 @@ def _update_med(neigh_ip_address, value): LOG.info('MED value for neigh: %s updated to %s' % (neigh_conf, 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]) diff --git a/ryu/services/protocols/bgp/bgpspeaker.py b/ryu/services/protocols/bgp/bgpspeaker.py index aa851c5..052e92a 100644 --- a/ryu/services/protocols/bgp/bgpspeaker.py +++ b/ryu/services/protocols/bgp/bgpspeaker.py @@ -319,3 +319,28 @@ class BGPSpeaker(object): show = {} show['params'] = ['rib', family] return call('operator.show', **show) + + def out_filter_set(self, neighbor_address, prefix_lists): + """ This method sets out-filter to neighbor. + + ``neighbor_address`` specifies the neighbor IP address + + ``prefix_lists`` specifies prefix list to filter route for advertisement. This + parameter must be PrefixList object. + + We can create PrefixList object as follows. + prefix_list = PrefixList('10.5.111.0/24',policy=PrefixList.POLICY_PERMIT) + + """ + func_name = 'neighbor.update' + + from ryu.lib.packet.bgp import RF_IPv4_UC + + prefix_value = {'prefix_lists': prefix_lists, 'route_family': RF_IPv4_UC} + filter_param = {neighbors.OUT_FILTER: prefix_value} + + param = {} + param[neighbors.IP_ADDRESS] = neighbor_address + param[neighbors.CHANGES] = filter_param + call(func_name, **param) + diff --git a/ryu/services/protocols/bgp/info_base/base.py b/ryu/services/protocols/bgp/info_base/base.py index 9d177c5..b84a44d 100644 --- a/ryu/services/protocols/bgp/info_base/base.py +++ b/ryu/services/protocols/bgp/info_base/base.py @@ -632,6 +632,17 @@ 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/model.py b/ryu/services/protocols/bgp/model.py index 51c8bb0..b5b52c4 100644 --- a/ryu/services/protocols/bgp/model.py +++ b/ryu/services/protocols/bgp/model.py @@ -19,7 +19,7 @@ sessions. """ import logging - +from netaddr.ip import IPAddress, IPNetwork LOG = logging.getLogger('bgpspeaker.model') @@ -146,3 +146,52 @@ class SentRoute(object): @property def sent_peer(self): return self._sent_peer + + +class PrefixList(object): + """Holds prefix information to filter for outgoing routes + """ + POLICY_DENY = 0 + POLICY_PERMIT = 1 + + def __init__(self, prefix, policy=POLICY_PERMIT, ge=None, le=None, seq=None): + self.prefix = prefix + self.policy = policy + self.network = IPNetwork(prefix) + self._ge = ge + self._le = le + self.seq = seq + + def __cmp__(self, other): + return cmp(self.prefix, other.prefix) + + @property + def ge(self): + return self._ge + + @property + def le(self): + return self._le + + def evaluate(self, prefix): + result = False + length = prefix.length + net = IPNetwork(prefix.formatted_nlri_str) + LOG.debug('evaluate prefix : %s and %s' % (self.prefix, 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 diff --git a/ryu/services/protocols/bgp/peer.py b/ryu/services/protocols/bgp/peer.py index 47b562d..0e00096 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.model 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 @@ -114,7 +115,6 @@ PeerCounterNames = namedtuple( 'fms_established_transitions' ) - class PeerState(object): """A BGP neighbor state. Think of this class as of information and stats container for Peer. @@ -323,6 +323,7 @@ class Peer(Source, Sink, NeighborConfListener, Activity): self._sent_init_non_rtc_update = False self._init_rtc_nlri_path = [] + @property def remote_as(self): return self._neigh_conf.remote_as @@ -440,6 +441,41 @@ 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(): + 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 = True + for pl in prefix_lists: + policy, result = pl.evaluate(nlri) + + if policy == PrefixList.POLICY_PERMIT and result: + send_withdraw = False + 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,6 +519,26 @@ class Peer(Source, Sink, NeighborConfListener, Activity): Also, checks if any policies prevent sending this message. Populates Adj-RIB-out with corresponding `SentRoute`. """ + + # TODO support IPv6 + # evaluate prefix list + if self._neigh_conf.cap_mbgp_ipv4: + prefix_lists = self._neigh_conf.out_filter + allow_to_send = True + + if not outgoing_route.path.is_withdraw: + for prefix_list in prefix_lists: + allow_to_send = False + 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 + + if not allow_to_send: + LOG.debug('prefix : %s is not sent because of out-filter' % nlri) + return + # TODO(PH): optimized by sending several prefixes per update. # Construct and send update message. update_msg = self._construct_update(outgoing_route) 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..78df31b 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,9 @@ 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 +248,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 +378,31 @@ 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] = [] + initial_seq = 0 + prefix_lists = value['prefix_lists'] + for prefix_list in prefix_lists: + if not prefix_list.seq: + initial_seq += 5 + prefix_list.seq = initial_seq + else: + seq = prefix_list.seq + if seq >= initial_seq: + initial_seq = seq + 5 + + self._settings[OUT_FILTER].append(prefix_list) + self._settings[OUT_FILTER].sort(key=lambda x: x.seq) + 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 +546,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 +556,9 @@ 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) ------------------------------------------------------------------------------ Open source business process management suite built on Java and Eclipse Turn processes into business applications with Bonita BPM Community Edition Quickly connect people, data, and systems into organized workflows Winner of BOSSIE, CODIE, OW2 and Gartner awards http://p.sf.net/sfu/Bonitasoft _______________________________________________ Ryu-devel mailing list Ryu-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/ryu-devel