# ipop-project # Copyright 2016, 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 controller.framework.ipoplib as ipoplib from controller.framework.ControllerModule import ControllerModule 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["IPOP"]["ProtocolVersion"] != 5: raise ValueError("Invalid control version detected") # Get the original CBT if this is the response if ctl["IPOP"]["ControlType"] == "TincanResponse": cbt = self._cfx_handle._pending_cbts[ctl["IPOP"]["TransactionId"]] cbt.set_response(ctl["IPOP"]["Response"]["Message"], ctl["IPOP"]["Response"]["Success"]) self.complete_cbt(cbt) else: self._tci_publisher.post_update(ctl["IPOP"]["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 = ipoplib.CTL_CREATE_CTRL_LINK ctl["IPOP"]["TransactionId"] = cbt.tag if self._cm_config["CtrlRecvPort"] is not None: ctl["IPOP"]["Request"]["Port"] = self._cm_config["CtrlRecvPort"] ctl["IPOP"]["Request"]["AddressFamily"] = "af_inet" ctl["IPOP"]["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 = ipoplib.CTL_CONFIGURE_LOGGING ctl["IPOP"]["TransactionId"] = cbt.tag if not use_defaults: ctl["IPOP"]["Request"]["Level"] = log_cfg["LogLevel"] ctl["IPOP"]["Request"]["Device"] = log_cfg["Device"] ctl["IPOP"]["Request"]["Directory"] = log_cfg["Directory"] ctl["IPOP"]["Request"]["Filename"] = log_cfg["TincanLogFileName"] ctl["IPOP"]["Request"]["MaxArchives"] = log_cfg["MaxArchives"] ctl["IPOP"]["Request"]["MaxFileSize"] = log_cfg["MaxFileSize"] ctl["IPOP"]["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 = ipoplib.CTL_CREATE_LINK ctl["IPOP"]["TransactionId"] = cbt.tag req = ctl["IPOP"]["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 = ipoplib.CTL_CREATE_TUNNEL ctl["IPOP"]["TransactionId"] = cbt.tag req = ctl["IPOP"]["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 = ipoplib.CTL_QUERY_CAS ctl["IPOP"]["TransactionId"] = cbt.tag req = ctl["IPOP"]["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 = ipoplib.CTL_QUERY_LINK_STATS ctl["IPOP"]["TransactionId"] = cbt.tag req = ctl["IPOP"]["Request"] req["TunnelIds"] = msg self.send_control(json.dumps(ctl)) def req_handler_query_tunnel_info(self, cbt): msg = cbt.request.params ctl = ipoplib.CTL_QUERY_TUNNEL_INFO ctl["IPOP"]["TransactionId"] = cbt.tag req = ctl["IPOP"]["Request"] req["OverlayId"] = msg["OverlayId"] self.send_control(json.dumps(ctl)) def req_handler_remove_tunnel(self, cbt): msg = cbt.request.params ctl = ipoplib.CTL_REMOVE_TUNNEL ctl["IPOP"]["TransactionId"] = cbt.tag req = ctl["IPOP"]["Request"] req["OverlayId"] = msg["OverlayId"] req["TunnelId"] = msg["TunnelId"] self.send_control(json.dumps(ctl)) if "TapName" in msg and msg["TapName"]: ipoplib.runshell([self.iptool, "link", "del", "dev", msg["TapName"]]) def req_handler_remove_link(self, cbt): msg = cbt.request.params ctl = ipoplib.CTL_REMOVE_LINK ctl["IPOP"]["TransactionId"] = cbt.tag req = ctl["IPOP"]["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