#!/usr/bin/python # *-* coding: utf-8 *-* def testmod(modname): """ Check if module has been effectively imported. @param: modname string Name of module @return: True/False boolean Return True if module has been imported, False otherwise """ if modname not in sys.modules: return False return True import sys # Import system interfcace import os # import time # Import time module for time.time() (wait a time using task scheduler) import socket # Import socket for TCP socket support import select # Import select for non-blocking connection/error handlelling import errno # # Import low-level and high-level threading interface if sys.version_info[0] >= 3: from _thread import * # Use _thread module instead thread module for python 3 and highter from threading import * # Import Thread(), Lock() else: from thread import * ############################################################################# Import Thread(), Lock() if not testmod("thread"): from dummy_threading import * # Use dummy_threading if thread module is missing for python 2.x and lower else: from threading import * # Otherwise use threading TCPyVersMajor = 1 # Server major version TCPyVersMinor = 3 # Server minor version TCPyVersBuild = 0 # Server build version class TCPyServer(Thread): ############################################################################# Private attributes __conf = { "console": [ # Console data None, # Console socket { "ifFamily": socket.AF_INET, # Console interface family "iface": "127.0.0.1", # Console interface name "port": 49153, # Console TCP port "clientMax": 1 # Console client maximum connection }, # Console configuration None, # Console thread object None # Console event object ], "server": [ # Server data None, # Server socket { "ifFamily": socket.AF_INET, # Server interface family "iface": "", # Server interface name "port": 49152, # Server TCP port "clientMax": 16, # Server client maximum connection "verbosity": True # Server output mode (quiet/verbose) }, # Server configuration None, # Server thread object None # Server event object ] } __isRunning = False # Server state __listenerList = [] # Listener socket list __erroredList = [] # Pontently errored socket list __clientList = [] # Client List __lock_isRunning = Lock() # Lock on server state __lock_listenerList = Lock() # Lock on listener socket list __lock_erroredList = Lock() # Lock on pontently errored socket list __lock_clientList = Lock() # Lock on client list ############################################################################# Public attributes # ***** No pubic attribute ############################################################################# Private method def __init__(self, ifFamily = socket.AF_INET, iface = "", port = 49152, clientMax = 16, verbosity = True, confFile = "./TCPyServer.conf"): """ Instantiate the TCPyServer object in a separate thread. @param: ifFamily integer Socket family, use socket.AF_ contant @param: iface string Interface Name, e.g.: IP address or host name @param: port integer TCP service port @param: clientMax integer Client maximum connection @param: verbosity boolean Verbose or quiet mode @param: confFile string Server configuration file @return: Server thread object or None if error occur """ _name = "TCPySerever" # Proccess name Thread.__init__(self, name = _name) # Set TCPyServer run in own thread self.daemon = True # Set this thread is a daemonic-thread if os.path.isfile(confFile): # If configuration exist pass # Load parameter from there else: # Else use call parameters self.__conf["server"][1]["ifFamily"] = ifFamily # self.__conf["server"][1]["iface"] = iface # self.__conf["server"][1]["port"] = port # Private TCP port are in range 49152–65535 self.__conf["server"][1]["clientMax"] = clientMax # self.__conf["server"][1]["verbosity"] = verbosity # try: # Try to self.start() # Start thread except: # Caugth all error if occur return None # And return None object to caller def run(self): self.__debug("[-] TCPyServer daemon thread started") # Print start message on output llst = self.__listenerList llck = self.__lock_listenerList elst = self.__erroredList elck = self.__lock_erroredList clst = self.__clientList clck = self.__lock_clientList for listener in ("console", "server"): # For all listener self.__startListener(listener) # Start listener self.__isRunning = True # Set server is running while self.__isRunning: # While server is running try: (rsock, wsock, esock) = select.select(llst, [], elst) # Check current state of all socket for s in esock: # Foreach socket is errored if s is self.__conf["console"][0]: name = "console" else: name = "server" self.__restartListener(name) # Restart socket pass for s in rsock: # Foreach socket is readable (csock, caddr) = s.accept() # Accept connection on current socket m = "[-] Accept incomming transmission from {}:{}" self.__debug(m.format(caddr[0], str(caddr[1]))) (cargs) = (csock, caddr) # Created argument list for client thread self.__addList(elst, elck, csock) # Add client socket to erroredList if s is self.__conf["server"][0]: # If the current socket is server socket clt = (csock, caddr, None, None) # Created client record in form (client socket, client address, client thread, client event) self.__addList(clst, clck, clt) # Add client record to clienList start_new_thread(self.__serveAppLayer, (cargs)) # Start session in new thread except socket.error as e: m = "[x] {}" if type(e) == str: m = m.format(e) elif type(e) is tuple: # Else if e is tuple (err, msg) = e.args # Get errno and strerror() if (err == errno.EAGAIN # If timeout occur before operation was finished or err == errno.EWOULDBLOCK # or the operation would block on non-blocking socket or err == errno.EINTR): # or an interrupt occur before data are available continue # Non-fatal error, just waiting at end of loop else: # Else a fatal error occur strerror = "Code={}, Msg={}".format(e.args) m = m.format(strerror) # Format error message with error code and error string self.__debug(m) break except RuntimeError as e: self.__debug(e) break except: m = "[x] Unexpected error: {}".format(sys.exc_info()[0]) self.__debug(m) break time.sleep(0.1) # Wait 100ms before continue loop self.__stopListener("console") # Stop console listener self.__stopListener("server") # Stop server listener self.__debug("[-] TCPyServer daemon thread stopped") def __debug(self, msg): """ Print debugging information if verbosity initilisation parameter's is set to True @param: msg string Message to be printed """ if self.__conf["server"][1]["verbosity"]: # If in verbose mode: print msg # print messeage. def __printVersion(self): """ Print version information @param: msg string Message to be printed """ print "TCPyServer v{}.{}.{}".format(str(TCPyVersMajor), str(TCPyVersMinor), str(TCPyVersBuild)) def __P(self, lock): """ Semaphore-like P() function @param: lock Lock() object Lock to be acquire """ haveLock = False while not haveLock: # While the current thread haven't lock: if not (lock).acquire(True): # if not acquire lock: time.sleep(0.005) # wait 5ms for retry else: # else haveLock = True # set lock is acquired def __V(self, lock): """ Semaphore-like V() function @param: lock Lock() object Lock to be release """ try: (lock).release() # Release lock except ThreadError as e: m = "[x] The current lock already release: {}".format(e) raise RuntimeError(m) def __addList(self, lst, lck, data): """ Add data to protected list @param: lst list List to be update @param: lck Lock() object Lock on list @param: data Lock() Object Data to be append """ try: # Try to self.__P(lck) # Acquire lock lst.append(data) # Add data to list self.__V(lck) # Release lock except e: # If an error occur raise RuntimeError(e) # Reraise as RuntimeError def __delList(self, lstr, lck, data): """ Delete data from protected list @param: lst list List to be update @param: lck Lock() object Lock on list @param: data Lock() Object Data to be remove """ try: # Try to self.__P(lck) # Acquire lock lst.remove(data) # Add data to list self.__V(lck) # Release lock except e: # If an error occur raise RuntimeError(e) # Reraise as RuntimeError def __startListener(self, name): """ Start listener @param: name string Listener name """ ifFamily = self.__conf[name][1]["ifFamily"] iface = self.__conf[name][1]["iface"] port = self.__conf[name][1]["port"] clientMax = self.__conf[name][1]["clientMax"] elst = self.__erroredList elck = self.__lock_erroredList llst = self.__listenerList llck = self.__lock_listenerList try: # Try to self.__conf[name][0] = socket.socket(ifFamily, socket.SOCK_STREAM) # Open socket self.__debug("[-] {} socket opened".format(name)) # (self.__conf[name][0]).setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # Define socket options self.__debug("[-] {} socket configured".format(name)) # (self.__conf[name][0]).bind((iface, port)) # Bind interface and port self.__debug("[-] {} bound to {}:{}".format(name, iface, str(port)))# (self.__conf[name][0]).listen(clientMax) # Listen connection self.__debug("[-] {} listen {}:{}".format(name, iface, str(port))) # self.__addList(elst, elck, self.__conf[name][0]) # Add current listener socket to potently errored socket list self.__addList(llst, llck, self.__conf[name][0]) # Add current listener socket to potently readable socket list if name == "server": # If the current listener is the server self.onStart() # Call user fucntion onStart() for start application layer except socket.error as e: # If an error (e) occurs on socket if type(e) is str: # If e is string raise RuntimeError("[x] {}".format(e)) # Raise exception with e as message elif type(e) is tuple: # Else if e is tuple err, msg = e.args # Set err to errno and msg to strerror() if (err == errno.EAGAIN # If timeout occur before operation was finished or err == errno.EWOULDBLOCK # or the operation would block on non-blocking socket or err == errno.EINTR): # or an interrupt occur before data are available # Fermer le socket return False # Non-fatal error else: # Else a fatal error occur m = "[x] Code: {}, Msg: {}".format(str(err), msg) # Set m with error message raise RuntimeError(m) # Reraise as RuntimeError except RuntimeError as e: # If a runtime error occur raise RuntimeError(e) # Reraise except: # If an unexcepted error occur (assumed as fatal) m = "[x] Unexpected error: {}".format(sys.exc_info()[0]) # Set m with error message raise RuntimeError(m) # Reraise as RuntimeError return True def __stopListener(self, name): """ Stop listener @param: name string Listener name """ try: # Try to (self.__conf[name][0]).shutdown(SHUT_RD) # Disallow receive data on listener socket if name == "server": # If listener is the server self.__isRunning = Fasle # Set server is down self.__delList(elst, elck, self.__conf[name][0]) # Delete current listener socket to potently errored socket list self.__delList(llst, llck, self.__conf[name][0]) # Delete current listener socket to potently readable socket list if name == "server": # If listener is the server self.onStop() # Call user fucntion onStop() for shutdown application layer (self.__conf[name][0]).close() # Close listener socket except socket.error as e: # If an error (e) occurs on socket if type(e) is str: # If e is string raise RuntimeError("[x] {}".format(e)) # Raise exception with e as message elif type(e) is tuple: # Else if e is tuple err, msg = e.args # Set err to errno and msg to strerror() if (err == errno.EAGAIN # If timeout occur before operation was finished or err == errno.EWOULDBLOCK # or the operation would block on non-blocking socket or err == errno.EINTR): # or an interrupt occur before data are available # Fermer le socket return False # Non-fatal error else: # Else a fatal error occur m = "[x] Code: {}, Msg: {}".format(str(err), msg) # Set m with error message raise RuntimeError(m) # Reraise as RuntimeError except RuntimeError as e: # If a runtime error occur raise RuntimeError(e) # Reraise except: # If an unexcepted error occur (assumed as fatal) m = "[x] Unexpected error: {}".format(sys.exc_info()[0]) # Set m with error message raise RuntimeError(m) # Reraise as RuntimeError return True def __restartListener(self, name): """ Restart listener @param: name string Listener name """ try: # Try to if name == "server": # If listener is the server self.onRestart(1) # Call user fucntion onRestart() for first pass of application layer restart # Traiter l'erreur dans __stopListener self.__stopListener(name) # Stop server if name == "server": # If listener is the server self.onRestart(2) # Call user fucntion onRestart() for second pass of application layer restart # Traiter l'erreur dans __startListener self.__startListener(name) # Start server if name == "server": # If listener is the server self.onRestart(3) # Call user fucntion onRestart() for thrid pass of application layer restart except e: # If an error occur raise RuntimeError(e) # Reraise as RuntimeError def __serveAppLayer(self, csock, caddr): """ Serve user-define applicayion layer @param: csock socket object Client socket @param: caddr Iface addr object Client address """ ip = caddr[0] port = caddr[1] lstc = self.__clientList lckc = self.__lock_clientList lste = self.__erroredList lcke = self.__lock_erroredList post = "[-] Client thread ({})".format(self.name) popen = "[-] Client connection successfuly opened for {}:{}" pmsg = "[-] Incomming client message from {}:{}: {}" pclose = "[-] Client connection successfuly closed for {}:{}" self.__debug("{} created for {}:{}".format(post, ip, str(port))) # Print the start of client thread on output try: # Try to self.__debug(popen.format(ip, str(port))) # if csock is not self.__conf["console"][0]: # If client socket isn't linked to console self.onOpen(csock, caddr) # Call user define onOpen() function for application layer connection setup inSession = True # Set client start current session while inSession: # While they have one message to be read msg = self.__receiveMessage(csock, caddr) # Read message if csock is not self.__conf["console"][0]: # If client socket isn't linked to console inSession = self.onMessage(csock, caddr, msg) # Call user define onMessage(msg) function for application layer message proccessing else: # Else inSession = self.__consoleMessage(csock, caddr, msg) # Call console proccessing function self.__debug(pmsg.format(ip, str(port), msg)) # Print trace of message if csock is not self.__conf["console"][0]: # If client socket isn't linked to console self.onClose(csock, caddr) # Call user define onClose() function for application layer connection shutdown self.__debug(pclose.format(ip, str(port))) # except Exception as e: # If an exception occur (for support application layer exception) # Fermer le socket m = "[x] Exception occurs in __receiveMessage() from {}:{}: {}" # Construct error message if type(e) is str: # If e is string m = m.format(ip, str(port), e) # Format error message with string elif type(e) is tuple: # Else if e is tuple err, msg = e.args # Get errno and strerror() m = m.format(ip, str(port), "Code={}, Msg={}".format(err, msg)) # Format error message with error code and error string raise RuntimeError(m) # Reraise as RuntimeError except: # If an unexcepted error occur (assumed as fatal) # Fermer le socket m = "[x] Unexpected error: {}".format(sys.exc_info()[0]) # Set m with error message raise RuntimeError(m) # Reraise as RuntimeError finally: csock.shutdown() csock.close() self.__delList(lstc, lckc, (csock, caddr),) # Delete client to clientList self.__delList(lste, lcke, csock) # Delete client socket to erroredList self.__debug("{} terminated".format(post)) # Print the end of client thread on output def __receiveMessage(self, csock, caddr): """ Receive message from client @param: csock socket object Client socket @param: caddr Iface addr object Client address """ chunks = [] # List of received chunk while (True): # While message isn't fully receive (or error occurs) (sr, sw, se) = select.select([csock], [], [csock]) # Check if client socket is readable or errored s = (se, sr) # Set s as tuple in form (errored socket, readable socket) i = 0 # Set i to 0 while (i < 2): # Foreach socket list if (s[i]): # If socket list isn't an empty tuple try: # Try to if not i: # If proccessing on errored socket (s[i][0]).recv(1, socket.MSG_DONTWAIT) # Receive message in non-blocking mode for raise exception else: # Else chunk = (s[i][0]).recv(4096) # Read 4096 characters on socket if not chunk: # If chunk is an empty string (end of message) return b''.join(chunks) # Concatenate chunks and return message chunks.append(chunk) # Append current chunk to chunk list except socket.error as e: # If an error occurs on socket if type(e) is str: # If e is string raise RuntimeError(e) # Reraise as RuntimeError elif type(e) is tuple: # Else if e is tuple err, msg = e.args # Set err to errno and msg to strerror() err = str(err) # Cast err in string if (err == errno.EAGAIN # If timeout occur before data was received or err == errno.EWOULDBLOCK # or the operation would block on non-blocking socket or err == errno.EINTR): # or an interrupt occur before data are available continue # Non-fatal error, just waiting at end of loop else: # Else a fatal error occur m = "[x] Code: {}, Msg: {}".format(err, msg) # Set m with error message raise RuntimeError(m) # Reraise as RuntimeError except: # If an unexcepted error occur m = "[x] Unexpected error: {}".format(sys.exc_info()[0])# Set m with error message raise RuntimeError(m) # Reraise as RuntimeError time.sleep(0.1) # Wait 100ms before continue loop def __sendMessage(self, csock, caddr, msg): """ Send message to client @param: csock socket object Client socket @param: caddr Iface addr object Client address @param: msg String Message to be sent to client """ totalsent = 0 # Total of byte sent on socket msglen = len(msg) # Lenght of message #csock = client[0] # Client socket while totalsent < msglen: # While they have a chunk of message to be write try: # Try to (rsock, wsock, esock) = select.select([csock], [], [csock]) # Check if socket is errored or writable if esock: # If socket is errored esock.recv(1, socket.MSG_DONTWAIT) # Receive message in non-blocking mode for raise exception elif wsock: # Else if socket is writable sent = csock.send(msg[totalsent:]) # Send next chunk if sent == 0: # If no data has been sent raise RuntimeError("[x] socket connection broken") # Raise RuntimeError totalsent = totalsent + sent # Add sent chunk lenght to total of byte sent on socket except socket.error as e: # If an error occurs on socket if type(e) is str: # If e is string raise RuntimeError(e) # Reraise as RuntimeError elif type(e) is tuple: # Else if e is tuple err, msg = e.args # Set err to errno and msg to strerror() err = str(err) # Cast err in string if (err == errno.EAGAIN # If timeout occur before data was received or err == errno.EWOULDBLOCK # or the operation would block on non-blocking socket or err == errno.EINTR): # or an interrupt occur before data are available continue # Non-fatal error, just waiting at end of loop else: # Else a fatal error occur m = "[x] Code: {}, Msg: {}".format(err, msg) # Set m with error message raise RuntimeError(m) # Reraise as RuntimeError except: # If an unexcepted error occur m = "[x] Unexpected error: {}".format(sys.exc_info()[0]) # Set m with error message raise RuntimeError(m) # Reraise as RuntimeError return totalsent def __broadcastMessage(self): pass ############################################################################# Public method (exec only, if overwrite private function cannot be call) def sendMessage(self, csock, caddr, msg): """ Send message to client @param: csock socket object Client socket @param: caddr Iface addr object Client address @param: msg String Message to be sent to client """ return self.__sendMessage(client, msg) def broadcastMessage(self, msg): """ Send message to all client @param: msg String Message to be sent to all client """ return self.__broadcastMessage(msg) ############################################################################# Public method (exec/overwrite) def onStart(self): """ User define function called when console and server sockets are in listenning, and before enter in accept/error proceessing loop. """ pass def onStop(self): """ User define function called before stop server/console sockets. """ pass def onRestart(self, index): """ User define function called when console/server sockets are restarted. @param: index integer Current restart state: 1 before stop server/console socket 2 after stop server/console socket and before restart there 3 after restart server/console socket """ pass def onOpen(self, csock, caddr): """ User define function called when client connection is opened. @param: csock socket object Client socket @param: caddr Iface addr object Client address """ pass def onMessage(self, csock, caddr): """ User define function called when receive client message. Implement application protocol there. @param: csock socket object Client socket @param: caddr Iface addr object Client address """ pass def onClose(self, csock, caddr): """ User define function called when client connection is closed. @param: csock socket object Client socket @param: caddr Iface addr object Client address """ pass