def remove_connection(self, conn, host=None, reason=UNKNOWN): """ Remove a connection, it was closed by the server. :param conn: Connection to remove :param host: The host for to the connection. If passed, the connection will be removed faster. :param reason: Why this connection is removed """ # Just make sure we don't leak open connections try: conn.close() except OpenSSL.SSL.SysCallError: # This exception is raised when the remote end closes the connection # before we do. We continue as if nothing happen, because our goal # is to have a closed connection, and we already got that. pass # Remove it from out internal DB for conns in (self._free_conns, self._used_conns): conns.discard(conn) args = (conn, reason) msg = 'Removed %s from pool. Reason "%s"' debug(msg % args)
def remove_connection(self, conn, reason=UNKNOWN): """ Remove a connection, it was closed by the server. :param conn: Connection to remove :param reason: Why this connection is removed """ # Just make sure we don't leak open connections try: conn.close() except (AttributeError, OpenSSL.SSL.SysCallError): # This exception is raised when the remote end closes the connection # before we do. We continue as if nothing happen, because our goal # is to have a closed connection, and we already got that. pass # Remove it from out internal DB for conns in (self._free_conns, self._used_conns): conns.discard(conn) msg = 'Removed %s from pool. Reason "%s"' args = (conn, reason) debug(msg % args) msg = 'Free connection size %s / Used connection size %s' args = (len(self._free_conns), len(self._used_conns)) debug(msg % args)
def cleanup_broken_connections(self): """ Find connections that have been in self._used_conns or self._free_conns for more than FORCEFULLY_CLOSE_CONN_TIME. Close and remove them. :return: None """ now = time.time() for conn in self._used_conns.copy(): current_request_start = conn.current_request_start if current_request_start is None: continue time_in_used_state = now - current_request_start if time_in_used_state > self.FORCEFULLY_CLOSE_CONN_TIME: reason = ('Connection %s has been in "used_conns" for more than' ' %.2f seconds, forcefully closing it') args = (conn, self.FORCEFULLY_CLOSE_CONN_TIME) om.out.debug(reason % args) # This does a conn.close() and removes from self._used_conns self.remove_connection(conn, reason=reason) else: msg = '%s has been in used state for %.2f seconds' args = (conn, time_in_used_state) debug(msg % args) for conn in self._free_conns.copy(): connection_manager_move_ts = conn.connection_manager_move_ts if connection_manager_move_ts is None: continue time_in_free_state = now - connection_manager_move_ts if time_in_free_state > self.FORCEFULLY_CLOSE_CONN_TIME: reason = ('Connection %s has been in "free_conns" for more than' ' %.2f seconds, forcefully closing it') args = (conn, self.FORCEFULLY_CLOSE_CONN_TIME) om.out.debug(reason % args) # This does a conn.close() and removes from self._used_conns self.remove_connection(conn, reason=reason) else: msg = '%s has been in free state for %.2f seconds' args = (conn, time_in_free_state) debug(msg % args)
def replace_connection(self, bad_conn, req, conn_factory): """ Replace a broken connection. :param bad_conn: The bad connection :param req: The request we want to send using the new connection :param conn_factory: The factory function for new connection creation. """ # Remove self.remove_connection(bad_conn, reason='replace connection') # Create the new one new_conn = conn_factory(req) new_conn.current_request_start = time.time() new_conn.connection_manager_move_ts = time.time() self._used_conns.add(new_conn) # Log args = (bad_conn, new_conn) debug('Replaced broken %s with the new %s' % args) return new_conn
def _create_new_connection(self, req, conn_factory, host_port, conn_total): """ Creates a new HTTP connection using conn_factory :return: An HTTP connection """ debug('Creating a new HTTPConnection for request %s' % req) # Create a new connection conn = conn_factory(req) conn.current_request_start = time.time() conn.connection_manager_move_ts = time.time() # Store it internally self._used_conns.add(conn) # Log msg = 'Added %s to pool to use in %s, current %s pool size: %s' args = (conn, req, host_port, conn_total + 1) debug(msg % args) return conn
def _reuse_connection(self, req, host_port): """ Find an existing connection to reuse :param req: HTTP request :param host: The host to connect to :return: """ for conn in self.get_all_free_for_host_port(host_port): try: self._free_conns.remove(conn) except KeyError: # The connection was removed from the set by another thread continue else: self._used_conns.add(conn) conn.current_request_start = time.time() conn.connection_manager_move_ts = time.time() msg = 'Reusing free %s to use in %s' args = (conn, req) debug(msg % args) return conn
def get_available_connection(self, req, conn_factory): """ Return an available connection ready to be reused :param req: Request we want to send using the connection. :param conn_factory: Factory function for connection creation. Receives req as parameter. """ waited_time_for_conn = 0.0 host = req.get_host() self.log_stats(host) while waited_time_for_conn < self.GET_AVAILABLE_CONNECTION_RETRY_MAX_TIME: if not req.new_connection: # If the user is not specifying that he needs a new HTTP # connection for this request then check if we can reuse an # existing free connection from the connection pool # # By default req.new_connection is False, meaning that we'll # most likely re-use the connections # # A user sets req.new_connection to True when he wants to # do something special with the connection (such as setting # a specific timeout) for conn in self.get_all_free_for_host(host): try: self._free_conns.remove(conn) except KeyError: # The connection was removed from the set by another thread continue else: self._used_conns.add(conn) conn.current_request_start = time.time() msg = 'Reusing free %s to use in %s' args = (conn, req) debug(msg % args) return conn debug('Going to create a new HTTPConnection') # If the connection pool is not full let's try to create a new conn conn_total = self.get_connections_total(host) if conn_total < self.MAX_CONNECTIONS: # Create a new connection conn = conn_factory(req) conn.current_request_start = time.time() # Store it internally self._used_conns.add(conn) # Log msg = 'Added %s to pool to use in %s, current %s pool size: %s' args = (conn, req, host, conn_total + 1) debug(msg % args) if waited_time_for_conn > 0: msg = 'Waited %.2fs for a connection to be available in the pool.' om.out.debug(msg % waited_time_for_conn) return conn # Well, the connection pool for this host is full, this # means that many threads are sending requests to the host # and using the connections. This is not bad, just shows # that w3af is keeping the connections busy # # Another reason for this situation is that the connections # are *really* slow => taking many seconds to retrieve the # HTTP response => not freeing often # # We should wait a little and try again args = (conn_total, host) msg = ('MAX_CONNECTIONS (%s) for host %s reached. Waiting for one' ' to be released') debug(msg % args) waited_time_for_conn += self.GET_AVAILABLE_CONNECTION_RETRY_SECS time.sleep(self.GET_AVAILABLE_CONNECTION_RETRY_SECS) # Yet another potential situation is that w3af is not freeing the # connections properly because of a bug. Connections that never # leave the self._used_conns are going to slowly kill the HTTP # client, since at some point (when the max is reached) no more HTTP # requests will be sent. self.cleanup_broken_connections() msg = ('HTTP connection pool (keepalive) waited too long (%s sec)' ' for a free connection, giving up. This usually occurs' ' when w3af is scanning using a slow connection, the remote' ' server is slow (or applying QoS to IP addresses flagged' ' as attackers) or the configured number of threads in w3af' ' is too high compared with the connection manager' ' MAX_CONNECTIONS.') raise ConnectionPoolException(msg % self.GET_AVAILABLE_CONNECTION_RETRY_MAX_TIME)
def get_available_connection(self, req, conn_factory): """ Return an available connection ready to be reused :param req: Request we want to send using the connection. :param conn_factory: Factory function for connection creation. Receives req as parameter. """ host_port = req.get_netloc() self.log_stats(host_port) waited_time_for_conn = 0.0 while waited_time_for_conn < self.GET_AVAILABLE_CONNECTION_RETRY_MAX_TIME: # # One potential situation is that w3af is not freeing the # connections properly because of a bug. Connections that never # leave self._used_conns or self._free_conns are going to slowly kill # the HTTP connection pool, and then the whole framework, since at # some point (when the max is reached) no more HTTP requests will be # sent. # self.cleanup_broken_connections() conn_total = self.get_connections_total(host_port) # # If the connection pool is not full let's try to create a new connection # this is the default case, we want to quickly populate the connection # pool and, only if the pool is full, re-use the existing connections # # FIXME: Doing this here without a lock leads to a situation where # the total connections exceed the MAX_CONNECTIONS # if conn_total < self.MAX_CONNECTIONS: conn = self._create_new_connection(req, conn_factory, host_port, conn_total) self._log_waited_time_for_conn(waited_time_for_conn) return conn if req.new_connection: # # The user is requesting a new HTTP connection, this is a rare # case because req.new_connection is False by default. # # Before this feature was used together with req.timeout, but # now it is not required anymore. # # This code path is reached when there is no more space in the # connection pool, but because new_connection is set, it is # possible to force a free connection to be closed: # if len(self._free_conns) > len(self._used_conns): # # Close one of the free connections and create a new one. # # Close an existing free connection because the framework # is not using them (more free than used), this action should # not degrade the connection pool performance # conn = self.get_free_connection_to_close() if conn is not None: self.remove_connection(conn, reason='need fresh connection') self._log_waited_time_for_conn(waited_time_for_conn) return self._create_new_connection(req, conn_factory, host_port, conn_total) msg = ('The HTTP request %s has new_connection set to True.' ' This forces the ConnectionManager to wait until a' ' new connection can be created. No pre-existing' ' connections can be reused.') args = (req,) debug(msg % args) else: # # If the user is not specifying that he needs a new HTTP # connection for this request then check if we can reuse an # existing free connection from the connection pool # # By default req.new_connection is False, meaning that we'll # most likely re-use the connections # # A user sets req.new_connection to True when he wants to # do something special with the connection. In the past a # new_connection was set to True when a timeout was specified, # that is not required anymore! # conn = self._reuse_connection(req, host_port) if conn is not None: self._log_waited_time_for_conn(waited_time_for_conn) return conn # # Well, the connection pool for this host is full AND there are # no free connections to re-use, this means that many threads are # sending requests to the host and using the connections. This is # not bad, just shows that w3af is keeping the connections busy # # Another reason for this situation is that the connections # are *really* slow => taking many seconds to retrieve the # HTTP response => not freeing often # # We should wait a little and try again # msg = ('Will not create a new connection because MAX_CONNECTIONS' ' (%s) for host %s was reached. Waiting for a connection' ' to be released to send HTTP request %s. %s') stats = self.get_connection_pool_stats(host_port) args = (self.MAX_CONNECTIONS, host_port, req, stats) debug(msg % args) waited_time_for_conn += self.GET_AVAILABLE_CONNECTION_RETRY_SECS time.sleep(self.GET_AVAILABLE_CONNECTION_RETRY_SECS) msg = ('HTTP connection pool (keepalive) waited too long (%s sec)' ' for a free connection, giving up. This usually occurs' ' when w3af is scanning using a slow connection, the remote' ' server is slow (or applying QoS to IP addresses flagged' ' as attackers) or the configured number of threads in w3af' ' is too high compared with the connection manager' ' MAX_CONNECTIONS.') raise ConnectionPoolException(msg % self.GET_AVAILABLE_CONNECTION_RETRY_MAX_TIME)