deb-ryu/ryu/services/protocols/bgp/core_managers/peer_manager.py

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)