1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431
|
# Copyright (C) 2014 Nippon Telegraph and Telephone Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This module provides a convenient application for using OSKen BGPSpeaker and for
writing your BGP application.
It reads a configuration file which includes settings for neighbors, routes
and some others.
Please refer to ``os_ken/services/protocols/bgp/bgp_sample_conf.py`` for the
sample configuration.
Usage Example::
$ osken-manager os_ken/services/protocols/bgp/application.py \\
--bgp-app-config-file os_ken/services/protocols/bgp/bgp_sample_conf.py
SSH Console
===========
You can also use the SSH console and see the RIB and do some operations from
this console.
The SSH port and username/password can be configured by the configuration file.
You can check the help by hitting '?' key in this interface.
Example::
$ ssh localhost -p 4990
Hello, this is OSKen BGP speaker (version 4.19).
bgpd> # Hit '?' key
clear - allows to reset BGP connections
help - show this help
quit - exit this session
set - set runtime settings
show - shows runtime state information
bgpd>
bgpd> show rib all
Status codes: * valid, > best
Origin codes: i - IGP, e - EGP, ? - incomplete
Network Labels Next Hop Reason Metric LocPrf Path
*> 10.10.1.0/24 None 0.0.0.0 Only Path i
bgpd>
Integration with Other Applications
===================================
``os_ken.services.protocols.bgp.application.OSKenBGPSpeaker`` will notifies the
following events to other OSKen applications.
- ``EventBestPathChanged``
- ``EventAdjRibInChanged``
- ``EventPeerDown``
- ``EventPeerUp``
To catch these events, specify ``@set_ev_cls()`` decorator to the event
handlers in the OSKen applications.
Example Application::
# my_bgp_app.py
from os_ken.base import app_manager
from os_ken.controller.handler import set_ev_cls
from os_ken.services.protocols.bgp import application as bgp_application
class MyBGPApp(app_manager.OSKenApp):
_CONTEXTS = {
'os_kenbgpspeaker': bgp_application.OSKenBGPSpeaker,
}
def __init__(self, *args, **kwargs):
super(MyBGPApp, self).__init__(*args, **kwargs)
# Stores "os_ken.services.protocols.bgp.application.OSKenBGPSpeaker"
# instance in order to call the APIs of
# "os_ken.services.protocols.bgp.bgpspeaker.BGPSpeaker" via
# "self.app.speaker".
# Please note at this time, "BGPSpeaker" is NOT instantiated yet.
self.app = kwargs['os_kenbgpspeaker']
@set_ev_cls(bgp_application.EventBestPathChanged)
def _best_patch_changed_handler(self, ev):
self.logger.info(
'Best path changed: is_withdraw=%s, path=%s',
ev.is_withdraw, ev.path)
Usage Example::
$ osken-manager my_bgp_app.py \\
--bgp-app-config-file os_ken/services/protocols/bgp/bgp_sample_conf.py
.. note::
For the APIs for ``os_ken.services.protocols.bgp.bgpspeaker.BGPSpeaker``,
please refer to :doc:`../library_bgp_speaker_ref`.
API Reference
=============
"""
import logging
import os
from os_ken import cfg
from os_ken.lib import hub
from os_ken.utils import load_source
from os_ken.base.app_manager import OSKenApp
from os_ken.controller.event import EventBase
from os_ken.services.protocols.bgp.base import add_bgp_error_metadata
from os_ken.services.protocols.bgp.base import BGPSException
from os_ken.services.protocols.bgp.base import BIN_ERROR
from os_ken.services.protocols.bgp.bgpspeaker import BGPSpeaker
from os_ken.services.protocols.bgp.net_ctrl import NET_CONTROLLER
from os_ken.services.protocols.bgp.net_ctrl import NC_RPC_BIND_IP
from os_ken.services.protocols.bgp.net_ctrl import NC_RPC_BIND_PORT
from os_ken.services.protocols.bgp.rtconf.base import RuntimeConfigError
from os_ken.services.protocols.bgp.rtconf.common import LOCAL_AS
from os_ken.services.protocols.bgp.rtconf.common import ROUTER_ID
from os_ken.services.protocols.bgp.utils.validation import is_valid_ipv4
from os_ken.services.protocols.bgp.utils.validation import is_valid_ipv6
LOG = logging.getLogger('bgpspeaker.application')
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`.
"""
pass
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 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)
# 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))
class EventBestPathChanged(EventBase):
"""
Event called when any best remote path is changed due to UPDATE messages
or remote peer's down.
This event is the wrapper for ``best_path_change_handler`` of
``bgpspeaker.BGPSpeaker``.
``path`` attribute contains an instance of ``info_base.base.Path``
subclasses.
If ``is_withdraw`` attribute is ``True``, ``path`` attribute has the
information of the withdraw route.
"""
def __init__(self, path, is_withdraw):
super(EventBestPathChanged, self).__init__()
self.path = path
self.is_withdraw = is_withdraw
class EventAdjRibInChanged(EventBase):
"""
Event called when any adj-RIB-in path is changed due to UPDATE messages
or remote peer's down.
This event is the wrapper for ``adj_rib_in_change_handler`` of
``bgpspeaker.BGPSpeaker``.
``path`` attribute contains an instance of ``info_base.base.Path``
subclasses.
If ``is_withdraw`` attribute is ``True``, ``path`` attribute has the
information of the withdraw route.
``peer_ip`` is the peer's IP address who sent this path.
``peer_as`` is the peer's AS number who sent this path.
"""
def __init__(self, path, is_withdraw, peer_ip, peer_as):
super(EventAdjRibInChanged, self).__init__()
self.path = path
self.is_withdraw = is_withdraw
self.peer_ip = peer_ip
self.peer_as = peer_as
class EventPeerDown(EventBase):
"""
Event called when the session to the remote peer goes down.
This event is the wrapper for ``peer_down_handler`` of
``bgpspeaker.BGPSpeaker``.
``remote_ip`` attribute is the IP address of the remote peer.
``remote_as`` attribute is the AS number of the remote peer.
"""
def __init__(self, remote_ip, remote_as):
super(EventPeerDown, self).__init__()
self.remote_ip = remote_ip
self.remote_as = remote_as
class EventPeerUp(EventBase):
"""
Event called when the session to the remote peer goes up.
This event is the wrapper for ``peer_up_handler`` of
``bgpspeaker.BGPSpeaker``.
``remote_ip`` attribute is the IP address of the remote peer.
``remote_as`` attribute is the AS number of the remote peer.
"""
def __init__(self, remote_ip, remote_as):
super(EventPeerUp, self).__init__()
self.remote_ip = remote_ip
self.remote_as = remote_as
class OSKenBGPSpeaker(OSKenApp):
"""
Base application for implementing BGP applications.
"""
_EVENTS = [
EventBestPathChanged,
EventAdjRibInChanged,
EventPeerDown,
EventPeerUp,
]
def __init__(self, *args, **kwargs):
super(OSKenBGPSpeaker, self).__init__(*args, **kwargs)
self.config_file = CONF.config_file
# BGPSpeaker instance (not instantiated yet)
self.speaker = None
def start(self):
super(OSKenBGPSpeaker, self).start()
# 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...')
# Note: paramiko used in bgp.operator.ssh is the optional
# requirements, imports bgp.operator.ssh here.
from os_ken.services.protocols.bgp.operator import ssh
hub.spawn(ssh.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):
"""
Starts BGPSpeaker using the given settings.
"""
# Check required settings.
_required_settings = (
LOCAL_AS,
ROUTER_ID,
)
for required in _required_settings:
if required not in settings:
raise ApplicationException(
desc='Required BGP configuration missing: %s' % required)
# Set event notify handlers if no corresponding handler specified.
settings.setdefault(
'best_path_change_handler', self._notify_best_path_changed_event)
settings.setdefault(
'adj_rib_in_change_handler', self._notify_adj_rib_in_changed_event)
settings.setdefault(
'peer_down_handler', self._notify_peer_down_event)
settings.setdefault(
'peer_up_handler', self._notify_peer_up_event)
# Pop settings other than creating BGPSpeaker instance.
neighbors_settings = settings.pop('neighbors', [])
vrfs_settings = settings.pop('vrfs', [])
routes_settings = settings.pop('routes', [])
# Create BGPSpeaker instance.
LOG.debug('Starting BGPSpeaker...')
settings.setdefault('as_number', settings.pop(LOCAL_AS))
self.speaker = BGPSpeaker(**settings)
# Add neighbors.
LOG.debug('Adding neighbors...')
self._add_neighbors(neighbors_settings)
# Add VRFs.
LOG.debug('Adding VRFs...')
self._add_vrfs(vrfs_settings)
# Add routes
LOG.debug('Adding routes...')
self._add_routes(routes_settings)
def _notify_best_path_changed_event(self, ev):
ev = EventBestPathChanged(ev.path, ev.is_withdraw)
self.send_event_to_observers(ev)
def _notify_adj_rib_in_changed_event(self, ev, peer_ip, peer_as):
ev = EventAdjRibInChanged(ev.path, ev.is_withdraw, peer_ip, peer_as)
self.send_event_to_observers(ev)
def _notify_peer_down_event(self, remote_ip, remote_as):
ev = EventPeerDown(remote_ip, remote_as)
self.send_event_to_observers(ev)
def _notify_peer_up_event(self, remote_ip, remote_as):
ev = EventPeerUp(remote_ip, remote_as)
self.send_event_to_observers(ev)
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.
"""
for neighbor_settings in settings:
LOG.debug('Adding neighbor settings: %s', neighbor_settings)
try:
self.speaker.neighbor_add(**neighbor_settings)
except RuntimeConfigError as e:
LOG.exception(e)
def _add_vrfs(self, settings):
"""
Add BGP VRFs from the given settings.
All valid VRFs are loaded.
Miss-configured VRFs are ignored and errors are logged.
"""
for vrf_settings in settings:
LOG.debug('Adding VRF settings: %s', vrf_settings)
try:
self.speaker.vrf_add(**vrf_settings)
except RuntimeConfigError as e:
LOG.exception(e)
def _add_routes(self, settings):
"""
Add BGP routes from given settings.
All valid routes are loaded.
Miss-configured routes are ignored and errors are logged.
"""
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
elif 'flowspec_family' in route_settings:
prefix_add = self.speaker.flowspec_prefix_add
else:
LOG.debug('Skip invalid route settings: %s', route_settings)
continue
LOG.debug('Adding route settings: %s', route_settings)
try:
prefix_add(**route_settings)
except RuntimeConfigError as e:
LOG.exception(e)
|