diff options
-rw-r--r-- | ryu/services/protocols/bgp/application.py | 304 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/bgp_sample_conf.py | 120 |
2 files changed, 232 insertions, 192 deletions
diff --git a/ryu/services/protocols/bgp/application.py b/ryu/services/protocols/bgp/application.py index a73e83e1..c8d0f6af 100644 --- a/ryu/services/protocols/bgp/application.py +++ b/ryu/services/protocols/bgp/application.py @@ -12,221 +12,213 @@ # implied. # See the License for the specific language governing permissions and # limitations under the License. + """ Defines bases classes to create a BGP application. """ -import logging -from logging.config import dictConfig -import traceback -from oslo_config import cfg +import logging +import os +from ryu import cfg from ryu.lib import hub from ryu.utils import load_source from ryu.base.app_manager import RyuApp -from ryu.services.protocols.bgp.api.base import call from ryu.services.protocols.bgp.base import add_bgp_error_metadata from ryu.services.protocols.bgp.base import BGPSException from ryu.services.protocols.bgp.base import BIN_ERROR -from ryu.services.protocols.bgp.core_manager import CORE_MANAGER -from ryu.services.protocols.bgp import net_ctrl +from ryu.services.protocols.bgp.bgpspeaker import BGPSpeaker +from ryu.services.protocols.bgp.net_ctrl import NET_CONTROLLER +from ryu.services.protocols.bgp.net_ctrl import NC_RPC_BIND_IP +from ryu.services.protocols.bgp.net_ctrl import NC_RPC_BIND_PORT +from ryu.services.protocols.bgp.operator.ssh import SSH_CLI_CONTROLLER from ryu.services.protocols.bgp.rtconf.base import RuntimeConfigError from ryu.services.protocols.bgp.rtconf.common import BGP_SERVER_PORT from ryu.services.protocols.bgp.rtconf.common import DEFAULT_BGP_SERVER_PORT -from ryu.services.protocols.bgp.rtconf.common import \ - DEFAULT_REFRESH_MAX_EOR_TIME -from ryu.services.protocols.bgp.rtconf.common import \ - DEFAULT_REFRESH_STALEPATH_TIME +from ryu.services.protocols.bgp.rtconf.common import ( + DEFAULT_REFRESH_MAX_EOR_TIME, DEFAULT_REFRESH_STALEPATH_TIME) from ryu.services.protocols.bgp.rtconf.common import DEFAULT_LABEL_RANGE from ryu.services.protocols.bgp.rtconf.common import LABEL_RANGE from ryu.services.protocols.bgp.rtconf.common import LOCAL_AS from ryu.services.protocols.bgp.rtconf.common import REFRESH_MAX_EOR_TIME from ryu.services.protocols.bgp.rtconf.common import REFRESH_STALEPATH_TIME from ryu.services.protocols.bgp.rtconf.common import ROUTER_ID -from ryu.services.protocols.bgp.rtconf import neighbors -from ryu.services.protocols.bgp.rtconf import vrfs from ryu.services.protocols.bgp.utils.validation import is_valid_ipv4 -from ryu.services.protocols.bgp.operator import ssh +from ryu.services.protocols.bgp.utils.validation import is_valid_ipv6 LOG = logging.getLogger('bgpspeaker.application') -CONF = cfg.CONF -CONF.register_opts([ - cfg.IntOpt('bind-port', default=50002, help='rpc-port'), - cfg.StrOpt('bind-ip', default='0.0.0.0', help='rpc-bind-ip'), - cfg.StrOpt('bgp-config-file', default=None, - help='bgp-config-file') -]) +CONF = cfg.CONF['bgp-app'] @add_bgp_error_metadata(code=BIN_ERROR, sub_code=1, def_desc='Unknown bootstrap exception.') class ApplicationException(BGPSException): - """Specific Base exception related to `BSPSpeaker`.""" + """ + Specific Base exception related to `BSPSpeaker`. + """ pass -class RyuBGPSpeaker(RyuApp): - def __init__(self, *args, **kwargs): - self.bind_ip = RyuBGPSpeaker.validate_rpc_ip(CONF.bind_ip) - self.bind_port = RyuBGPSpeaker.validate_rpc_port(CONF.bind_port) - self.config_file = CONF.bgp_config_file - super(RyuBGPSpeaker, self).__init__(*args, **kwargs) +def validate_rpc_host(ip): + """ + Validates the given ip for use as RPC server address. + """ + if not is_valid_ipv4(ip) and not is_valid_ipv6(ip): + raise ApplicationException( + desc='Invalid RPC ip address: %s' % ip) + return ip - def start(self): - # Only two main green threads are required for APGW bgp-agent. - # One for NetworkController, another for BGPS core. - # If configuration file was provided and loaded successfully. We start - # BGPS core using these settings. If no configuration file is provided - # or if configuration file is missing minimum required settings BGPS - # core is not started. - if self.config_file: - LOG.debug('Loading config. from settings file.') - settings = self.load_config(self.config_file) - # Configure log settings, if available. - if getattr(settings, 'LOGGING', None): - dictConfig(settings.LOGGING) - - if getattr(settings, 'BGP', None): - self._start_core(settings) - - if getattr(settings, 'SSH', None) is not None: - hub.spawn(ssh.SSH_CLI_CONTROLLER.start, None, **settings.SSH) - # Start Network Controller to server RPC peers. - t = hub.spawn(net_ctrl.NET_CONTROLLER.start, *[], - **{net_ctrl.NC_RPC_BIND_IP: self.bind_ip, - net_ctrl.NC_RPC_BIND_PORT: self.bind_port}) - LOG.debug('Started Network Controller') +def load_config(config_file): + """ + Validates the given file for use as the settings file for BGPSpeaker + and loads the configuration from the given file as a module instance. + """ + if not config_file or not os.path.isfile(config_file): + raise ApplicationException( + desc='Invalid configuration file: %s' % config_file) - super(RyuBGPSpeaker, self).start() + # Loads the configuration from the given file, if available. + try: + return load_source('bgpspeaker.application.settings', config_file) + except Exception as e: + raise ApplicationException(desc=str(e)) - return t - @classmethod - def validate_rpc_ip(cls, ip): - """Validates given ip for use as rpc host bind address. - """ - if not is_valid_ipv4(ip): - raise ApplicationException(desc='Invalid rpc ip address.') - return ip +class RyuBGPSpeaker(RyuApp): - @classmethod - def validate_rpc_port(cls, port): - """Validates give port for use as rpc server port. - """ - if not port: - raise ApplicationException(desc='Invalid rpc port number.') - if isinstance(port, str): - port = int(port) + def __init__(self, *args, **kwargs): + super(RyuBGPSpeaker, self).__init__(*args, **kwargs) + self.config_file = CONF.config_file - return port + # BGPSpeaker instance (not instantiated yet) + self.speaker = None - def load_config(self, config_file): - """Validates give file as settings file for BGPSpeaker. + def start(self): + super(RyuBGPSpeaker, self).start() - Load the configuration from file as settings module. + # If configuration file was provided and loaded successfully, we start + # BGPSpeaker using the given settings. + # If no configuration file is provided or if any minimum required + # setting is missing, BGPSpeaker will not be started. + if self.config_file: + LOG.debug('Loading config file %s...', self.config_file) + settings = load_config(self.config_file) + + # Configure logging settings, if available. + if hasattr(settings, 'LOGGING'): + # Not implemented yet. + LOG.debug('Loading LOGGING settings... (NOT implemented yet)') + # from logging.config import dictConfig + # logging_settings = dictConfig(settings.LOGGING) + + # Configure BGP settings, if available. + if hasattr(settings, 'BGP'): + LOG.debug('Loading BGP settings...') + self._start_speaker(settings.BGP) + + # Configure SSH settings, if available. + if hasattr(settings, 'SSH'): + LOG.debug('Loading SSH settings...') + hub.spawn(SSH_CLI_CONTROLLER.start, **settings.SSH) + + # Start RPC server with the given RPC settings. + rpc_settings = { + NC_RPC_BIND_PORT: CONF.rpc_port, + NC_RPC_BIND_IP: validate_rpc_host(CONF.rpc_host), + } + return hub.spawn(NET_CONTROLLER.start, **rpc_settings) + + def _start_speaker(self, settings): """ - if not config_file or not isinstance(config_file, str): - raise ApplicationException('Invalid configuration file.') - - # Check if file can be read - try: - return load_source('settings', config_file) - except Exception as e: - raise ApplicationException(desc=str(e)) - - def _start_core(self, settings): - """Starts BGPS core using setting and given pool. + Starts BGPSpeaker using the given settings. """ - # Get common settings - routing_settings = settings.BGP.get('routing') - common_settings = {} + # Settings for starting BGPSpeaker + bgp_settings = {} - # Get required common settings. + # Get required settings. try: - common_settings[LOCAL_AS] = routing_settings.pop(LOCAL_AS) - common_settings[ROUTER_ID] = routing_settings.pop(ROUTER_ID) + bgp_settings['as_number'] = settings.get(LOCAL_AS) + bgp_settings['router_id'] = settings.get(ROUTER_ID) except KeyError as e: raise ApplicationException( - desc='Required minimum configuration missing %s' % - e) - - # Get optional common settings - common_settings[BGP_SERVER_PORT] = \ - routing_settings.get(BGP_SERVER_PORT, DEFAULT_BGP_SERVER_PORT) - common_settings[REFRESH_STALEPATH_TIME] = \ - routing_settings.get(REFRESH_STALEPATH_TIME, - DEFAULT_REFRESH_STALEPATH_TIME) - common_settings[REFRESH_MAX_EOR_TIME] = \ - routing_settings.get(REFRESH_MAX_EOR_TIME, - DEFAULT_REFRESH_MAX_EOR_TIME) - common_settings[LABEL_RANGE] = \ - routing_settings.get(LABEL_RANGE, DEFAULT_LABEL_RANGE) - - # Start BGPS core service - waiter = hub.Event() - call('core.start', waiter=waiter, **common_settings) - waiter.wait() - - LOG.debug('Core started %s', CORE_MANAGER.started) - # Core manager started add configured neighbor and vrfs - if CORE_MANAGER.started: - # Add neighbors. - self._add_neighbors(routing_settings) - - # Add Vrfs. - self._add_vrfs(routing_settings) - - # Add Networks - self._add_networks(routing_settings) - - def _add_neighbors(self, routing_settings): - """Add bgp peers/neighbors from given settings to BGPS runtime. - - All valid neighbors are loaded. Miss-configured neighbors are ignored - and error is logged. + desc='Required BGP configuration missing: %s' % e) + + # Get optional settings. + bgp_settings[BGP_SERVER_PORT] = settings.get( + BGP_SERVER_PORT, DEFAULT_BGP_SERVER_PORT) + bgp_settings[REFRESH_STALEPATH_TIME] = settings.get( + REFRESH_STALEPATH_TIME, DEFAULT_REFRESH_STALEPATH_TIME) + bgp_settings[REFRESH_MAX_EOR_TIME] = settings.get( + REFRESH_MAX_EOR_TIME, DEFAULT_REFRESH_MAX_EOR_TIME) + bgp_settings[LABEL_RANGE] = settings.get( + LABEL_RANGE, DEFAULT_LABEL_RANGE) + + # Create BGPSpeaker instance. + LOG.debug('Starting BGPSpeaker...') + self.speaker = BGPSpeaker(**bgp_settings) + + # Add neighbors. + LOG.debug('Adding neighbors...') + self._add_neighbors(settings.get('neighbors', [])) + + # Add VRFs. + LOG.debug('Adding VRFs...') + self._add_vrfs(settings.get('vrfs', [])) + + # Add Networks + LOG.debug('Adding routes...') + self._add_routes(settings.get('routes', [])) + + def _add_neighbors(self, settings): + """ + Add BGP neighbors from the given settings. + + All valid neighbors are loaded. + Miss-configured neighbors are ignored and errors are logged. """ - bgp_neighbors = routing_settings.setdefault('bgp_neighbors', {}) - for ip, bgp_neighbor in bgp_neighbors.items(): + for neighbor_settings in settings: + LOG.debug('Adding neighbor settings: %s', neighbor_settings) try: - bgp_neighbor[neighbors.IP_ADDRESS] = ip - call('neighbor.create', **bgp_neighbor) - LOG.debug('Added neighbor %s', ip) - except RuntimeConfigError as re: - LOG.error(re) - LOG.error(traceback.format_exc()) - continue + self.speaker.neighbor_add(**neighbor_settings) + except RuntimeConfigError as e: + LOG.exception(e) - def _add_vrfs(self, routing_settings): - """Add VRFs from given settings to BGPS runtime. + def _add_vrfs(self, settings): + """ + Add BGP VRFs from the given settings. - If any of the VRFs are miss-configured errors are logged. All valid VRFs are loaded. + Miss-configured VRFs are ignored and errors are logged. """ - vpns_conf = routing_settings.setdefault('vpns', {}) - for vrfname, vrf in vpns_conf.items(): + for vrf_settings in settings: + LOG.debug('Adding VRF settings: %s', vrf_settings) try: - vrf[vrfs.VRF_NAME] = vrfname - call('vrf.create', **vrf) - LOG.debug('Added vrf %s', vrf) + self.speaker.vrf_add(**vrf_settings) except RuntimeConfigError as e: - LOG.error(e) - continue + LOG.exception(e) - def _add_networks(self, routing_settings): - """Add networks from given settings to BGPS runtime. + def _add_routes(self, settings): + """ + Add BGP routes from given settings. - If any of the networks are miss-configured errors are logged. - All valid networks are loaded. + All valid routes are loaded. + Miss-configured routes are ignored and errors are logged. """ - networks = routing_settings.setdefault('networks', []) - for prefix in networks: + for route_settings in settings: + if 'prefix' in route_settings: + prefix_add = self.speaker.prefix_add + elif 'route_type' in route_settings: + prefix_add = self.speaker.evpn_prefix_add + else: + LOG.debug('Skip invalid route settings: %s', route_settings) + continue + + LOG.debug('Adding route settings: %s', route_settings) try: - call('network.add', prefix=prefix) - LOG.debug('Added network %s', prefix) + prefix_add(**route_settings) except RuntimeConfigError as e: - LOG.error(e) - continue + LOG.exception(e) diff --git a/ryu/services/protocols/bgp/bgp_sample_conf.py b/ryu/services/protocols/bgp/bgp_sample_conf.py index b3801563..4c40fef6 100644 --- a/ryu/services/protocols/bgp/bgp_sample_conf.py +++ b/ryu/services/protocols/bgp/bgp_sample_conf.py @@ -1,49 +1,98 @@ import os +from ryu.services.protocols.bgp.bgpspeaker import RF_VPN_V4 +from ryu.services.protocols.bgp.bgpspeaker import RF_L2_EVPN +from ryu.services.protocols.bgp.bgpspeaker import EVPN_MAC_IP_ADV_ROUTE +from ryu.services.protocols.bgp.bgpspeaker import TUNNEL_TYPE_VXLAN + + # ============================================================================= # BGP configuration. # ============================================================================= BGP = { - # General BGP configuration. - 'routing': { - # ASN for this BGP instance. - 'local_as': 64512, - - # BGP Router ID. - 'router_id': '10.10.0.1', - - # We list all BGP neighbors below. We establish EBGP sessions with peer - # with different AS number then configured above. We will - # establish IBGP session if AS number is same. - 'bgp_neighbors': { - '10.0.0.1': { - 'remote_as': 64513, - 'multi_exit_disc': 100 - }, - '10.10.0.2': { - 'remote_as': 64514, - }, - }, - - 'networks': [ - '10.20.0.0/24', - '10.30.0.0/24', - '10.40.0.0/16', - '10.50.0.0/16', - ], - }, + # AS number for this BGP instance. + 'local_as': 65001, + # BGP Router ID. + 'router_id': '172.17.0.1', + + # List of BGP neighbors. + # The parameters for each neighbor are the same as the arguments of + # BGPSpeaker.neighbor_add() method. + 'neighbors': [ + { + 'address': '172.17.0.2', + 'remote_as': 65002, + 'enable_ipv4': True, + 'enable_vpnv4': True, + }, + { + 'address': '172.17.0.3', + 'remote_as': 65000, + 'enable_evpn': True, + }, + ], + + # List of BGP VRF tables. + # The parameters for each VRF table are the same as the arguments of + # BGPSpeaker.vrf_add() method. + 'vrfs': [ + { + 'route_dist': '65001:100', + 'import_rts': ['65001:100'], + 'export_rts': ['65001:100'], + 'route_family': RF_VPN_V4, + }, + { + 'route_dist': '65000:200', + 'import_rts': ['65000:200'], + 'export_rts': ['65000:200'], + 'route_family': RF_L2_EVPN, + }, + ], + + # List of BGP routes. + # The parameters for each route are the same as the arguments of + # BGPSpeaker.prefix_add() or BGPSpeaker.evpn_prefix_add() method. + 'routes': [ + # Example of IPv4 prefix + { + 'prefix': '10.10.1.0/24', + }, + # Example of VPNv4 prefix + { + 'prefix': '10.20.1.0/24', + 'next_hop': '172.17.0.1', + 'route_dist': '65001:100', + }, + # Example of EVPN prefix + { + 'route_type': EVPN_MAC_IP_ADV_ROUTE, + 'route_dist': '65000:200', + 'esi': 0, + 'ethernet_tag_id': 0, + 'tunnel_type': TUNNEL_TYPE_VXLAN, + 'vni': 200, + 'mac_addr': 'aa:bb:cc:dd:ee:ff', + 'ip_addr': '10.30.1.1', + 'next_hop': '172.17.0.1', + }, + ], } -# SSH = { -# 'ssh_port': 4990, -# 'ssh_host': 'localhost', -# 'ssh_hostkey': '/etc/ssh_host_rsa_key', -# 'ssh_username': 'ryu', -# 'ssh_password': 'ryu' -# } +# ============================================================================= +# SSH server configuration. +# ============================================================================= +SSH = { + 'ssh_port': 4990, + 'ssh_host': 'localhost', + # 'ssh_host_key': '/etc/ssh_host_rsa_key', + # 'ssh_username': 'ryu', + # 'ssh_password': 'ryu', +} + # ============================================================================= # Logging configuration. @@ -101,7 +150,6 @@ LOGGING = { 'loggers': { 'bgpspeaker': { 'handlers': ['console', 'log_file'], - 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False, }, |