309 lines
12 KiB
Python
309 lines
12 KiB
Python
import logging
|
|
import netaddr
|
|
|
|
from ryu.services.protocols.bgp.base import SUPPORTED_GLOBAL_RF
|
|
from ryu.services.protocols.bgp.model import OutgoingRoute
|
|
from ryu.services.protocols.bgp.peer import Peer
|
|
from ryu.lib.packet.bgp import BGPPathAttributeCommunities
|
|
from ryu.lib.packet.bgp import BGP_ATTR_TYPE_MULTI_EXIT_DISC
|
|
from ryu.lib.packet.bgp import BGP_ATTR_TYPE_COMMUNITIES
|
|
from ryu.lib.packet.bgp import RF_RTC_UC
|
|
from ryu.lib.packet.bgp import RouteTargetMembershipNLRI
|
|
from ryu.services.protocols.bgp.utils.bgp \
|
|
import clone_path_and_update_med_for_target_neighbor
|
|
LOG = logging.getLogger('bgpspeaker.core_managers.peer_manager')
|
|
|
|
|
|
class PeerManager(object):
|
|
def __init__(
|
|
self, core_service, neighbors_conf,
|
|
):
|
|
self._core_service = core_service
|
|
self._signal_bus = core_service.signal_bus
|
|
self._table_manager = core_service.table_manager
|
|
self._rt_manager = core_service.rt_manager
|
|
self._peers = {}
|
|
|
|
# Peer to RTFilter map
|
|
# Key: Peer instance
|
|
# Value: set of RTs that constitute RT filter for this peer
|
|
self._peer_to_rtfilter_map = {}
|
|
self._neighbors_conf = neighbors_conf
|
|
|
|
@property
|
|
def iterpeers(self):
|
|
return iter(self._peers.values())
|
|
|
|
def set_peer_to_rtfilter_map(self, new_map):
|
|
self._peer_to_rtfilter_map = new_map
|
|
|
|
def add_peer(self, neigh_conf, common_conf):
|
|
peer = Peer(common_conf, neigh_conf, self._core_service,
|
|
self._signal_bus, self)
|
|
self._peers[neigh_conf.ip_address] = peer
|
|
self._core_service.on_peer_added(peer)
|
|
|
|
def remove_peer(self, neigh_conf):
|
|
neigh_ip_address = neigh_conf.ip_address
|
|
peer = self._peers.get(neigh_ip_address)
|
|
peer.stop()
|
|
del self._peers[neigh_ip_address]
|
|
self._core_service.on_peer_removed(peer)
|
|
|
|
def get_by_addr(self, addr):
|
|
return self._peers.get(str(netaddr.IPAddress(addr)))
|
|
|
|
def on_peer_down(self, peer):
|
|
"""Peer down handler.
|
|
|
|
Cleans up the paths in global tables that was received from this peer.
|
|
"""
|
|
LOG.debug('Cleaning obsolete paths whose source/version: %s/%s',
|
|
peer.ip_address, peer.version_num)
|
|
# Launch clean-up for each global tables.
|
|
self._table_manager.clean_stale_routes(peer)
|
|
|
|
def _get_non_rtc_peers(self):
|
|
non_rtc_peer_list = set()
|
|
for peer in self._peers.values():
|
|
if (peer.in_established() and
|
|
not peer.is_mpbgp_cap_valid(RF_RTC_UC)):
|
|
non_rtc_peer_list.add(peer)
|
|
return non_rtc_peer_list
|
|
|
|
def curr_peer_rtfilter(self, peer):
|
|
return self._peer_to_rtfilter_map.get(peer)
|
|
|
|
def get_peers_in_established(self):
|
|
"""Returns list of peers in established state."""
|
|
est_peers = []
|
|
for peer in self._peers.values():
|
|
if peer.in_established:
|
|
est_peers.append(peer)
|
|
return est_peers
|
|
|
|
def resend_sent(self, route_family, peer):
|
|
"""For given `peer` re-send sent paths.
|
|
|
|
Parameters:
|
|
- `route-family`: (RouteFamily) of the sent paths to re-send
|
|
- `peer`: (Peer) peer for which we need to re-send sent paths
|
|
"""
|
|
if peer not in self._peers.values():
|
|
raise ValueError('Could not find given peer (%s)' % peer)
|
|
|
|
if route_family not in SUPPORTED_GLOBAL_RF:
|
|
raise ValueError(
|
|
'Given route family (%s) is not supported.' % route_family
|
|
)
|
|
|
|
# Iterate over the global table for given afi, safi and enqueue
|
|
# out-going routes.
|
|
table = self._table_manager.get_global_table_by_route_family(
|
|
route_family
|
|
)
|
|
|
|
for destination in table.values():
|
|
# Check if this destination's sent - routes include this peer.
|
|
# i.e. check if this destinations was advertised and enqueue
|
|
# the path only if it was. If the current best-path has not been
|
|
# advertised before, it might already have a OutgoingRoute queued
|
|
# to be sent to the peer.
|
|
sent_routes = destination.sent_routes
|
|
if sent_routes is None or len(sent_routes) == 0:
|
|
continue
|
|
for sent_route in sent_routes:
|
|
if sent_route.sent_peer == peer:
|
|
# update med - if previously med was set per neighbor or
|
|
# wasn't set at all now it could have changed and we may
|
|
# need to set new value there
|
|
p = sent_route.path
|
|
if p.med_set_by_target_neighbor or p.get_pattr(
|
|
BGP_ATTR_TYPE_MULTI_EXIT_DISC) is None:
|
|
sent_route.path = \
|
|
clone_path_and_update_med_for_target_neighbor(
|
|
sent_route.path, peer.med
|
|
)
|
|
|
|
ogr = OutgoingRoute(sent_route.path,
|
|
for_route_refresh=True)
|
|
peer.enque_outgoing_msg(ogr)
|
|
|
|
def req_rr_to_non_rtc_peers(self, route_family):
|
|
"""Makes refresh request to all peers for given address family.
|
|
|
|
Skips making request to peer that have valid RTC capability.
|
|
"""
|
|
assert route_family != RF_RTC_UC
|
|
for peer in self._peers.values():
|
|
# First check if peer is in established state
|
|
if (peer.in_established and
|
|
# Check if peer has valid capability for given address
|
|
# family
|
|
peer.is_mbgp_cap_valid(route_family) and
|
|
# Check if peer has valid capability for RTC
|
|
not peer.is_mbgp_cap_valid(RF_RTC_UC)):
|
|
peer.request_route_refresh(route_family)
|
|
|
|
def make_route_refresh_request(self, peer_ip, *route_families):
|
|
"""Request route-refresh for peer with `peer_ip` for given
|
|
`route_families`.
|
|
|
|
Will make route-refresh request for a given `route_family` only if such
|
|
capability is supported and if peer is in ESTABLISHED state. Else, such
|
|
requests are ignored. Raises appropriate error in other cases. If
|
|
`peer_ip` is equal to 'all' makes refresh request to all valid peers.
|
|
"""
|
|
LOG.debug('Route refresh requested for peer %s and route families %s',
|
|
peer_ip, route_families)
|
|
if not SUPPORTED_GLOBAL_RF.intersection(route_families):
|
|
raise ValueError('Given route family(s) % is not supported.' %
|
|
route_families)
|
|
|
|
peer_list = []
|
|
# If route-refresh is requested for all peers.
|
|
if peer_ip == 'all':
|
|
peer_list.extend(self.get_peers_in_established())
|
|
else:
|
|
given_peer = self._peers.get(peer_ip)
|
|
if not given_peer:
|
|
raise ValueError('Invalid/unrecognized peer %s' % peer_ip)
|
|
if not given_peer.in_established:
|
|
raise ValueError('Peer currently do not have established'
|
|
' session.')
|
|
peer_list.append(given_peer)
|
|
|
|
# Make route refresh request to valid peers.
|
|
for peer in peer_list:
|
|
peer.request_route_refresh(*route_families)
|
|
|
|
return True
|
|
|
|
def comm_all_rt_nlris(self, peer):
|
|
"""Shares/communicates current best rt_nlri paths with this peers.
|
|
|
|
Can be used to send initial updates after we have established session
|
|
with `peer` with which RTC capability is valid. Takes into account
|
|
peers RTC_AS setting and filters all RT NLRIs whose origin AS do not
|
|
match this setting.
|
|
"""
|
|
# First check if for this peer mpbgp-rtc is valid.
|
|
if not peer.is_mbgp_cap_valid(RF_RTC_UC):
|
|
return
|
|
|
|
neigh_conf = self._neighbors_conf.get_neighbor_conf(peer.ip_address)
|
|
peer_rtc_as = neigh_conf.rtc_as
|
|
# Iterate over all RT_NLRI destination communicate qualifying RT_NLRIs
|
|
rtc_table = self._table_manager.get_rtc_table()
|
|
for dest in rtc_table.values():
|
|
best_path = dest.best_path
|
|
# Ignore a destination that currently does not have best path
|
|
if not best_path:
|
|
continue
|
|
|
|
# If this is a local path
|
|
if best_path.source is None:
|
|
# Check RT NLRI's origin AS matches peer RTC_AS setting
|
|
origin_as = best_path.nlri.origin_as
|
|
if origin_as == peer_rtc_as:
|
|
peer.communicate_path(best_path)
|
|
else:
|
|
# Communicate all remote RT NLRIs
|
|
peer.communicate_path(best_path)
|
|
|
|
# Also communicate EOR as per RFC
|
|
peer.enque_end_of_rib(RF_RTC_UC)
|
|
|
|
def comm_all_best_paths(self, peer):
|
|
"""Shares/communicates current best paths with this peers.
|
|
|
|
Can be used to send initial updates after we have established session
|
|
with `peer`.
|
|
"""
|
|
LOG.debug('Communicating current best path for all afi/safi except'
|
|
' 1/132')
|
|
# We will enqueue best path from all global destination.
|
|
for route_family, table in self._table_manager.iter:
|
|
if route_family == RF_RTC_UC:
|
|
continue
|
|
if peer.is_mbgp_cap_valid(route_family):
|
|
for dest in table.values():
|
|
if dest.best_path:
|
|
peer.communicate_path(dest.best_path)
|
|
|
|
def comm_new_best_to_bgp_peers(self, new_best_path):
|
|
"""Communicates/enqueues given best path to be sent to all qualifying
|
|
bgp peers.
|
|
|
|
If this path came from iBGP peers, it is not sent to other iBGP peers.
|
|
If this path has community-attribute, and if settings for recognize-
|
|
well-know attributes is set, we do as per [RFC1997], and queue outgoing
|
|
route only to qualifying BGP peers.
|
|
"""
|
|
# Filter based on standard community
|
|
# If new best path has community attribute, it should be taken into
|
|
# account when sending UPDATE to peers.
|
|
comm_attr = new_best_path.get_pattr(BGP_ATTR_TYPE_COMMUNITIES)
|
|
if comm_attr:
|
|
comm_attr_na = comm_attr.has_comm_attr(
|
|
BGPPathAttributeCommunities.NO_ADVERTISE
|
|
)
|
|
# If we have NO_ADVERTISE attribute is present, we do not send
|
|
# UPDATE to any peers
|
|
if comm_attr_na:
|
|
LOG.debug('New best path has community attr. NO_ADVERTISE = %s'
|
|
'. Hence not advertising to any peer', comm_attr_na)
|
|
return
|
|
|
|
qualified_peers = self._collect_peers_of_interest(
|
|
new_best_path
|
|
)
|
|
|
|
# Distribute new best-path to qualified peers.
|
|
for peer in qualified_peers:
|
|
peer.communicate_path(new_best_path)
|
|
|
|
def _collect_peers_of_interest(self, new_best_path):
|
|
"""Collect all peers that qualify for sharing a path with given RTs.
|
|
"""
|
|
path_rts = new_best_path.get_rts()
|
|
qualified_peers = set(self._peers.values())
|
|
|
|
# Filter out peers based on RTC_AS setting if path is for RT_NLRI
|
|
qualified_peers = self._rt_manager.filter_by_origin_as(
|
|
new_best_path, qualified_peers
|
|
)
|
|
|
|
# We continue to filter out qualified peer based on path RTs
|
|
# If new best path has RTs, we need to share this UPDATE with
|
|
# qualifying peers
|
|
if path_rts:
|
|
# We add Default_RTC_NLRI to path RTs so that we can send it to
|
|
# peers that have expressed interest in all paths
|
|
path_rts.append(RouteTargetMembershipNLRI.DEFAULT_RT)
|
|
# All peers that do not have RTC capability qualify
|
|
qualified_peers = set(self._get_non_rtc_peers())
|
|
# Peers that have RTC capability and have common RT with the path
|
|
# also qualify
|
|
peer_to_rtfilter_map = self._peer_to_rtfilter_map
|
|
for peer, rt_filter in peer_to_rtfilter_map.items():
|
|
# Ignore Network Controller (its not a BGP peer)
|
|
if peer is None:
|
|
continue
|
|
|
|
if rt_filter is None:
|
|
qualified_peers.add(peer)
|
|
elif rt_filter.intersection(path_rts):
|
|
qualified_peers.add(peer)
|
|
|
|
return qualified_peers
|
|
|
|
def schedule_rr_to_non_rtc_peers(self):
|
|
for route_family in SUPPORTED_GLOBAL_RF:
|
|
# Since we are dealing with peers that do not support RTC,
|
|
# ignore this address family
|
|
if route_family == RF_RTC_UC:
|
|
continue
|
|
|
|
self.req_rr_to_non_rtc_peers(route_family)
|