001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.mina.filter.firewall; 021 022import java.net.InetSocketAddress; 023import java.net.SocketAddress; 024import java.util.Iterator; 025import java.util.Map; 026import java.util.concurrent.ConcurrentHashMap; 027import java.util.concurrent.locks.Lock; 028import java.util.concurrent.locks.ReentrantLock; 029 030import org.apache.mina.core.filterchain.IoFilter; 031import org.apache.mina.core.filterchain.IoFilterAdapter; 032import org.apache.mina.core.session.IoSession; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035 036/** 037 * A {@link IoFilter} which blocks connections from connecting 038 * at a rate faster than the specified interval. 039 * 040 * @author <a href="http://mina.apache.org">Apache MINA Project</a> 041 */ 042public class ConnectionThrottleFilter extends IoFilterAdapter { 043 /** A logger for this class */ 044 private final static Logger LOGGER = LoggerFactory.getLogger(ConnectionThrottleFilter.class); 045 046 /** The default delay to wait for a session to be accepted again */ 047 private static final long DEFAULT_TIME = 1000; 048 049 /** 050 * The minimal delay the sessions will have to wait before being created 051 * again 052 */ 053 private long allowedInterval; 054 055 /** The map of created sessiosn, associated with the time they were created */ 056 private final Map<String, Long> clients; 057 058 /** A lock used to protect the map from concurrent modifications */ 059 private Lock lock = new ReentrantLock(); 060 061 // A thread that is used to remove sessions that have expired since they 062 // have 063 // been added. 064 private class ExpiredSessionThread extends Thread { 065 public void run() { 066 067 try { 068 // Wait for the delay to be expired 069 Thread.sleep(allowedInterval); 070 } catch (InterruptedException e) { 071 // We have been interrupted, get out of the loop. 072 return; 073 } 074 075 // now, remove all the sessions that have been created 076 // before the delay 077 long currentTime = System.currentTimeMillis(); 078 079 lock.lock(); 080 081 try { 082 Iterator<String> sessions = clients.keySet().iterator(); 083 084 while (sessions.hasNext()) { 085 String session = sessions.next(); 086 long creationTime = clients.get(session); 087 088 if (creationTime + allowedInterval < currentTime) { 089 clients.remove(session); 090 } 091 } 092 } finally { 093 lock.unlock(); 094 } 095 } 096 } 097 098 /** 099 * Default constructor. Sets the wait time to 1 second 100 */ 101 public ConnectionThrottleFilter() { 102 this(DEFAULT_TIME); 103 } 104 105 /** 106 * Constructor that takes in a specified wait time. 107 * 108 * @param allowedInterval 109 * The number of milliseconds a client is allowed to wait 110 * before making another successful connection 111 * 112 */ 113 public ConnectionThrottleFilter(long allowedInterval) { 114 this.allowedInterval = allowedInterval; 115 clients = new ConcurrentHashMap<String, Long>(); 116 117 // Create the cleanup thread 118 ExpiredSessionThread cleanupThread = new ExpiredSessionThread(); 119 120 // And make it a daemon so that it's killed when the server exits 121 cleanupThread.setDaemon(true); 122 123 // start the cleanuo thread now 124 cleanupThread.start(); 125 } 126 127 /** 128 * Sets the interval between connections from a client. 129 * This value is measured in milliseconds. 130 * 131 * @param allowedInterval 132 * The number of milliseconds a client is allowed to wait 133 * before making another successful connection 134 */ 135 public void setAllowedInterval(long allowedInterval) { 136 lock.lock(); 137 138 try { 139 this.allowedInterval = allowedInterval; 140 } finally { 141 lock.unlock(); 142 } 143 } 144 145 /** 146 * Method responsible for deciding if a connection is OK 147 * to continue 148 * 149 * @param session 150 * The new session that will be verified 151 * @return 152 * True if the session meets the criteria, otherwise false 153 */ 154 protected boolean isConnectionOk(IoSession session) { 155 SocketAddress remoteAddress = session.getRemoteAddress(); 156 157 if (remoteAddress instanceof InetSocketAddress) { 158 InetSocketAddress addr = (InetSocketAddress) remoteAddress; 159 long now = System.currentTimeMillis(); 160 161 lock.lock(); 162 163 try { 164 if (clients.containsKey(addr.getAddress().getHostAddress())) { 165 166 LOGGER.debug("This is not a new client"); 167 Long lastConnTime = clients.get(addr.getAddress().getHostAddress()); 168 169 clients.put(addr.getAddress().getHostAddress(), now); 170 171 // if the interval between now and the last connection is 172 // less than the allowed interval, return false 173 if (now - lastConnTime < allowedInterval) { 174 LOGGER.warn("Session connection interval too short"); 175 return false; 176 } 177 178 return true; 179 } 180 181 clients.put(addr.getAddress().getHostAddress(), now); 182 } finally { 183 lock.unlock(); 184 } 185 186 return true; 187 } 188 189 return false; 190 } 191 192 @Override 193 public void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception { 194 if (!isConnectionOk(session)) { 195 LOGGER.warn("Connections coming in too fast; closing."); 196 session.close(true); 197 } 198 199 nextFilter.sessionCreated(session); 200 } 201}