View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.mina.filter.firewall;
21  
22  import java.net.InetSocketAddress;
23  import java.net.SocketAddress;
24  import java.util.Iterator;
25  import java.util.Map;
26  import java.util.concurrent.ConcurrentHashMap;
27  import java.util.concurrent.locks.Lock;
28  import java.util.concurrent.locks.ReentrantLock;
29  
30  import org.apache.mina.core.filterchain.IoFilter;
31  import org.apache.mina.core.filterchain.IoFilterAdapter;
32  import org.apache.mina.core.session.IoSession;
33  import org.slf4j.Logger;
34  import org.slf4j.LoggerFactory;
35  
36  /**
37   * A {@link IoFilter} which blocks connections from connecting
38   * at a rate faster than the specified interval.
39   *
40   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
41   */
42  public class ConnectionThrottleFilter extends IoFilterAdapter {
43      /** A logger for this class */
44      private final static Logger LOGGER = LoggerFactory.getLogger(ConnectionThrottleFilter.class);
45  
46      /** The default delay to wait for a session to be accepted again */
47      private static final long DEFAULT_TIME = 1000;
48  
49      /**
50       * The minimal delay the sessions will have to wait before being created
51       * again
52       */
53      private long allowedInterval;
54  
55      /** The map of created sessiosn, associated with the time they were created */
56      private final Map<String, Long> clients;
57  
58      /** A lock used to protect the map from concurrent modifications */
59      private Lock lock = new ReentrantLock();
60  
61      // A thread that is used to remove sessions that have expired since they
62      // have
63      // been added.
64      private class ExpiredSessionThread extends Thread {
65          public void run() {
66  
67              try {
68                  // Wait for the delay to be expired
69                  Thread.sleep(allowedInterval);
70              } catch (InterruptedException e) {
71                  // We have been interrupted, get out of the loop.
72                  return;
73              }
74  
75              // now, remove all the sessions that have been created
76              // before the delay
77              long currentTime = System.currentTimeMillis();
78  
79              lock.lock();
80  
81              try {
82                  Iterator<String> sessions = clients.keySet().iterator();
83  
84                  while (sessions.hasNext()) {
85                      String session = sessions.next();
86                      long creationTime = clients.get(session);
87  
88                      if (creationTime + allowedInterval < currentTime) {
89                          clients.remove(session);
90                      }
91                  }
92              } finally {
93                  lock.unlock();
94              }
95          }
96      }
97  
98      /**
99       * 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                     if (LOGGER.isDebugEnabled()) {
167                         LOGGER.debug("This is not a new client");
168                     }
169                     
170                     Long lastConnTime = clients.get(addr.getAddress().getHostAddress());
171 
172                     clients.put(addr.getAddress().getHostAddress(), now);
173 
174                     // if the interval between now and the last connection is
175                     // less than the allowed interval, return false
176                     if (now - lastConnTime < allowedInterval) {
177                         LOGGER.warn("Session connection interval too short");
178                         return false;
179                     }
180 
181                     return true;
182                 }
183 
184                 clients.put(addr.getAddress().getHostAddress(), now);
185             } finally {
186                 lock.unlock();
187             }
188 
189             return true;
190         }
191 
192         return false;
193     }
194 
195     @Override
196     public void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception {
197         if (!isConnectionOk(session)) {
198             LOGGER.warn("Connections coming in too fast; closing.");
199             session.closeNow();
200         }
201 
202         nextFilter.sessionCreated(session);
203     }
204 }