185 lines
6.8 KiB
Python
185 lines
6.8 KiB
Python
# 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})
|