diff options
-rw-r--r-- | doc/source/library.rst | 2 | ||||
-rw-r--r-- | doc/source/library_bgp_speaker.rst | 44 | ||||
-rw-r--r-- | doc/source/library_bgp_speaker_ref.rst | 12 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/api/rtconf.py | 7 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/application.py | 10 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/bgpspeaker.py | 287 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/core_managers/table_manager.py | 6 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/info_base/ipv4.py | 2 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/peer.py | 12 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/signals/emit.py | 6 |
10 files changed, 375 insertions, 13 deletions
diff --git a/doc/source/library.rst b/doc/source/library.rst index eee6877a..38cc3872 100644 --- a/doc/source/library.rst +++ b/doc/source/library.rst @@ -10,3 +10,5 @@ Ryu provides some useful library for your network applications. library_packet.rst library_packet_ref.rst library_of_config.rst + library_bgp_speaker.rst + library_bgp_speaker_ref.rst diff --git a/doc/source/library_bgp_speaker.rst b/doc/source/library_bgp_speaker.rst new file mode 100644 index 00000000..443242cf --- /dev/null +++ b/doc/source/library_bgp_speaker.rst @@ -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 diff --git a/doc/source/library_bgp_speaker_ref.rst b/doc/source/library_bgp_speaker_ref.rst new file mode 100644 index 00000000..493f322e --- /dev/null +++ b/doc/source/library_bgp_speaker_ref.rst @@ -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: diff --git a/ryu/services/protocols/bgp/api/rtconf.py b/ryu/services/protocols/bgp/api/rtconf.py index bca6cc78..2869da5e 100644 --- a/ryu/services/protocols/bgp/api/rtconf.py +++ b/ryu/services/protocols/bgp/api/rtconf.py @@ -178,3 +178,10 @@ def add_network(prefix): tm = CORE_MANAGER.get_core_service().table_manager tm.add_to_ipv4_global_table(prefix) 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 diff --git a/ryu/services/protocols/bgp/application.py b/ryu/services/protocols/bgp/application.py index 163e75f4..b803d369 100644 --- a/ryu/services/protocols/bgp/application.py +++ b/ryu/services/protocols/bgp/application.py @@ -67,12 +67,12 @@ class ApplicationException(BGPSException): pass -class BGPSpeaker(RyuApp): +class RyuBGPSpeaker(RyuApp): def __init__(self, *args, **kwargs): - self.bind_ip = BGPSpeaker.validate_rpc_ip(CONF.bind_ip) - self.bind_port = BGPSpeaker.validate_rpc_port(CONF.bind_port) + 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(BGPSpeaker, self).__init__(*args, **kwargs) + super(RyuBGPSpeaker, self).__init__(*args, **kwargs) def start(self): # 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}) LOG.debug('Started Network Controller') - super(BGPSpeaker, self).start() + super(RyuBGPSpeaker, self).start() return t diff --git a/ryu/services/protocols/bgp/bgpspeaker.py b/ryu/services/protocols/bgp/bgpspeaker.py new file mode 100644 index 00000000..b0726004 --- /dev/null +++ b/ryu/services/protocols/bgp/bgpspeaker.py @@ -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) diff --git a/ryu/services/protocols/bgp/core_managers/table_manager.py b/ryu/services/protocols/bgp/core_managers/table_manager.py index d6535b81..f8265dd7 100644 --- a/ryu/services/protocols/bgp/core_managers/table_manager.py +++ b/ryu/services/protocols/bgp/core_managers/table_manager.py @@ -477,12 +477,13 @@ class TableCoreManager(object): raise BgpCoreError(desc='Invalid Ipv6 prefix or nexthop.') ip6, masklen = prefix.split('/') prefix = IP6AddrPrefix(int(masklen), ip6) + return vrf_table.insert_vrf_path( prefix, next_hop=next_hop, 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('/') _nlri = IPAddrPrefix(int(masklen), ip) src_ver_num = 1 @@ -497,7 +498,8 @@ class TableCoreManager(object): pathattrs[BGP_ATTR_TYPE_AS_PATH] = aspath 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 self.learn_path(new_path) diff --git a/ryu/services/protocols/bgp/info_base/ipv4.py b/ryu/services/protocols/bgp/info_base/ipv4.py index 2e4db3fb..de0d4c77 100644 --- a/ryu/services/protocols/bgp/info_base/ipv4.py +++ b/ryu/services/protocols/bgp/info_base/ipv4.py @@ -38,9 +38,11 @@ class IPv4Dest(Destination, NonVrfPathProcessingMixin): def _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): NonVrfPathProcessingMixin._new_best_path(self, best_path) + self._core_service._signal_bus.best_path_changed(best_path) class Ipv4Table(Table): diff --git a/ryu/services/protocols/bgp/peer.py b/ryu/services/protocols/bgp/peer.py index 95c223c5..c56b2941 100644 --- a/ryu/services/protocols/bgp/peer.py +++ b/ryu/services/protocols/bgp/peer.py @@ -773,12 +773,12 @@ class Peer(Source, Sink, NeighborConfListener, Activity): if unkown_opttrans_attrs: new_pathattr.extend(unkown_opttrans_attrs.values()) - if isinstance(path, Ipv4Path): - update = BGPUpdate(path_attributes=new_pathattr, - nlri=nlri_list) - else: - update = BGPUpdate(path_attributes=new_pathattr) - return update + if isinstance(path, Ipv4Path): + update = BGPUpdate(path_attributes=new_pathattr, + nlri=nlri_list) + else: + update = BGPUpdate(path_attributes=new_pathattr) + return update def _connect_loop(self, client_factory): """In the current greeenlet we try to establish connection with peer. diff --git a/ryu/services/protocols/bgp/signals/emit.py b/ryu/services/protocols/bgp/signals/emit.py index 7f41c93f..18a12871 100644 --- a/ryu/services/protocols/bgp/signals/emit.py +++ b/ryu/services/protocols/bgp/signals/emit.py @@ -11,6 +11,7 @@ class BgpSignalBus(SignalBus): BGP_VRF_STATS_CONFIG_CHANGED = ( 'core', 'vrf', 'config', 'stats', 'changed' ) + BGP_BEST_PATH_CHANGED = ('core', 'best', 'changed') def bgp_error(self, peer, code, subcode, reason): return self.emit_signal( @@ -53,3 +54,8 @@ class BgpSignalBus(SignalBus): self.BGP_VRF_STATS_CONFIG_CHANGED, vrf_conf ) + + def best_path_changed(self, best_path): + return self.emit_signal( + self.BGP_BEST_PATH_CHANGED, + best_path) |