--- /dev/null
+# -*- coding: utf-8 -*-
+
+##
+# Copyright 2015 Telefonica Investigacion y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+'''
+Implement like a proxy for TCP/IP in a separated thread.
+It creates two sockets to bypass the TCP/IP packets among the fix console
+server specified at class construction (console_host, console_port)
+and a client that connect against the (host, port) specified also at construction
+
+ --------------------- -------------------------------
+ | OPENMANO | | VIM |
+client 1 ----> | ConsoleProxyThread | ------> | Console server |
+client 2 ----> | (host, port) | ------> |(console_host, console_server)|
+ ... -------------------- ------------------------------
+'''
+__author__="Alfonso Tierno"
+__date__ ="$19-nov-2015 09:07:15$"
+
+import socket
+import select
+import threading
+import logging
+
+
+class ConsoleProxyException(Exception):
+ '''raise when an exception has found'''
+class ConsoleProxyExceptionPortUsed(ConsoleProxyException):
+ '''raise when the port is used'''
+
+class ConsoleProxyThread(threading.Thread):
+ buffer_size = 4096
+ check_finish = 1 #frequency to check if requested to end in seconds
+
+ def __init__(self, host, port, console_host, console_port, log_level=None):
+ try:
+ threading.Thread.__init__(self)
+ self.console_host = console_host
+ self.console_port = console_port
+ self.host = host
+ self.port = port
+ self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self.server.bind((host, port))
+ self.server.listen(200)
+ #TODO timeout in a lock section can be used to autoterminate the thread
+ #when inactivity and timeout<time : set timeout=0 and terminate
+ #from outside, close class when timeout==0; set timeout=time+120 when adding a new console on this thread
+ #set self.timeout = time.time() + 120 at init
+ self.name = "ConsoleProxy " + console_host + ":" + str(console_port)
+ self.input_list = [self.server]
+ self.channel = {}
+ self.terminate = False #put at True from outside to force termination
+ self.logger = logging.getLogger('openmano.console')
+ if log_level:
+ self.logger.setLevel( getattr(logging, log_level) )
+
+ except (socket.error, socket.herror, socket.gaierror, socket.timeout) as e:
+ if e is socket.error and e.errno==98:
+ raise ConsoleProxyExceptionPortUsed("socket.error " + str(e))
+ raise ConsoleProxyException(type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0])) )
+
+ def run(self):
+ while True:
+ try:
+ inputready, _, _ = select.select(self.input_list, [], [], self.check_finish)
+ except select.error as e:
+ self.logger.error("Exception on select %s: %s", type(e).__name__, str(e) )
+ self.on_terminate()
+
+ if self.terminate:
+ self.on_terminate()
+ self.logger.debug("Terminate because commanded")
+ break
+
+ for sock in inputready:
+ if sock == self.server:
+ self.on_accept()
+ else:
+ self.on_recv(sock)
+
+ def on_terminate(self):
+ while self.input_list:
+ if self.input_list[0] is self.server:
+ self.server.close()
+ del self.input_list[0]
+ else:
+ self.on_close(self.input_list[0], "Terminating thread")
+
+ def on_accept(self):
+ #accept
+ try:
+ clientsock, clientaddr = self.server.accept()
+ except (socket.error, socket.herror, socket.gaierror, socket.timeout) as e:
+ self.logger.error("Exception on_accept %s: %s", type(e).__name__, str(e) )
+ return False
+ #print self.name, ": Accept new client ", clientaddr
+
+ #connect
+ try:
+ forward = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ forward.connect((self.console_host, self.console_port))
+ name = "{}:{} => ({}:{} => {}:{}) => {}:{}".format(
+ *clientsock.getpeername(), *clientsock.getsockname(), *forward.getsockname(), *forward.getpeername() )
+ self.logger.warning("new connection " + name)
+
+ self.input_list.append(clientsock)
+ self.input_list.append(forward)
+ info = { "name": name,
+ "clientsock" : clientsock,
+ "serversock" : forward
+ }
+ self.channel[clientsock] = info
+ self.channel[forward] = info
+ return True
+ except (socket.error, socket.herror, socket.gaierror, socket.timeout) as e:
+ self.logger.error("Exception on_connect to server %s:%d; %s: %s Close client side %s",
+ self.console_host, self.console_port, type(e).__name__, str(e), str(clientaddr) )
+ clientsock.close()
+ return False
+
+ def on_close(self, sock, cause):
+ if sock not in self.channel:
+ return #can happen if there is data ready to received at both sides and the channel has been deleted. QUITE IMPROBABLE but just in case
+ info = self.channel[sock]
+ # debug info
+ sockname = "client" if sock is info["clientsock"] else "server"
+ self.logger.warning("del connection %s %s at %s side", info["name"], str(cause), str(sockname))
+ # close sockets
+ try:
+ # close the connection with client
+ info["clientsock"].close() # equivalent to do self.s.close()
+ except (socket.error, socket.herror, socket.gaierror, socket.timeout) as e:
+ self.logger.error("Exception on_close client socket %s: %s", type(e).__name__, str(e))
+ try:
+ # close the connection with remote server
+ info["serversock"].close()
+ except (socket.error, socket.herror, socket.gaierror, socket.timeout) as e:
+ self.logger.error("Exception on_close server socket %s: %s", type(e).__name__, str(e) )
+
+ # remove objects from input_list
+ self.input_list.remove(info["clientsock"])
+ self.input_list.remove(info["serversock"])
+ # delete both objects from channel dict
+ del self.channel[info["clientsock"]]
+ del self.channel[info["serversock"]]
+
+ def on_recv(self, sock):
+ if sock not in self.channel:
+ return # can happen if there is data ready to received at both sides and the channel has been deleted. QUITE IMPROBABLE but just in case
+ info = self.channel[sock]
+ peersock = info["serversock"] if sock is info["clientsock"] else info["clientsock"]
+ try:
+ data = sock.recv(self.buffer_size)
+ if len(data) == 0:
+ self.on_close(sock, "peer closed")
+ else:
+ # print self.data
+ sock = peersock
+ peersock.send(data)
+ except (socket.error, socket.herror, socket.gaierror, socket.timeout) as e:
+ # print(self.name, ": Exception {}: {}".format(type(e).__name__, e))
+ self.on_close(sock, "Exception {}: {}".format(type(e).__name__, e))
+
+
+
+ #def start_timeout(self):
+ # self.timeout = time.time() + 120
+