bgp: add bgpspaker module for non Ryu application usage

This enables you to use Ryu BGP feature as 'bgp speaker' python
library, that is, without ryu-manager, RPC API, REST API, or other Ryu
stuff, you can use Ryu BGP feature in your python application (just
import bgpspeaker.py). The sample code and the API reference docs are
included too.

Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
Reviewed-by: YAMAMOTO Takashi <yamamoto@valinux.co.jp>
This commit is contained in:
FUJITA Tomonori 2014-06-13 08:05:25 +09:00
parent 6fbd67e29b
commit 5474bf214c
10 changed files with 375 additions and 13 deletions

View File

@ -10,3 +10,5 @@ Ryu provides some useful library for your network applications.
library_packet.rst library_packet.rst
library_packet_ref.rst library_packet_ref.rst
library_of_config.rst library_of_config.rst
library_bgp_speaker.rst
library_bgp_speaker_ref.rst

View File

@ -0,0 +1,44 @@
*******************
BGP speaker library
*******************
Introduction
============
Ryu BGP speaker library helps you to enable your code to speak BGP
protocol. The library supports ipv4, ipv4 vpn, and ipv6 vpn address
families.
Example
=======
The following simple code creates a BGP instance with AS number 64512
and Router ID 10.0.0.1. It tries to establish a bgp session with a
peer (its IP is 192.168.177.32 and the AS number is 64513). The
instance advertizes some prefixes.
.. code-block:: python
import eventlet
from ryu.services.protocols.bgp.bgpspeaker import BGPSpeaker
def dump_remote_best_path_change(event):
print 'the best path changed:', event.remote_as, event.prefix,\
event.nexthop, event.is_withdraw
if __name__ == "__main__":
speaker = BGPSpeaker(as_number=64512, router_id='10.0.0.1',
best_path_change_handler=dump_remote_best_path_change)
speaker.neighbor_add('192.168.177.32', 64513)
count = 1
while True:
eventlet.sleep(30)
prefix = '10.20.' + str(count) + '.0/24'
print "add a new prefix", prefix
speaker.prefix_add(prefix)
count += 1
if count == 4:
speaker.shutdown()
break

View File

@ -0,0 +1,12 @@
*********************************
BGP speaker library API Reference
*********************************
BGPSpeaker class
================
.. autoclass:: ryu.services.protocols.bgp.bgpspeaker.BGPSpeaker
:members:
.. autoclass:: ryu.services.protocols.bgp.bgpspeaker.EventPrefix
:members:

View File

@ -178,3 +178,10 @@ def add_network(prefix):
tm = CORE_MANAGER.get_core_service().table_manager tm = CORE_MANAGER.get_core_service().table_manager
tm.add_to_ipv4_global_table(prefix) tm.add_to_ipv4_global_table(prefix)
return True return True
@register(name='network.del')
def del_network(prefix):
tm = CORE_MANAGER.get_core_service().table_manager
tm.add_to_ipv4_global_table(prefix, is_withdraw=True)
return True

View File

@ -67,12 +67,12 @@ class ApplicationException(BGPSException):
pass pass
class BGPSpeaker(RyuApp): class RyuBGPSpeaker(RyuApp):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.bind_ip = BGPSpeaker.validate_rpc_ip(CONF.bind_ip) self.bind_ip = RyuBGPSpeaker.validate_rpc_ip(CONF.bind_ip)
self.bind_port = BGPSpeaker.validate_rpc_port(CONF.bind_port) self.bind_port = RyuBGPSpeaker.validate_rpc_port(CONF.bind_port)
self.config_file = CONF.bgp_config_file self.config_file = CONF.bgp_config_file
super(BGPSpeaker, self).__init__(*args, **kwargs) super(RyuBGPSpeaker, self).__init__(*args, **kwargs)
def start(self): def start(self):
# Only two main green threads are required for APGW bgp-agent. # Only two main green threads are required for APGW bgp-agent.
@ -98,7 +98,7 @@ class BGPSpeaker(RyuApp):
net_ctrl.NC_RPC_BIND_PORT: self.bind_port}) net_ctrl.NC_RPC_BIND_PORT: self.bind_port})
LOG.debug('Started Network Controller') LOG.debug('Started Network Controller')
super(BGPSpeaker, self).start() super(RyuBGPSpeaker, self).start()
return t return t

View File

