From 81e659b760476a9aaeae788dce309458d0e704e6 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 6 Jul 2015 12:23:53 +0200 Subject: [PATCH] performance fix: minimizes connection overhead, using same socket by multiple commands without close it (ex.: 'start' sends several hundreds commands at once) --- bin/fail2ban-client | 50 +++++++++++++++++++--------------- fail2ban/client/csocket.py | 36 ++++++++++++++---------- fail2ban/server/asyncserver.py | 31 +++++++++++---------- 3 files changed, 66 insertions(+), 51 deletions(-) diff --git a/bin/fail2ban-client b/bin/fail2ban-client index ada4b376..7f3f5639 100755 --- a/bin/fail2ban-client +++ b/bin/fail2ban-client @@ -153,30 +153,36 @@ class Fail2banClient: return self.__processCmd([["ping"]], False) def __processCmd(self, cmd, showRet = True): - beautifier = Beautifier() - streamRet = True - for c in cmd: - beautifier.setInputCmd(c) - try: - client = CSocket(self.__conf["socket"]) - ret = client.send(c) - if ret[0] == 0: - logSys.debug("OK : " + `ret[1]`) + client = None + try: + beautifier = Beautifier() + streamRet = True + for c in cmd: + beautifier.setInputCmd(c) + try: + if not client: + client = CSocket(self.__conf["socket"]) + ret = client.send(c) + if ret[0] == 0: + logSys.debug("OK : " + `ret[1]`) + if showRet: + print beautifier.beautify(ret[1]) + else: + logSys.error("NOK: " + `ret[1].args`) + if showRet: + print beautifier.beautifyError(ret[1]) + streamRet = False + except socket.error: if showRet: - print beautifier.beautify(ret[1]) - else: - logSys.error("NOK: " + `ret[1].args`) + self.__logSocketError() + return False + except Exception, e: if showRet: - print beautifier.beautifyError(ret[1]) - streamRet = False - except socket.error: - if showRet: - self.__logSocketError() - return False - except Exception, e: - if showRet: - logSys.error(e) - return False + logSys.error(e) + return False + finally: + if client: + client.close() return streamRet def __logSocketError(self): diff --git a/fail2ban/client/csocket.py b/fail2ban/client/csocket.py index 9ac0eff1..1bb7e7de 100644 --- a/fail2ban/client/csocket.py +++ b/fail2ban/client/csocket.py @@ -29,39 +29,45 @@ from pickle import dumps, loads, HIGHEST_PROTOCOL import socket import sys -if sys.version_info >= (3,): - # b"" causes SyntaxError in python <= 2.5, so below implements equivalent - EMPTY_BYTES = bytes("", encoding="ascii") -else: - # python 2.x, string type is equivalent to bytes. - EMPTY_BYTES = "" - - class CSocket: + EMPTY_BYTES = "" + END_STRING = "" + CLOSE_STRING = "" + # python 2.x, string type is equivalent to bytes. if sys.version_info >= (3,): - END_STRING = bytes("", encoding='ascii') - else: - END_STRING = "" + # b"" causes SyntaxError in python <= 2.5, so below implements equivalent + EMPTY_BYTES = bytes(EMPTY_BYTES, encoding="ascii") + END_STRING = bytes(END_STRING, encoding='ascii') + CLOSE_STRING = bytes(CLOSE_STRING, encoding='ascii') - def __init__(self, sock = "/var/run/fail2ban/fail2ban.sock"): + def __init__(self, sock="/var/run/fail2ban/fail2ban.sock"): # Create an INET, STREAMing socket #self.csock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.__csock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) #self.csock.connect(("localhost", 2222)) self.__csock.connect(sock) + + def __del__(self): + self.close(False) def send(self, msg): # Convert every list member to string obj = dumps([str(m) for m in msg], HIGHEST_PROTOCOL) self.__csock.send(obj + CSocket.END_STRING) - ret = self.receive(self.__csock) + return self.receive(self.__csock) + + def close(self, sendEnd=True): + if not self.__csock: + return + if sendEnd: + self.__csock.sendall(CSocket.CLOSE_STRING + CSocket.END_STRING) self.__csock.close() - return ret + self.__csock = None @staticmethod def receive(sock): - msg = EMPTY_BYTES + msg = CSocket.EMPTY_BYTES while msg.rfind(CSocket.END_STRING) == -1: chunk = sock.recv(6) if chunk == '': diff --git a/fail2ban/server/asyncserver.py b/fail2ban/server/asyncserver.py index 4a8bc987..72406afa 100644 --- a/fail2ban/server/asyncserver.py +++ b/fail2ban/server/asyncserver.py @@ -38,14 +38,6 @@ from ..helpers import getLogger,formatExceptionInfo # Gets the instance of the logger. logSys = getLogger(__name__) -if sys.version_info >= (3,): - # b"" causes SyntaxError in python <= 2.5, so below implements equivalent - EMPTY_BYTES = bytes("", encoding="ascii") -else: - # python 2.x, string type is equivalent to bytes. - EMPTY_BYTES = "" - - ## # Request handler class. # @@ -54,10 +46,15 @@ else: class RequestHandler(asynchat.async_chat): + # python 2.x, string type is equivalent to bytes. + EMPTY_BYTES = "" + END_STRING = "" + CLOSE_STRING = "" if sys.version_info >= (3,): - END_STRING = bytes("", encoding="ascii") - else: - END_STRING = "" + # b"" causes SyntaxError in python <= 2.5, so below implements equivalent + EMPTY_BYTES = bytes(EMPTY_BYTES, encoding="ascii") + END_STRING = bytes(END_STRING, encoding="ascii") + CLOSE_STRING = bytes(CLOSE_STRING, encoding='ascii') def __init__(self, conn, transmitter): asynchat.async_chat.__init__(self, conn) @@ -76,16 +73,22 @@ class RequestHandler(asynchat.async_chat): # This method is called once we have a complete request. def found_terminator(self): + # Pop whole buffer + buf = self.__buffer + self.__buffer = [] # Joins the buffer items. - message = loads(EMPTY_BYTES.join(self.__buffer)) + message = loads(RequestHandler.EMPTY_BYTES.join(buf)) + # Close if close received + if message == RequestHandler.CLOSE_STRING: + # Closes the channel. + self.close_when_done() + return # Gives the message to the transmitter. message = self.__transmitter.proceed(message) # Serializes the response. message = dumps(message, HIGHEST_PROTOCOL) # Sends the response to the client. self.push(message + RequestHandler.END_STRING) - # Closes the channel. - self.close_when_done() def handle_error(self): e1, e2 = formatExceptionInfo()