mirror of https://github.com/EdgeVPNio/evio.git
496 lines
21 KiB
Python
496 lines
21 KiB
Python
# EdgeVPNio
|
|
# Copyright 2020, University of Florida
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
# THE SOFTWARE.
|
|
|
|
|
|
try:
|
|
import simplejson as json
|
|
except ImportError:
|
|
import json
|
|
import threading
|
|
import os
|
|
import socketserver
|
|
from abc import ABCMeta, abstractmethod
|
|
from distutils import spawn
|
|
from framework.ControllerModule import ControllerModule
|
|
import framework.Modlib as Modlib
|
|
|
|
|
|
class BridgeABC():
|
|
__metaclass__ = ABCMeta
|
|
|
|
bridge_type = NotImplemented
|
|
iptool = spawn.find_executable("ip")
|
|
|
|
def __init__(self, name, ip_addr, prefix_len, mtu, cm):
|
|
self.name = name
|
|
self.ip_addr = ip_addr
|
|
self.prefix_len = prefix_len
|
|
self.mtu = mtu
|
|
self.ports = set()
|
|
self.cm = cm
|
|
|
|
@abstractmethod
|
|
def add_port(self, port_name):
|
|
pass
|
|
|
|
@abstractmethod
|
|
def del_port(self, port_name):
|
|
pass
|
|
|
|
@abstractmethod
|
|
def del_br(self):
|
|
pass
|
|
|
|
@property
|
|
@abstractmethod
|
|
def brctl(self,):
|
|
pass
|
|
|
|
def __repr__(self):
|
|
""" Return a representaion of a bridge object. """
|
|
return "%s %s" % (self.bridge_type, self.name)
|
|
|
|
def __str__(self):
|
|
""" Return a string of the bridge name. """
|
|
return self.__repr__()
|
|
###################################################################################################
|
|
|
|
class OvsBridge(BridgeABC):
|
|
brctl = spawn.find_executable("ovs-vsctl")
|
|
bridge_type = "OVS"
|
|
|
|
def __init__(self, name, ip_addr, prefix_len, mtu, cm, stp_enable, sdn_ctrl_cfg=None):
|
|
""" Initialize an OpenvSwitch bridge object. """
|
|
super(OvsBridge, self).__init__(name, ip_addr, prefix_len, mtu, cm)
|
|
if OvsBridge.brctl is None or OvsBridge.iptool is None:
|
|
raise RuntimeError("openvswitch-switch was not found" if not OvsBridge.brctl else
|
|
"iproute2 was not found")
|
|
self._patch_port = self.name[:3]+"-pp0"
|
|
Modlib.runshell([OvsBridge.brctl, "--may-exist", "add-br", self.name])
|
|
|
|
|
|
if ip_addr and prefix_len:
|
|
net = "{0}/{1}".format(ip_addr, prefix_len)
|
|
Modlib.runshell([OvsBridge.iptool, "addr", "flush", "dev", self.name])
|
|
Modlib.runshell([OvsBridge.iptool, "addr", "add", net, "dev", self.name])
|
|
else:
|
|
Modlib.runshell(["sysctl", "net.ipv6.conf.{}.disable_ipv6=1".format(self.name)])
|
|
Modlib.runshell([OvsBridge.iptool, "addr", "flush", self.name])
|
|
try:
|
|
Modlib.runshell([OvsBridge.brctl, "set", "int", self.name,
|
|
"mtu_request=" + str(self.mtu)])
|
|
except RuntimeError as e:
|
|
self.cm.register_cbt("Logger", "LOG_WARN", "The following error occurred while setting"
|
|
" MTU for OVS bridge: {0}".format(e))
|
|
|
|
self.stp(stp_enable)
|
|
Modlib.runshell([OvsBridge.iptool, "link", "set", "dev", self.name, "up"])
|
|
|
|
if sdn_ctrl_cfg:
|
|
self.add_sdn_ctrl(sdn_ctrl_cfg)
|
|
|
|
def add_sdn_ctrl(self, sdn_ctrl_cfg):
|
|
if sdn_ctrl_cfg["ConnectionType"] == "tcp":
|
|
ctrl_conn_str = ":".join([sdn_ctrl_cfg["ConnectionType"],
|
|
sdn_ctrl_cfg["HostName"],
|
|
sdn_ctrl_cfg["Port"]])
|
|
|
|
Modlib.runshell([OvsBridge.brctl,
|
|
"set-controller",
|
|
self.name,
|
|
ctrl_conn_str])
|
|
|
|
def del_sdn_ctrl(self):
|
|
Modlib.runshell([OvsBridge.brctl, "del-controller", self.name])
|
|
|
|
def del_br(self):
|
|
self.del_sdn_ctrl()
|
|
|
|
Modlib.runshell([OvsBridge.brctl,
|
|
"--if-exists", "del-br", self.name])
|
|
|
|
def add_port(self, port_name):
|
|
Modlib.runshell([OvsBridge.iptool, "link", "set", "dev", port_name, "mtu",
|
|
str(self.mtu)])
|
|
Modlib.runshell(["sysctl", "net.ipv6.conf.{}.disable_ipv6=1".format(port_name)])
|
|
Modlib.runshell([OvsBridge.iptool, "addr", "flush", port_name])
|
|
Modlib.runshell([OvsBridge.brctl,
|
|
"--may-exist", "add-port", self.name, port_name])
|
|
self.ports.add(port_name)
|
|
|
|
def del_port(self, port_name):
|
|
Modlib.runshell([OvsBridge.brctl,
|
|
"--if-exists", "del-port", self.name, port_name])
|
|
if port_name in self.ports:
|
|
self.ports.remove(port_name)
|
|
|
|
def stp(self, enable):
|
|
Modlib.runshell([OvsBridge.brctl,
|
|
"set", "bridge", self.name, "stp_enable={0}"
|
|
.format("true" if enable else "false")])
|
|
|
|
def add_patch_port(self, peer_patch_port):
|
|
iface_opt = "options:peer={0}".format(peer_patch_port)
|
|
Modlib.runshell([OvsBridge.brctl,
|
|
"--may-exist", "add-port", self.name, self._patch_port,
|
|
"--", "set", "interface", self._patch_port, "type=patch", iface_opt])
|
|
|
|
def get_patch_port_name(self):
|
|
return self._patch_port
|
|
###################################################################################################
|
|
|
|
class LinuxBridge(BridgeABC):
|
|
brctl = spawn.find_executable("brctl")
|
|
bridge_type = "LXBR"
|
|
|
|
def __init__(self, name, ip_addr, prefix_len, mtu, cm, stp_enable):
|
|
""" Initialize a Linux bridge object. """
|
|
super(LinuxBridge, self).__init__(name, ip_addr, prefix_len, mtu, cm)
|
|
if LinuxBridge.brctl is None or LinuxBridge.iptool is None:
|
|
raise RuntimeError("bridge-utils was not found" if not LinuxBridge.brctl else
|
|
"iproute2 was not found")
|
|
p = Modlib.runshell([LinuxBridge.brctl, "show"])
|
|
wlist = map(str.split, p.stdout.decode("utf-8").splitlines()[1:])
|
|
brwlist = filter(lambda x: len(x) != 1, wlist)
|
|
brlist = map(lambda x: x[0], brwlist)
|
|
for br in brlist:
|
|
if br == name:
|
|
return
|
|
|
|
p = Modlib.runshell([LinuxBridge.brctl, "addbr", self.name])
|
|
net = "{0}/{1}".format(ip_addr, prefix_len)
|
|
if ip_addr and prefix_len:
|
|
Modlib.runshell([LinuxBridge.iptool, "addr", "add", net, "dev", name])
|
|
self.stp(stp_enable)
|
|
Modlib.runshell([LinuxBridge.iptool, "link", "set", "dev", name, "up"])
|
|
|
|
def del_br(self):
|
|
# Set the device down and delete the bridge
|
|
Modlib.runshell([LinuxBridge.iptool, "link", "set", "dev", self.name, "down"])
|
|
Modlib.runshell([LinuxBridge.brctl, "delbr", self.name])
|
|
|
|
def add_port(self, port_name):
|
|
Modlib.runshell([LinuxBridge.iptool, "link", "set", port_name, "mtu", str(self.mtu)])
|
|
Modlib.runshell([LinuxBridge.brctl, "addif", self.name, port_name])
|
|
self.ports.add(port_name)
|
|
|
|
def del_port(self, port_name):
|
|
p = Modlib.runshell([LinuxBridge.brctl, "show", self.name])
|
|
wlist = map(str.split, p.stdout.decode("utf-8").splitlines()[1:])
|
|
port_lines = filter(lambda x: len(x) == 4, wlist)
|
|
ports = map(lambda x: x[-1], port_lines)
|
|
for port in ports:
|
|
if port == port_name:
|
|
Modlib.runshell([LinuxBridge.brctl, "delif", self.name, port_name])
|
|
if port_name in self.ports:
|
|
self.ports.remove(port_name)
|
|
|
|
def stp(self, val=True):
|
|
""" Turn STP protocol on/off. """
|
|
if val:
|
|
state = "on"
|
|
else:
|
|
state = "off"
|
|
Modlib.runshell([LinuxBridge.brctl, "stp", self.name, state])
|
|
|
|
def set_bridge_prio(self, prio):
|
|
""" Set bridge priority value. """
|
|
Modlib.runshell([LinuxBridge.brctl,
|
|
"setbridgeprio", self.name, str(prio)])
|
|
|
|
def set_path_cost(self, port, cost):
|
|
""" Set port path cost value for STP protocol. """
|
|
Modlib.runshell([LinuxBridge.brctl,
|
|
"setpathcost", self.name, port, str(cost)])
|
|
|
|
def set_port_prio(self, port, prio):
|
|
""" Set port priority value. """
|
|
Modlib.runshell([LinuxBridge.brctl,
|
|
"setportprio", self.name, port, str(prio)])
|
|
###################################################################################################
|
|
class BoundedFloodProxy(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
|
"""
|
|
Starts the TCP proxy listener
|
|
Starts the Ryu engine and module
|
|
Supports interactions between BF Ryu module and evio controller
|
|
"""
|
|
RyuManager = spawn.find_executable("ryu-manager")
|
|
if RyuManager is None:
|
|
raise RuntimeError("RyuManager was not found, is it installed?")
|
|
def __init__(self, host_port_tuple, streamhandler, netman):
|
|
super().__init__(host_port_tuple, streamhandler)
|
|
self.netman = netman
|
|
self._bf_proc = None
|
|
|
|
def start_bf_client_module(self):
|
|
config = self.netman.config["BoundedFlood"]
|
|
cmd = [
|
|
BoundedFloodProxy.RyuManager,
|
|
"--user-flags", "modules/BFFlags.py",
|
|
"--nouse-stderr",
|
|
"--bf-config-string", json.dumps(config),
|
|
"modules/BoundedFlood.py"]
|
|
self._bf_proc = Modlib.create_process(cmd)
|
|
|
|
def server_close(self):
|
|
if self._bf_proc:
|
|
self._bf_proc.kill()
|
|
self._bf_proc.wait()
|
|
socketserver.TCPServer.server_close(self)
|
|
|
|
class BFRequestHandler(socketserver.BaseRequestHandler):
|
|
def handle(self):
|
|
data = self.request.recv(65536)
|
|
if not data:
|
|
return
|
|
task = json.loads(data.decode("utf-8"))
|
|
task = self.process_task(task)
|
|
self.request.sendall(bytes(json.dumps(task) + "\n", "utf-8"))
|
|
|
|
def process_task(self, task):
|
|
# task structure
|
|
# dict(Request=dict(Action=None, Params=None),
|
|
# Response=dict(Status=False, Data=None))
|
|
if task["Request"]["Action"] == "GetTunnels":
|
|
task = self._handle_get_topo(task)
|
|
elif task["Request"]["Action"] == "GetNodeId":
|
|
task["Response"] = dict(Status=True,
|
|
Data=dict(NodeId=str(self.server.netman.node_id)))
|
|
elif task["Request"]["Action"] == "TunnelRquest":
|
|
task["Response"] = dict(Status=True,
|
|
Data=dict(StatusMsg="Request shall be considered"))
|
|
self.server.netman.log("LOG_INFO", "On-demand tunnel request recvd %s", task["Request"])
|
|
self.server.netman.tunnel_request(task["Request"]["Params"]) # op is ADD/REMOVE
|
|
else:
|
|
self.server.netman.log("LOG_WARNING", "An unrecognized SDNI task was discarded %s",
|
|
task)
|
|
task["Response"] = dict(Status=False, Data=dict(ErrorMsg="Unsupported request"))
|
|
return task
|
|
|
|
def _handle_get_topo(self, task):
|
|
olid = task["Request"]["Params"]["OverlayId"]
|
|
topo = self.server.netman.get_ovl_topo(olid)
|
|
task["Response"] = dict(Status=True, Data=topo)
|
|
return task
|
|
|
|
###################################################################################################
|
|
class VNIC(BridgeABC):
|
|
brctl = None
|
|
bridge_type = "VNIC"
|
|
|
|
def __init__(self, ip_addr, prefix_len, mtu, cm):
|
|
super(VNIC, self).__init__("VirtNic", ip_addr, prefix_len, mtu, cm)
|
|
|
|
def del_br(self):
|
|
pass
|
|
|
|
def add_port(self, port_name):
|
|
self.name = port_name
|
|
net = "{0}/{1}".format(self.ip_addr, self.prefix_len)
|
|
Modlib.runshell([VNIC.iptool, "addr", "add", net, "dev", self.name])
|
|
Modlib.runshell([VNIC.iptool, "link", "set", self.name, "mtu", str(self.mtu)])
|
|
Modlib.runshell([VNIC.iptool, "link", "set", "dev", self.name, "up"])
|
|
|
|
|
|
def del_port(self, port_name):
|
|
pass
|
|
|
|
###################################################################################################
|
|
|
|
def BridgeFactory(overlay_id, dev_type, config, cm, sdn_config=None):
|
|
br = None
|
|
if dev_type == VNIC.bridge_type:
|
|
br = VNIC(ip_addr=config.get("IP4", None),
|
|
prefix_len=config.get("PrefixLen", None),
|
|
mtu=config.get("MTU", 1410),
|
|
cm=cm)
|
|
elif dev_type == LinuxBridge.bridge_type:
|
|
br = LinuxBridge(name=config["NamePrefix"][:8] + overlay_id[:7],
|
|
ip_addr=config.get("IP4", None),
|
|
prefix_len=config.get("PrefixLen", None),
|
|
mtu=config.get("MTU", 1410),
|
|
cm=cm,
|
|
stp_enable=(True if config.get("SwitchProtocol", "STP").casefold() == "stp" else False))
|
|
elif dev_type == OvsBridge.bridge_type:
|
|
br = OvsBridge(name=config["NamePrefix"][:8] + overlay_id[:7],
|
|
ip_addr=config.get("IP4", None),
|
|
prefix_len=config.get("PrefixLen", None),
|
|
mtu=config.get("MTU", 1410),
|
|
cm=cm,
|
|
stp_enable=(config.get("SwitchProtocol", "").casefold() == "stp"),
|
|
sdn_ctrl_cfg=sdn_config)
|
|
return br
|
|
|
|
###################################################################################################
|
|
|
|
class BridgeController(ControllerModule):
|
|
|
|
def __init__(self, cfx_handle, module_config, module_name):
|
|
super(BridgeController, self).__init__(cfx_handle, module_config, module_name)
|
|
self._bfproxy = None
|
|
self._server_thread = None
|
|
self._ovl_net = dict()
|
|
self._appbr = dict()
|
|
self._lock = threading.Lock()
|
|
self._tunnels = dict()
|
|
|
|
def initialize(self):
|
|
ign_br_names = dict()
|
|
for olid in self.overlays:
|
|
self._tunnels[olid] = dict()
|
|
br_cfg = self.overlays[olid]
|
|
ign_br_names[olid] = set()
|
|
self._ovl_net[olid] = BridgeFactory(olid, br_cfg["NetDevice"]["Type"],
|
|
br_cfg["NetDevice"], self,
|
|
br_cfg.get("SDNController", {}))
|
|
if "AppBridge" in br_cfg["NetDevice"]:
|
|
name = self._create_app_bridge(olid, br_cfg["NetDevice"]["AppBridge"])
|
|
ign_br_names[olid].add(name)
|
|
ign_br_names[olid].add(self._ovl_net[olid].name)
|
|
self.log("LOG_DEBUG", "ignored bridges=%s", ign_br_names)
|
|
# start the BF proxy if at least one overlay is configured for it
|
|
if "BoundedFlood" in self.config:
|
|
proxy_listen_address = self.config["BoundedFlood"]["ProxyListenAddress"]
|
|
proxy_listen_port = self.config["BoundedFlood"]["ProxyListenPort"]
|
|
self._bfproxy = BoundedFloodProxy(
|
|
(proxy_listen_address, proxy_listen_port),
|
|
BFRequestHandler, self)
|
|
self._server_thread = threading.Thread(target=self._bfproxy.serve_forever,
|
|
name="BFProxyServer")
|
|
self._server_thread.setDaemon(True)
|
|
self._server_thread.start()
|
|
# start the BF RYU module
|
|
self._bfproxy.start_bf_client_module()
|
|
|
|
self.register_cbt("LinkManager", "LNK_ADD_IGN_INF", ign_br_names)
|
|
#try:
|
|
# # Subscribe for data request notifications from OverlayVisualizer
|
|
# self._cfx_handle.start_subscription("OverlayVisualizer", "VIS_DATA_REQ")
|
|
#except NameError as err:
|
|
# if "OverlayVisualizer" in str(err):
|
|
# self.register_cbt("Logger", "LOG_INFO",
|
|
# "OverlayVisualizer module not loaded."
|
|
# " Visualization data will not be sent.")
|
|
|
|
self._cfx_handle.start_subscription("LinkManager", "LNK_TUNNEL_EVENTS")
|
|
self.register_cbt("Logger", "LOG_INFO", "Module Loaded")
|
|
|
|
def req_handler_manage_bridge(self, cbt):
|
|
try:
|
|
olid = cbt.request.params["OverlayId"]
|
|
br = self._ovl_net[olid]
|
|
tnlid = cbt.request.params["TunnelId"]
|
|
if cbt.request.params["UpdateType"] == "LnkEvConnected":
|
|
port_name = cbt.request.params["TapName"]
|
|
self._tunnels[olid][tnlid] = {
|
|
"PeerId": cbt.request.params["PeerId"],
|
|
"TunnelId": tnlid,
|
|
"ConnectedTimestamp": cbt.request.params["ConnectedTimestamp"],
|
|
"TapName": port_name,
|
|
"MAC": Modlib.delim_mac_str(cbt.request.params["MAC"]),
|
|
"PeerMac": Modlib.delim_mac_str(cbt.request.params["PeerMac"])
|
|
}
|
|
br.add_port(port_name)
|
|
self.log("LOG_INFO", "Port %s added to bridge %s", port_name, str(br))
|
|
elif cbt.request.params["UpdateType"] == "LnkEvRemoved":
|
|
self._tunnels[olid].pop(tnlid, None)
|
|
if br.bridge_type == OvsBridge.bridge_type:
|
|
port_name = cbt.request.params.get("TapName")
|
|
if port_name:
|
|
br.del_port(port_name)
|
|
self.log("LOG_INFO", "Port %s removed from bridge %s", port_name, str(br))
|
|
except RuntimeError as err:
|
|
self.register_cbt("Logger", "LOG_WARNING", str(err))
|
|
cbt.set_response(None, True)
|
|
self.complete_cbt(cbt)
|
|
|
|
def timer_method(self):
|
|
pass
|
|
|
|
def process_cbt(self, cbt):
|
|
if cbt.op_type == "Request":
|
|
if cbt.request.action == "LNK_TUNNEL_EVENTS":
|
|
self.req_handler_manage_bridge(cbt)
|
|
elif cbt.request.action == "VIS_DATA_REQ":
|
|
self.req_handler_vis_data(cbt)
|
|
else:
|
|
self.req_handler_default(cbt)
|
|
elif cbt.op_type == "Response":
|
|
parent_cbt = cbt.parent
|
|
cbt_data = cbt.response.data
|
|
cbt_status = cbt.response.status
|
|
self.free_cbt(cbt)
|
|
if (parent_cbt is not None and parent_cbt.child_count == 1):
|
|
parent_cbt.set_response(cbt_data, cbt_status)
|
|
self.complete_cbt(parent_cbt)
|
|
|
|
def terminate(self):
|
|
try:
|
|
if self._bfproxy:
|
|
self._bfproxy.server_close()
|
|
self._bfproxy.shutdown()
|
|
for olid in self._ovl_net:
|
|
if olid in self._appbr and self.overlays[olid]["NetDevice"]["AppBridge"].get("AutoDelete", False):
|
|
self._appbr[olid].del_br()
|
|
br = self._ovl_net[olid]
|
|
if self.overlays[olid]["NetDevice"].get("AutoDelete", False):
|
|
br.del_br()
|
|
else:
|
|
if br.bridge_type == OvsBridge.bridge_type:
|
|
for port in br.ports:
|
|
br.del_port(port)
|
|
except RuntimeError as err:
|
|
self.register_cbt("Logger", "LOG_WARNING", str(err))
|
|
|
|
def req_handler_vis_data(self, cbt):
|
|
br_data = dict()
|
|
is_data_available = False
|
|
for olid in self.overlays:
|
|
is_data_available = True
|
|
br_data[olid] = {}
|
|
br_data[olid]["Type"] = self.overlays[olid]["NetDevice"]["Type"]
|
|
br_data[olid]["BridgeName"] = self.overlays[olid]["NetDevice"]["NamePrefix"]
|
|
if "IP4" in self.overlays[olid]["NetDevice"]:
|
|
br_data[olid]["IP4"] = self.overlays[olid]["NetDevice"]["IP4"]
|
|
if "PrefixLen" in self.overlays[olid]["NetDevice"]:
|
|
br_data[olid]["PrefixLen"] = self.overlays[olid]["NetDevice"]["PrefixLen"]
|
|
if "MTU" in self.overlays[olid]["NetDevice"]:
|
|
br_data[olid]["MTU"] = self.overlays[olid]["NetDevice"]["MTU"]
|
|
br_data[olid]["AutoDelete"] = self.overlays[olid]["NetDevice"].get("AutoDelete", False)
|
|
cbt.set_response({"BridgeController": br_data}, is_data_available)
|
|
self.complete_cbt(cbt)
|
|
|
|
def _create_app_bridge(self, olid, abr_cfg):
|
|
name = abr_cfg["NamePrefix"] + olid[:7]
|
|
gbr = BridgeFactory(olid, abr_cfg["Type"], abr_cfg, self)
|
|
|
|
gbr.add_patch_port(self._ovl_net[olid].get_patch_port_name())
|
|
self._ovl_net[olid].add_patch_port(gbr.get_patch_port_name())
|
|
self._appbr[olid] = gbr
|
|
return name
|
|
|
|
|
|
def get_ovl_topo(self, overlay_id):
|
|
return self._tunnels.get(overlay_id, None)
|
|
|
|
def tunnel_request(self, req_params):
|
|
self.register_cbt("Topology", "TOP_REQUEST_OND_TUNNEL", req_params)
|