mirror of
https://github.com/faucetsdn/ryu.git
synced 2026-05-04 20:06:09 +02:00
bgp: add out-filter function
Added out-filter function to Ryu BGPSpeaker. It supports IPv4 and IPv6. 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:
parent
9d7433e44a
commit
3c0c9ce737
@ -10,3 +10,6 @@ BGPSpeaker class
|
||||
|
||||
.. autoclass:: ryu.services.protocols.bgp.bgpspeaker.EventPrefix
|
||||
:members:
|
||||
|
||||
.. autoclass:: ryu.services.protocols.bgp.bgpspeaker.PrefixList
|
||||
:members:
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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."""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user