Index: C:/Lab/VOID/projects/james-trunk/src/java/org/apache/james/util/connection/SimpleConnectionManager.java =================================================================== --- C:/Lab/VOID/projects/james-trunk/src/java/org/apache/james/util/connection/SimpleConnectionManager.java (revision 390042) +++ C:/Lab/VOID/projects/james-trunk/src/java/org/apache/james/util/connection/SimpleConnectionManager.java (working copy) @@ -56,6 +56,12 @@ */ private static final int DEFAULT_MAX_CONNECTIONS = 30; /** + * The default value for the maximum number of allowed client + * connections. + */ + private static final int DEFAULT_MAX_CONNECTIONS_PER_IP = 0; + + /** * The map of connection name / server connections managed by this connection * manager. */ @@ -69,6 +75,10 @@ */ protected int maxOpenConn = 0; /** + * The maximum number of client connections allowed per server connection per IP. + */ + protected int maxOpenConnPerIP = 0; + /** * The ThreadManager component that is used to provide a default thread pool. */ private ThreadManager threadManager; @@ -82,6 +92,7 @@ public void configure(final Configuration configuration) throws ConfigurationException { timeout = configuration.getChild("idle-timeout").getValueAsInteger(DEFAULT_SOCKET_TIMEOUT); maxOpenConn = configuration.getChild("max-connections").getValueAsInteger(DEFAULT_MAX_CONNECTIONS); + maxOpenConnPerIP = configuration.getChild("max-connections-per-ip").getValueAsInteger(DEFAULT_MAX_CONNECTIONS_PER_IP); if (timeout < 0) { StringBuffer exceptionBuffer = new StringBuffer(128).append("Specified socket timeout value of ").append(timeout).append( @@ -95,6 +106,13 @@ " is not a legal value."); throw new ConfigurationException(exceptionBuffer.toString()); } + if (maxOpenConnPerIP < 0) { + StringBuffer exceptionBuffer = + new StringBuffer(128).append("Specified maximum number of open connections per IP of ").append( + maxOpenConnPerIP).append( + " is not a legal value."); + throw new ConfigurationException(exceptionBuffer.toString()); + } if (getLogger().isDebugEnabled()) { getLogger().debug( "Connection timeout is " + (timeout == 0 ? "unlimited" : Long.toString(timeout))); @@ -159,9 +177,12 @@ } if (maxOpenConnections < 0) { throw new IllegalArgumentException("The maximum number of client connections per server socket cannot be less that zero."); + } + if (maxOpenConnPerIP < 0) { + throw new IllegalArgumentException("The maximum number of client connections (per IP) per server socket cannot be less that zero."); } ServerConnection runner = - new ServerConnection(socket, handlerFactory, threadPool, timeout, maxOpenConnections); + new ServerConnection(socket, handlerFactory, threadPool, timeout, maxOpenConnections, maxOpenConnPerIP); setupLogger(runner); ContainerUtil.initialize(runner); connectionMap.put(name, runner); @@ -207,6 +228,7 @@ * @param socket the ServerSocket from which to * @param handlerFactory the factory from which to acquire handlers * @param maxOpenConnections the maximum number of open connections allowed for this server socket. + * @param maxOpenConnections the maximum number of open connections per IP allowed for this server socket. * @exception Exception if an error occurs */ public void connect( @@ -253,5 +275,16 @@ public int getMaximumNumberOfOpenConnections() { return maxOpenConn; } + + /** + * Returns the default maximum number of open connections per IP supported by this + * SimpleConnectionManager + * + * @return the maximum number of connections + */ + public int getMaximumNumberOfOpenConnectionsPerIP() { + return maxOpenConnPerIP; + } + } Index: C:/Lab/VOID/projects/james-trunk/src/java/org/apache/james/util/connection/ServerConnection.java =================================================================== --- C:/Lab/VOID/projects/james-trunk/src/java/org/apache/james/util/connection/ServerConnection.java (revision 390042) +++ C:/Lab/VOID/projects/james-trunk/src/java/org/apache/james/util/connection/ServerConnection.java (working copy) @@ -23,6 +23,7 @@ import java.net.Socket; import java.net.SocketException; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import org.apache.avalon.cornerstone.services.connection.ConnectionHandler; @@ -96,13 +97,24 @@ * connection will allow. */ private int maxOpenConn; + + /** + * The maximum number of open client connections per IP that this server + * connection will allow. + */ + private int maxOpenConnPerIP; /** * A collection of client connection runners. */ private final ArrayList clientConnectionRunners = new ArrayList(); - + /** + * A HashMap of clientip and connections + */ + private final HashMap connectionPerIP = new HashMap(); + + /** * The thread used to manage this server connection. */ private Thread serverConnectionThread; @@ -117,17 +129,20 @@ * @param timeout the client idle timeout for this ServerConnection's client connections * @param maxOpenConn the maximum number of open client connections allowed for this * ServerConnection + * @param maxOpenConnPerIP the maximum number of open client connections allowed for this + * ServerConnection per IP */ public ServerConnection(ServerSocket serverSocket, ConnectionHandlerFactory handlerFactory, ThreadPool threadPool, int timeout, - int maxOpenConn) { + int maxOpenConn, int maxOpenConnPerIP) { this.serverSocket = serverSocket; this.handlerFactory = handlerFactory; connThreadPool = threadPool; socketTimeout = timeout; this.maxOpenConn = maxOpenConn; + this.maxOpenConnPerIP = maxOpenConnPerIP; } /** @@ -159,7 +174,9 @@ serverConnectionThread = null; thread.interrupt(); try { + serverSocket.close(); + } catch (IOException ie) { // Ignored - we're doing this to break out of the // accept. This minimizes the time required to @@ -191,6 +208,8 @@ runner = null; } clientConnectionRunners.clear(); + clearConnectionPerIP(); + } getLogger().debug("Cleaned up clients - " + this.toString()); @@ -243,6 +262,57 @@ } /** + * Raise the connectionCount for the given ipAdrress + * @param ip raise the connectionCount for the given ipAddress + */ + private synchronized void addConnectionPerIP (String ip) { + connectionPerIP.put(ip,Integer.toString(getConnectionPerIP(ip) +1)); + } + + /** + * Get the count of connections for the given ip + * @param ip the ipAddress to get the connections for. + * @return count + */ + private synchronized int getConnectionPerIP(String ip) { + int count = 0; + String curCount = null; + Object c = connectionPerIP.get(ip); + + if (c != null) { + curCount = c.toString(); + + if (curCount != null) { + return Integer.parseInt(curCount); + } + } + return count; + } + + /** + * Set the connection count for the given ipAddress + * @param ip ipAddres for which we want to set the count + */ + private synchronized void removeConnectionPerIP (String ip) { + + int count = getConnectionPerIP(ip); + if (count > 1) { + connectionPerIP.put(ip,Integer.toString(count -1)); + } else { + // not need this entry any more + connectionPerIP.remove(ip); + } + + } + + /** + * Clear the connection count map + */ + private synchronized void clearConnectionPerIP () { + connectionPerIP.clear(); + } + + /** * Provides the body for the thread of execution for a ServerConnection. * Connections made to the server socket are passed to an appropriate, * newly created, ClientConnectionRunner @@ -304,10 +374,26 @@ // We ignore this exception, as we already have an error condition. } continue; + } else if ((maxOpenConnPerIP > 0) && (getConnectionPerIP(clientSocket.getInetAddress().getHostAddress()) >= maxOpenConnPerIP)) { + getLogger().info("Maximum number of open connections per IP exceeded - refusing connection. Current number of connections is " + getConnectionPerIP(clientSocket.getInetAddress().getHostAddress())); + + if (getLogger().isWarnEnabled()) { + + getLogger().warn("Maximum number of open connections per IP exceeded - refusing connection. Current number of connections is " + getConnectionPerIP(clientSocket.getInetAddress().getHostAddress())); + } + try { + clientSocket.close(); + } catch (IOException ignored) { + // We ignore this exception, as we already have an error condition. + } + continue; + } else { + addConnectionPerIP(clientSocket.getInetAddress().getHostAddress()); clientSocket.setSoTimeout(socketTimeout); runner = addClientConnectionRunner(); runner.setSocket(clientSocket); + } } setupLogger( runner ); @@ -320,6 +406,7 @@ getLogger().error("Internal error - insufficient threads available to service request. " + Thread.activeCount() + " threads in service request pool.", e); try { + removeConnectionPerIP(clientSocket.getInetAddress().getHostAddress()); clientSocket.close(); } catch (IOException ignored) { // We ignore this exception, as we already have an error condition. @@ -368,7 +455,7 @@ public ClientConnectionRunner() { } - + /** * The dispose operation that terminates the runner. Should only be * called by the ServerConnection that owns the ClientConnectionRunner @@ -430,6 +517,9 @@ getLogger().error( "Error handling connection", e ); } finally { + // remove this connection from map! + removeConnectionPerIP(clientSocket.getInetAddress().getHostAddress()); + // Close the underlying socket try { if (clientSocket != null) { Index: C:/Lab/VOID/projects/james-trunk/src/conf/james-config.xml =================================================================== --- C:/Lab/VOID/projects/james-trunk/src/conf/james-config.xml (revision 395808) +++ C:/Lab/VOID/projects/james-trunk/src/conf/james-config.xml (working copy) @@ -1154,9 +1158,17 @@ + + + + + 300000 30 +