# 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. import ctypes from ctypes import util as ctypes_util from multiprocessing import Process import socket import eventlet import netaddr from os_ken.lib import hub from os_ken.lib import rpc from os_ken.services.protocols.bgp.api.base import PREFIX from os_ken.services.protocols.bgp import bgpspeaker from os_ken.services.protocols.bgp import net_ctrl from os_ken.services.protocols.bgp.rtconf import neighbors from os_ken.services.protocols.bgp.rtconf.neighbors import PASSWORD from oslo_log import log as logging import tenacity from neutron.privileged.agent.linux import ip_lib from neutron_dynamic_routing._i18n import _LI from neutron_dynamic_routing import privileged from neutron_dynamic_routing.privileged import utils as bgp_utils from neutron_dynamic_routing.services.bgp.agent.driver import utils eventlet.monkey_patch() libc = ctypes.PyDLL(ctypes_util.find_library('c'), use_errno=True) _setns = libc.setns CLONE_NEWNET = 0x40000000 LOG = logging.getLogger(__name__) PROCESS_CACHE = bgp_utils.BgpSpeakerProcessCache() VERSION_IPV6 = 6 RPC_PORT = 50002 RPC_HOST = '127.0.0.1' def setns(fd, nstype): if hasattr(fd, 'fileno'): fd = fd.fileno() _setns(fd, nstype) def get_netns_path(nsname): return '/var/run/netns/%s' % nsname @privileged.bgp_speaker_cmd.entrypoint def add_bgp_speaker(bgp_speaker_id, local_as, bgp_router_id, ns, ip_version): with open(get_netns_path(ns)) as fd: setns(fd, CLONE_NEWNET) bgp_process = BgpProcess(ns, local_as, bgp_router_id, ip_version) bgp_process.start() PROCESS_CACHE.put_bgp_speaker_process(bgp_speaker_id, bgp_process) @privileged.bgp_speaker_cmd.entrypoint def del_bgp_speaker(bgp_speaker_id, ns): with open(get_netns_path(ns)) as fd: setns(fd, CLONE_NEWNET) endpoint = socket.create_connection((RPC_HOST, RPC_PORT)) client = rpc.Client(endpoint) client.call('core.stop', []) process = PROCESS_CACHE.remove_bgp_speaker_process(bgp_speaker_id) if process: process.terminate() @privileged.bgp_speaker_cmd.entrypoint @tenacity.retry(retry=tenacity.retry_if_exception_type( (EOFError, ConnectionRefusedError)), wait=tenacity.wait_random(min=1, max=2)) def add_bgp_neighbor(bgp_speaker_id, peer_ip, remote_as, ns, password=None, auth_type='none'): with open(get_netns_path(ns)) as fd: setns(fd, CLONE_NEWNET) bgp_neighbor = { neighbors.IP_ADDRESS: peer_ip, neighbors.REMOTE_AS: remote_as, PASSWORD: password, } endpoint = socket.create_connection((RPC_HOST, RPC_PORT)) client = rpc.Client(endpoint) client.call('neighbor.create', [bgp_neighbor]) @privileged.bgp_speaker_cmd.entrypoint def del_bgp_neighbor(bgp_speaker_id, peer_ip, ns): LOG.info(_LI('BGPAAS: Router namespace= %(ns)s.'), {'ns': ns}) with open(get_netns_path(ns)) as fd: setns(fd, CLONE_NEWNET) bgp_neighbor = { neighbors.IP_ADDRESS: peer_ip, } endpoint = socket.create_connection((RPC_HOST, RPC_PORT)) client = rpc.Client(endpoint) client.call('neighbor.delete', [bgp_neighbor]) @privileged.bgp_speaker_cmd.entrypoint def advertise_routes(routes): endpoint = socket.create_connection((RPC_HOST, RPC_PORT)) client = rpc.Client(endpoint) for route in routes: networks = { PREFIX: route, } client.call('network.add', [networks]) @privileged.bgp_speaker_cmd.entrypoint def withdraw_routes(routes): endpoint = socket.create_connection((RPC_HOST, RPC_PORT)) client = rpc.Client(endpoint) for route in routes: networks = { PREFIX: route, } client.call('network.del', [networks]) class BgpProcess(Process): def __init__(self, namespace, local_as, bgp_router_id, ip_version): Process.__init__(self) self._namespace = namespace self._local_as = local_as self._bgp_router_id = bgp_router_id self._ip_version = ip_version def run(self): utils.validate_as_num('local_as', self._local_as) server_host = ('0.0.0.0',) if self._ip_version == VERSION_IPV6: server_host = ('::',) bgpspeaker.BGPSpeaker( as_number=self._local_as, router_id=self._bgp_router_id, bgp_server_hosts=server_host, best_path_change_handler=self.best_path_change_event, peer_down_handler=self.bgp_peer_down_event, peer_up_handler=self.bgp_peer_up_event) hub.spawn(net_ctrl.NET_CONTROLLER.start, **{net_ctrl.NC_RPC_BIND_IP: '0.0.0.0', net_ctrl.NC_RPC_BIND_PORT: RPC_PORT}).wait() def best_path_change_event(self, event): LOG.info(_LI("Best path change observed. cidr=%(prefix)s, " "nexthop=%(nexthop)s, remote_as=%(remote_as)d, " "is_withdraw=%(is_withdraw)s, " "namespace=%(namespace)s"), {'prefix': event.prefix, 'nexthop': event.nexthop, 'remote_as': event.remote_as, 'is_withdraw': event.is_withdraw, 'namespace': self._namespace}) ip_version = netaddr.IPNetwork(event.prefix or event.nexthop).version if event.is_withdraw: ip_lib.delete_ip_route(self._namespace, event.prefix, ip_version, via=event.nexthop) else: ip_lib.add_ip_route(self._namespace, event.prefix, ip_version, via=event.nexthop) # Function for logging BGP peer. def bgp_peer_down_event(self, remote_ip, remote_as): LOG.info(_LI('BGP Peer %(peer_ip)s for remote_as=%(peer_as)d went ' 'DOWN. namespace=%(namespace)s'), {'peer_ip': remote_ip, 'peer_as': remote_as, 'namespace': self._namespace}) def bgp_peer_up_event(self, remote_ip, remote_as): LOG.info(_LI('BGP Peer %(peer_ip)s for remote_as=%(peer_as)d is UP.' 'namespace=%(namespace)s'), {'peer_ip': remote_ip, 'peer_as': remote_as, 'namespace': self._namespace})