mirror of https://github.com/EdgeVPNio/evio.git
254 lines
11 KiB
Python
254 lines
11 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.
|
|
|
|
import socket
|
|
import select
|
|
try:
|
|
import simplejson as json
|
|
except ImportError:
|
|
import json
|
|
from threading import Thread
|
|
import traceback
|
|
from distutils import spawn
|
|
import framework.Modlib as modlib
|
|
from framework.ControllerModule import ControllerModule
|
|
import framework.Version as ver
|
|
|
|
class TincanInterface(ControllerModule):
|
|
def __init__(self, cfx_handle, module_config, module_name):
|
|
super(TincanInterface, self).__init__(cfx_handle, module_config, module_name)
|
|
self._tincan_listener_thread = None # UDP listener thread object
|
|
self._tci_publisher = None
|
|
|
|
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
self._sock_svr = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
# Controller UDP listening socket
|
|
self._sock_svr.bind((self._cm_config["RcvServiceAddress"],
|
|
self._cm_config["CtrlRecvPort"]))
|
|
# Controller UDP sending socket
|
|
self._dest = (self._cm_config["SndServiceAddress"], self._cm_config["CtrlSendPort"])
|
|
self._sock.bind(("", 0))
|
|
self._sock_list = [self._sock_svr]
|
|
self.iptool = spawn.find_executable("ip")
|
|
|
|
def initialize(self):
|
|
self._tincan_listener_thread = Thread(target=self.__tincan_listener)
|
|
self._tincan_listener_thread.setDaemon(True)
|
|
self._tincan_listener_thread.start()
|
|
self.create_control_link()
|
|
self._tci_publisher = self._cfx_handle.publish_subscription("TCI_TINCAN_MSG_NOTIFY")
|
|
self.register_cbt("Logger", "LOG_QUERY_CONFIG")
|
|
self.log("LOG_INFO", "Module loaded")
|
|
|
|
def __tincan_listener(self):
|
|
try:
|
|
while True:
|
|
socks, _, _ = select.select(self._sock_list, [], [],
|
|
self._cm_config["SocketReadWaitTime"])
|
|
# Iterate across all socket list to obtain Tincan messages
|
|
for sock in socks:
|
|
if sock == self._sock_svr:
|
|
data = sock.recvfrom(self._cm_config["MaxReadSize"])
|
|
ctl = json.loads(data[0].decode("utf-8"))
|
|
if ctl["EVIO"]["ProtocolVersion"] != ver.EVIO_VER_CTL:
|
|
raise ValueError("Invalid control version detected")
|
|
# Get the original CBT if this is the response
|
|
if ctl["EVIO"]["ControlType"] == "TincanResponse":
|
|
cbt = self._cfx_handle._pending_cbts[ctl["EVIO"]["TransactionId"]]
|
|
cbt.set_response(ctl["EVIO"]["Response"]["Message"],
|
|
ctl["EVIO"]["Response"]["Success"])
|
|
self.complete_cbt(cbt)
|
|
else:
|
|
self._tci_publisher.post_update(ctl["EVIO"]["Request"])
|
|
except Exception as err:
|
|
log_cbt = self.register_cbt(
|
|
"Logger", "LOG_WARNING", "Tincan Listener exception:{0}\n"
|
|
"{1}".format(err, traceback.format_exc()))
|
|
self.submit_cbt(log_cbt)
|
|
|
|
def create_control_link(self,):
|
|
self.register_cbt("Logger", "LOG_INFO", "Creating Tincan control link")
|
|
cbt = self.create_cbt(self._module_name, self._module_name, "TCI_CREATE_CTRL_LINK")
|
|
ctl = modlib.CTL_CREATE_CTRL_LINK
|
|
ctl["EVIO"]["TransactionId"] = cbt.tag
|
|
if self._cm_config["CtrlRecvPort"] is not None:
|
|
ctl["EVIO"]["Request"]["Port"] = self._cm_config["CtrlRecvPort"]
|
|
|
|
ctl["EVIO"]["Request"]["AddressFamily"] = "af_inet"
|
|
ctl["EVIO"]["Request"]["IP"] = self._cm_config["RcvServiceAddress"]
|
|
|
|
self._cfx_handle._pending_cbts[cbt.tag] = cbt
|
|
self.send_control(json.dumps(ctl))
|
|
|
|
def resp_handler_create_control_link(self, cbt):
|
|
if cbt.response.status == "False":
|
|
msg = "Failed to create Tincan response link: CBT={0}".format(cbt)
|
|
raise RuntimeError(msg)
|
|
|
|
def configure_tincan_logging(self, log_cfg, use_defaults=False):
|
|
cbt = self.create_cbt(self._module_name, self._module_name, "TCI_CONFIGURE_LOGGING")
|
|
ctl = modlib.CTL_CONFIGURE_LOGGING
|
|
ctl["EVIO"]["TransactionId"] = cbt.tag
|
|
if not use_defaults:
|
|
ctl["EVIO"]["Request"]["Level"] = log_cfg["LogLevel"]
|
|
ctl["EVIO"]["Request"]["Device"] = log_cfg["Device"]
|
|
ctl["EVIO"]["Request"]["Directory"] = log_cfg["Directory"]
|
|
ctl["EVIO"]["Request"]["Filename"] = log_cfg["TincanLogFileName"]
|
|
ctl["EVIO"]["Request"]["MaxArchives"] = log_cfg["MaxArchives"]
|
|
ctl["EVIO"]["Request"]["MaxFileSize"] = log_cfg["MaxFileSize"]
|
|
ctl["EVIO"]["Request"]["ConsoleLevel"] = log_cfg["ConsoleLevel"]
|
|
self._cfx_handle._pending_cbts[cbt.tag] = cbt
|
|
self.send_control(json.dumps(ctl))
|
|
|
|
def resp_handler_configure_tincan_logging(self, cbt):
|
|
if cbt.response.status == "False":
|
|
msg = "Failed to configure Tincan logging: CBT={0}".format(cbt)
|
|
self.register_cbt("Logger", "LOG_WARNING", msg)
|
|
|
|
def req_handler_create_link(self, cbt):
|
|
msg = cbt.request.params
|
|
ctl = modlib.CTL_CREATE_LINK
|
|
ctl["EVIO"]["TransactionId"] = cbt.tag
|
|
req = ctl["EVIO"]["Request"]
|
|
req["OverlayId"] = msg["OverlayId"]
|
|
req["TunnelId"] = msg["TunnelId"]
|
|
req["NodeId"] = msg.get("NodeId")
|
|
req["LinkId"] = msg["LinkId"]
|
|
req["PeerInfo"]["UID"] = msg["NodeData"].get("UID")
|
|
req["PeerInfo"]["MAC"] = msg["NodeData"].get("MAC")
|
|
req["PeerInfo"]["CAS"] = msg["NodeData"].get("CAS")
|
|
req["PeerInfo"]["FPR"] = msg["NodeData"].get("FPR")
|
|
# Optional overlay data to create overlay on demand
|
|
req["StunServers"] = msg.get("StunServers")
|
|
req["TurnServers"] = msg.get("TurnServers")
|
|
req["Type"] = msg["Type"]
|
|
req["TapName"] = msg.get("TapName")
|
|
req["IgnoredNetInterfaces"] = msg.get("IgnoredNetInterfaces")
|
|
self.send_control(json.dumps(ctl))
|
|
|
|
def req_handler_create_tunnel(self, cbt):
|
|
msg = cbt.request.params
|
|
ctl = modlib.CTL_CREATE_TUNNEL
|
|
ctl["EVIO"]["TransactionId"] = cbt.tag
|
|
req = ctl["EVIO"]["Request"]
|
|
req["StunServers"] = msg["StunServers"]
|
|
req["TurnServers"] = msg.get("TurnServers")
|
|
req["Type"] = msg["Type"]
|
|
req["TapName"] = msg["TapName"]
|
|
req["OverlayId"] = msg["OverlayId"]
|
|
req["TunnelId"] = msg["TunnelId"]
|
|
req["NodeId"] = msg.get("NodeId")
|
|
req["IgnoredNetInterfaces"] = msg.get("IgnoredNetInterfaces")
|
|
self.send_control(json.dumps(ctl))
|
|
|
|
def req_handler_query_candidate_address_set(self, cbt):
|
|
msg = cbt.request.params
|
|
ctl = modlib.CTL_QUERY_CAS
|
|
ctl["EVIO"]["TransactionId"] = cbt.tag
|
|
req = ctl["EVIO"]["Request"]
|
|
req["OverlayId"] = msg["OverlayId"]
|
|
req["LinkId"] = msg["LinkId"]
|
|
self.send_control(json.dumps(ctl))
|
|
|
|
def req_handler_query_link_stats(self, cbt):
|
|
msg = cbt.request.params
|
|
ctl = modlib.CTL_QUERY_LINK_STATS
|
|
ctl["EVIO"]["TransactionId"] = cbt.tag
|
|
req = ctl["EVIO"]["Request"]
|
|
req["TunnelIds"] = msg
|
|
self.send_control(json.dumps(ctl))
|
|
|
|
def req_handler_query_tunnel_info(self, cbt):
|
|
msg = cbt.request.params
|
|
ctl = modlib.CTL_QUERY_TUNNEL_INFO
|
|
ctl["EVIO"]["TransactionId"] = cbt.tag
|
|
req = ctl["EVIO"]["Request"]
|
|
req["OverlayId"] = msg["OverlayId"]
|
|
self.send_control(json.dumps(ctl))
|
|
|
|
def req_handler_remove_tunnel(self, cbt):
|
|
msg = cbt.request.params
|
|
ctl = modlib.CTL_REMOVE_TUNNEL
|
|
ctl["EVIO"]["TransactionId"] = cbt.tag
|
|
req = ctl["EVIO"]["Request"]
|
|
req["OverlayId"] = msg["OverlayId"]
|
|
req["TunnelId"] = msg["TunnelId"]
|
|
self.send_control(json.dumps(ctl))
|
|
if "TapName" in msg and msg["TapName"]:
|
|
modlib.runshell([self.iptool, "link", "del", "dev", msg["TapName"]])
|
|
|
|
def req_handler_remove_link(self, cbt):
|
|
msg = cbt.request.params
|
|
ctl = modlib.CTL_REMOVE_LINK
|
|
ctl["EVIO"]["TransactionId"] = cbt.tag
|
|
req = ctl["EVIO"]["Request"]
|
|
req["OverlayId"] = msg["OverlayId"]
|
|
req["TunnelId"] = msg["TunnelId"]
|
|
req["LinkId"] = msg["LinkId"]
|
|
self.send_control(json.dumps(ctl))
|
|
|
|
def process_cbt(self, cbt):
|
|
if cbt.op_type == "Request":
|
|
if cbt.request.action == "TCI_CREATE_LINK":
|
|
self.req_handler_create_link(cbt)
|
|
|
|
elif cbt.request.action == "TCI_REMOVE_LINK":
|
|
self.req_handler_remove_link(cbt)
|
|
|
|
elif cbt.request.action == "TCI_CREATE_TUNNEL":
|
|
self.req_handler_create_tunnel(cbt)
|
|
|
|
elif cbt.request.action == "TCI_QUERY_CAS":
|
|
self.req_handler_query_candidate_address_set(cbt)
|
|
|
|
elif cbt.request.action == "TCI_QUERY_LINK_STATS":
|
|
self.req_handler_query_link_stats(cbt)
|
|
|
|
elif cbt.request.action == "TCI_QUERY_TUNNEL_INFO":
|
|
self.req_handler_query_tunnel_info(cbt)
|
|
|
|
elif cbt.request.action == "TCI_REMOVE_TUNNEL":
|
|
self.req_handler_remove_tunnel(cbt)
|
|
|
|
else:
|
|
self.req_handler_default(cbt)
|
|
elif cbt.op_type == "Response":
|
|
if cbt.request.action == "LOG_QUERY_CONFIG":
|
|
self.configure_tincan_logging(cbt.response.data,
|
|
not cbt.response.status)
|
|
|
|
elif cbt.request.action == "TCI_CREATE_CTRL_LINK":
|
|
self.resp_handler_create_control_link(cbt)
|
|
|
|
elif cbt.request.action == "TCI_CONFIGURE_LOGGING":
|
|
self.resp_handler_configure_tincan_logging(cbt)
|
|
|
|
self.free_cbt(cbt)
|
|
|
|
def send_control(self, msg):
|
|
return self._sock.sendto(bytes(msg.encode("utf-8")), self._dest)
|
|
|
|
def timer_method(self):
|
|
pass
|
|
|
|
def terminate(self):
|
|
pass
|