@ -0,0 +1,287 @@
# 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 offers a class to enable your code to speak BGP protocol.
"""
from ryu.lib import hub
from ryu.services.protocols.bgp.core_manager import CORE_MANAGER
from ryu.services.protocols.bgp.signals.emit import BgpSignalBus
from ryu.services.protocols.bgp.api.base import call
from ryu.services.protocols.bgp.api.base import PREFIX
from ryu.services.protocols.bgp.api.base import NEXT_HOP
from ryu.services.protocols.bgp.api.base import ROUTE_DISTINGUISHER
from ryu.services.protocols.bgp.rtconf.common import LOCAL_AS
from ryu.services.protocols.bgp.rtconf.common import ROUTER_ID
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_BGP_CONN_RETRY_TIME
from ryu.services.protocols.bgp.rtconf.common import DEFAULT_LABEL_RANGE
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 LABEL_RANGE
from ryu.services.protocols.bgp.rtconf import neighbors
from ryu.services.protocols.bgp.rtconf import vrfs
from ryu.services.protocols.bgp.rtconf.base import CAP_MBGP_IPV4
from ryu.services.protocols.bgp.rtconf.base import CAP_MBGP_VPNV4
from ryu.services.protocols.bgp.rtconf.base import CAP_MBGP_VPNV6
from ryu.services.protocols.bgp.rtconf.neighbors import DEFAULT_CAP_MBGP_IPV4
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.application import RyuBGPSpeaker
class EventPrefix(object):
"""
Used to pass an update on any best remote path to
best_path_change_handler.
================ ======================================================
Attribute Description
================ ======================================================
remote_as The AS number of a peer that caused this change
route_dist None in the case of ipv4 or ipv6 family
prefix A prefix was changed
nexthop The nexthop of the changed prefix
is_withdraw True if this prefix has gone otherwise False
================ ======================================================
"""
def __init__(self, remote_as, route_dist, prefix, nexthop, is_withdraw):
self.remote_as = remote_as
self.route_dist = route_dist
self.prefix = prefix
self.nexthop = nexthop
self.is_withdraw = is_withdraw
class BGPSpeaker(object):
def __init__(self, as_number, router_id,
bgp_server_port=DEFAULT_BGP_SERVER_PORT,
refresh_stalepath_time=DEFAULT_REFRESH_STALEPATH_TIME,
refresh_max_eor_time=DEFAULT_REFRESH_MAX_EOR_TIME,
best_path_change_handler=None):
"""Create a new BGPSpeaker object with as_number and router_id to
listen on bgp_server_port.
``as_number`` specifies an Autonomous Number. It must be an integer
between 1 and 65535.
``router_id`` specifies BGP router identifier. It must be the
string representation of an IPv4 address (e.g. 10.0.0.1).
``bgp_server_port`` specifies TCP listen port number. 179 is
used if not specified.
``refresh_stalepath_time`` causes the BGP speaker to remove
stale routes from the BGP table after the timer expires, even
if the speaker does not receive a Router-Refresh End-of-RIB
message. This feature is disabled (not implemented yet).
``refresh_max_eor_time`` causes the BGP speaker to generate a
Route-Refresh End-of-RIB message if it was not able to
generate one due to route flapping. This feature is disabled
(not implemented yet).
``best_path_change_handler``, if specified, is called when any
best remote path is changed due to an update message or remote
peer down. The handler is supposed to take one argument, the
instance of an EventPrefix class instance.
"""
super(BGPSpeaker, self).__init__()
self.speaker = RyuBGPSpeaker()
settings = {}
settings[LOCAL_AS] = as_number
settings[ROUTER_ID] = router_id
settings[BGP_SERVER_PORT] = bgp_server_port
settings[REFRESH_STALEPATH_TIME] = refresh_stalepath_time
settings[REFRESH_MAX_EOR_TIME] = refresh_max_eor_time
self._core_start(settings)
self._init_signal_listeners()
self._best_path_change_handler = best_path_change_handler
def _notify_best_path_changed(self, path):
if not path.source:
# ours
return
ev = EventPrefix(remote_as=path.source.remote_as,
route_dist=None,
prefix=path.nlri.addr + '/' + str(path.nlri.length),
nexthop=path.nexthop, is_withdraw=path.is_withdraw)
if self._best_path_change_handler:
self._best_path_change_handler(ev)
def _init_signal_listeners(self):
CORE_MANAGER.get_core_service()._signal_bus.register_listener(
BgpSignalBus.BGP_BEST_PATH_CHANGED,
lambda _, dest: self._notify_best_path_changed(dest)
)
def _core_start(self, settings):
waiter = hub.Event()
call('core.start', waiter=waiter, **settings)
waiter.wait()
def _serve_forever(self):
pass
def shutdown(self):
""" Shutdown BGP speaker
"""
call('core.stop')
def neighbor_add(self, address, remote_as,
enable_ipv4=DEFAULT_CAP_MBGP_IPV4,
enable_vpnv4=DEFAULT_CAP_MBGP_VPNV4,
enable_vpnv6=DEFAULT_CAP_MBGP_VPNV6):
""" This method registers a new neighbor. The BGP speaker tries to
establish a bgp session with the peer (accepts a connection
from the peer and also tries to connect to it).
``address`` specifies the IP address of the peer. It must be
the string representation of an IP address. Only IP v4 is
supported now.
``remote_as`` specifies the AS number of the peer. It must be
an integer between 1 and 65535.
``enable_ipv4`` enables IPv4 address family for this
neighbor. The default is True.
``enable_vpnv4`` enables VPNv4 address family for this
neighbor. The default is False.
``enable_vpnv6`` enables VPNv6 address family for this
neighbor. The default is False.
"""
bgp_neighbor = {}
bgp_neighbor[neighbors.IP_ADDRESS] = address
bgp_neighbor[neighbors.REMOTE_AS] = remote_as
bgp_neighbor[CAP_MBGP_IPV4] = enable_ipv4
bgp_neighbor[CAP_MBGP_VPNV4] = enable_vpnv4
bgp_neighbor[CAP_MBGP_VPNV6] = enable_vpnv6
call('neighbor.create', **bgp_neighbor)
def neighbor_del(self, address):
""" This method unregister the registered neighbor. If a session with
the peer exists, the session will be closed.
``address`` specifies the IP address of the peer. It must be
the string representation of an IP address.
"""
bgp_neighbor = {}
bgp_neighbor[neighbors.IP_ADDRESS] = address
call('neighbor.delete', **bgp_neighbor)
def prefix_add(self, prefix, next_hop=None, route_dist=None,
route_family=None):
""" This method adds a new prefix to be advertized.
``prefix`` must be the string representation of an IP network
(e.g., 10.1.1.0/24).
``next_hop`` specifies the next hop address for this
prefix. This parameter is necessary for only VPNv4 and VPNv6
address families.
``route_dist`` specifies a route distinguisher value. This
parameter is necessary for only VPNv4 and VPNv6 address
families.
"""
func_name = 'network.add'
networks = {}
networks[PREFIX] = prefix
if next_hop:
networks[NEXT_HOP] = next_hop
if route_dist:
func_name = 'prefix.add_local'
networks[ROUTE_DISTINGUISHER] = route_dist
call(func_name, **networks)
def prefix_del(self, prefix, route_dist=None, route_family=None):
""" This method deletes a advertized prefix.
``prefix`` must be the string representation of an IP network
(e.g., 10.1.1.0/24).
``route_dist`` specifies a route distinguisher value. This
parameter is necessary for only VPNv4 and VPNv6 address
families.
"""
func_name = 'network.del'
networks = {}
networks[PREFIX] = prefix
if route_dist:
func_name = 'prefix.delete_local'
networks[ROUTE_DISTINGUISHER] = route_dist
call(func_name, **networks)
def vrf_add(self, route_dist, import_rts, export_rts, site_of_origins=None,
multi_exit_disc=None):
""" This method adds a new vrf used for VPN.
``route_dist`` specifies a route distinguisher value.
``import_rts`` specifies route targets to be imported.
``export_rts`` specifies route targets to be exported.
"""
vrf = {}
vrf[vrfs.ROUTE_DISTINGUISHER] = route_dist
vrf[vrfs.IMPORT_RTS] = import_rts
vrf[vrfs.EXPORT_RTS] = export_rts
call('vrf.create', **vrf)
def vrf_del(self, route_dist):
""" This method deletes the existing vrf.
``route_dist`` specifies a route distinguisher value.
"""
vrf = {}
vrf[vrfs.ROUTE_DISTINGUISHER] = route_dist
call('vrf.delete', **vrf)
def vrfs_get(self):
show = {}
show['params'] = ['vrf', 'routes', 'all']
return call('operator.show', **show)
def rib_get(self, family='ipv4'):
""" This method returns the BGP routing information in a json
format. This will be improved soon.
``family`` specifies the address family of the RIB.
"""
show = {}
show['params'] = ['rib', family]
return call('operator.show', **show)

View File

@ -477,12 +477,13 @@ class TableCoreManager(object):
raise BgpCoreError(desc='Invalid Ipv6 prefix or nexthop.') raise BgpCoreError(desc='Invalid Ipv6 prefix or nexthop.')
ip6, masklen = prefix.split('/') ip6, masklen = prefix.split('/')
prefix = IP6AddrPrefix(int(masklen), ip6) prefix = IP6AddrPrefix(int(masklen), ip6)
return vrf_table.insert_vrf_path( return vrf_table.insert_vrf_path(
prefix, next_hop=next_hop, prefix, next_hop=next_hop,
gen_lbl=True gen_lbl=True
) )
def add_to_ipv4_global_table(self, prefix): def add_to_ipv4_global_table(self, prefix, is_withdraw=False):
ip, masklen = prefix.split('/') ip, masklen = prefix.split('/')
_nlri = IPAddrPrefix(int(masklen), ip) _nlri = IPAddrPrefix(int(masklen), ip)
src_ver_num = 1 src_ver_num = 1
@ -497,7 +498,8 @@ class TableCoreManager(object):
pathattrs[BGP_ATTR_TYPE_AS_PATH] = aspath pathattrs[BGP_ATTR_TYPE_AS_PATH] = aspath
new_path = Ipv4Path(peer, _nlri, src_ver_num, new_path = Ipv4Path(peer, _nlri, src_ver_num,
pattrs=pathattrs, nexthop=nexthop) pattrs=pathattrs, nexthop=nexthop,
is_withdraw=is_withdraw)
# add to global ipv4 table and propagates to neighbors # add to global ipv4 table and propagates to neighbors
self.learn_path(new_path) self.learn_path(new_path)

View File

@ -38,9 +38,11 @@ class IPv4Dest(Destination, NonVrfPathProcessingMixin):
def _best_path_lost(self): def _best_path_lost(self):
NonVrfPathProcessingMixin._best_path_lost(self) NonVrfPathProcessingMixin._best_path_lost(self)
self._core_service._signal_bus.best_path_changed(self)
def _new_best_path(self, best_path): def _new_best_path(self, best_path):
NonVrfPathProcessingMixin._new_best_path(self, best_path) NonVrfPathProcessingMixin._new_best_path(self, best_path)
self._core_service._signal_bus.best_path_changed(best_path)
class Ipv4Table(Table): class Ipv4Table(Table):

View File

@ -773,12 +773,12 @@ class Peer(Source, Sink, NeighborConfListener, Activity):
if unkown_opttrans_attrs: if unkown_opttrans_attrs:
new_pathattr.extend(unkown_opttrans_attrs.values()) new_pathattr.extend(unkown_opttrans_attrs.values())
if isinstance(path, Ipv4Path): if isinstance(path, Ipv4Path):
update = BGPUpdate(path_attributes=new_pathattr, update = BGPUpdate(path_attributes=new_pathattr,
nlri=nlri_list) nlri=nlri_list)
else: else:
update = BGPUpdate(path_attributes=new_pathattr) update = BGPUpdate(path_attributes=new_pathattr)
return update return update
def _connect_loop(self, client_factory): def _connect_loop(self, client_factory):
"""In the current greeenlet we try to establish connection with peer. """In the current greeenlet we try to establish connection with peer.

View File

@ -11,6 +11,7 @@ class BgpSignalBus(SignalBus):
BGP_VRF_STATS_CONFIG_CHANGED = ( BGP_VRF_STATS_CONFIG_CHANGED = (
'core', 'vrf', 'config', 'stats', 'changed' 'core', 'vrf', 'config', 'stats', 'changed'
) )
BGP_BEST_PATH_CHANGED = ('core', 'best', 'changed')
def bgp_error(self, peer, code, subcode, reason): def bgp_error(self, peer, code, subcode, reason):
return self.emit_signal( return self.emit_signal(
@ -53,3 +54,8 @@ class BgpSignalBus(SignalBus):
self.BGP_VRF_STATS_CONFIG_CHANGED, self.BGP_VRF_STATS_CONFIG_CHANGED,
vrf_conf vrf_conf
) )
def best_path_changed(self, best_path):
return self.emit_signal(
self.BGP_BEST_PATH_CHANGED,
best_path)