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.closeNow();
197        }
198
199        nextFilter.sessionCreated(session);
200    }
201}