/*
* Tigase Jabber/XMPP Server
* Copyright (C) 2004-2012 "Artur Hefczyc" <artur.hefczyc@tigase.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. Look for COPYING file in the top folder.
* If not, see http://www.gnu.org/licenses/.
*
* $Rev$
* Last modified by $Author$
* $Date$
*/
package tigase.net;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
//~--- classes ----------------------------------------------------------------
/**
* Describe class ConnectionOpenThread here.
*
*
* Created: Wed Jan 25 23:51:28 2006
*
* @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a>
* @version $Rev$
*/
public class ConnectionOpenThread implements Runnable {
private static final Logger log = Logger.getLogger(ConnectionOpenThread.class.getName());
private static ConnectionOpenThread acceptThread = null;
/**
*
*/
public static final long def_5222_throttling = 200;
/** Field description */
public static final long def_5223_throttling = 50;
/** Field description */
public static final long def_5280_throttling = 1000;
/** Field description */
public static final long def_5269_throttling = 100;
/** Field description */
public static Map<Integer, PortThrottlingData> throttling = new ConcurrentHashMap<Integer,
PortThrottlingData>(10);
//~--- fields ---------------------------------------------------------------
protected long accept_counter = 0;
private Selector selector = null;
private boolean stopping = false;
private Timer timer = null;
private ConcurrentLinkedQueue<ConnectionOpenListener> waiting =
new ConcurrentLinkedQueue<ConnectionOpenListener>();
//~--- constructors ---------------------------------------------------------
/**
* Creates a new <code>ConnectionOpenThread</code> instance.
*
*/
private ConnectionOpenThread() {
timer = new Timer("Connections open timer", true);
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
for (PortThrottlingData portData : throttling.values()) {
portData.lastSecondConnections = 0;
}
}
}, 1000, 1000);
try {
selector = Selector.open();
} catch (Exception e) {
log.log(Level.SEVERE, "Server I/O error, can't continue my work.", e);
stopping = true;
} // end of try-catch
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public static ConnectionOpenThread getInstance() {
// Long new_throttling = Long.getLong("new-connections-throttling");
// if (new_throttling != null) {
// throttling = new_throttling;
// log.log(Level.WARNING, "New connections throttling set to: {0}", throttling);
// }
if (acceptThread == null) {
acceptThread = new ConnectionOpenThread();
Thread thrd = new Thread(acceptThread);
thrd.setName("ConnectionOpenThread");
thrd.start();
if (log.isLoggable(Level.FINER)) {
log.finer("ConnectionOpenThread started.");
}
} // end of if (acceptThread == null)
return acceptThread;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param al
*/
public void addConnectionOpenListener(ConnectionOpenListener al) {
waiting.offer(al);
selector.wakeup();
}
/**
* Method description
*
*
* @param al
*/
public void removeConnectionOpenListener(ConnectionOpenListener al) {
for (SelectionKey key : selector.keys()) {
if (al == key.attachment()) {
try {
key.cancel();
SelectableChannel channel = key.channel();
channel.close();
} catch (Exception e) {
log.log(Level.WARNING, "Exception during removing connection listener.", e);
}
break;
}
}
}
/**
* Method description
*
*/
@Override
public void run() {
while ( !stopping) {
try {
selector.select();
// Set<SelectionKey> selected_keys = selector.selectedKeys();
// for (SelectionKey sk : selected_keys) {
for (Iterator i = selector.selectedKeys().iterator(); i.hasNext(); ) {
SelectionKey sk = (SelectionKey) i.next();
i.remove();
SocketChannel sc = null;
if ((sk.readyOps() & SelectionKey.OP_ACCEPT) != 0) {
ServerSocketChannel nextReady = (ServerSocketChannel) sk.channel();
sc = nextReady.accept();
if (log.isLoggable(Level.FINEST)) {
log.finest("OP_ACCEPT");
}
PortThrottlingData port_throttling = throttling.get(nextReady.socket().getLocalPort());
if (port_throttling != null) {
++port_throttling.lastSecondConnections;
if (port_throttling.lastSecondConnections > port_throttling.throttling) {
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "New connections throttling level exceeded, closing: {0}",
sc);
}
sc.close();
sc = null;
}
} else {
// Hm, this should not happen actually
log.log(Level.WARNING, "Throttling not configured for port: {0}",
nextReady.socket().getLocalPort());
}
} // end of if (sk.readyOps() & SelectionKey.OP_ACCEPT)
if ((sk.readyOps() & SelectionKey.OP_CONNECT) != 0) {
sk.cancel();
sc = (SocketChannel) sk.channel();
if (log.isLoggable(Level.FINEST)) {
log.finest("OP_CONNECT");
}
} // end of if (sk.readyOps() & SelectionKey.OP_ACCEPT)
if (sc != null) {
// We have to catch exception here as sometimes socket is closed
// or connection is broken before we start configuring it here
// then whatever we do on the socket it throws an exception
try {
sc.configureBlocking(false);
sc.socket().setSoLinger(false, 0);
sc.socket().setReuseAddress(true);
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "Registered new client socket: {0}", sc);
}
ConnectionOpenListener al = (ConnectionOpenListener) sk.attachment();
sc.socket().setTrafficClass(al.getTrafficClass());
sc.socket().setReceiveBufferSize(al.getReceiveBufferSize());
al.accept(sc);
} catch (java.net.SocketException e) {
log.log(Level.INFO, "Socket closed instantly after it had been opened?", e);
ConnectionOpenListener al = (ConnectionOpenListener) sk.attachment();
al.accept(sc);
}
} else {
log.warning("Can't obtain socket channel from selection key.");
} // end of if (sc != null) else
++accept_counter;
}
addAllWaiting();
} catch (IOException e) {
log.log(Level.SEVERE, "Server I/O error.", e);
// stopping = true;
} // end of catch
catch (Exception e) {
log.log(Level.SEVERE, "Other service exception.", e);
// stopping = true;
} // end of catch
}
}
/**
* Method description
*
*/
public void start() {
Thread t = new Thread(this);
t.setName("ConnectionOpenThread");
t.start();
}
/**
* Method description
*
*/
public void stop() {
stopping = true;
selector.wakeup();
}
private void addAllWaiting() throws IOException {
ConnectionOpenListener al = null;
while ((al = waiting.poll()) != null) {
try {
addPort(al);
} catch (Exception e) {
log.log(Level.WARNING, "Error: creating connection for: " + al, e);
} // end of try-catch
} // end of for ()
}
private void addISA(InetSocketAddress isa, ConnectionOpenListener al) throws IOException {
switch (al.getConnectionType()) {
case accept :
long port_throttling = getThrottlingForPort(isa.getPort());
throttling.put(isa.getPort(), new PortThrottlingData(port_throttling));
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST,
"Setting up throttling for the port {0} to {1} connections per second.",
new Object[] { isa.getPort(),
port_throttling });
}
if (log.isLoggable(Level.FINEST)) {
log.finest("Setting up 'accept' channel...");
}
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().setReceiveBufferSize(al.getReceiveBufferSize());
ssc.configureBlocking(false);
ssc.socket().bind(isa);
ssc.register(selector, SelectionKey.OP_ACCEPT, al);
break;
case connect :
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Setting up ''connect'' channel for: {0}/{1}",
new Object[] { isa.getAddress(),
isa.getPort() });
}
SocketChannel sc = SocketChannel.open();
sc.socket().setReceiveBufferSize(al.getReceiveBufferSize());
sc.socket().setTrafficClass(al.getTrafficClass());
sc.configureBlocking(false);
sc.connect(isa);
sc.register(selector, SelectionKey.OP_CONNECT, al);
break;
default :
log.log(Level.WARNING, "Unknown connection type: {0}", al.getConnectionType());
break;
} // end of switch (al.getConnectionType())
}
private void addPort(ConnectionOpenListener al) throws IOException {
if ((al.getIfcs() == null) || (al.getIfcs().length == 0) || al.getIfcs()[0].equals("ifc")
|| al.getIfcs()[0].equals("*")) {
addISA(new InetSocketAddress(al.getPort()), al);
} else {
for (String ifc : al.getIfcs()) {
addISA(new InetSocketAddress(ifc, al.getPort()), al);
} // end of for ()
} // end of if (ip == null || ip.equals("")) else
}
//~--- get methods ----------------------------------------------------------
private long getThrottlingForPort(int port) {
long result = def_5222_throttling;
switch (port) {
case 5223 :
result = def_5223_throttling;
break;
case 5269 :
result = def_5269_throttling;
break;
case 5280 :
result = def_5280_throttling;
break;
}
String throttling_prop = System.getProperty("new-connections-throttling");
if (throttling_prop != null) {
String[] all_ports_thr = throttling_prop.split(",");
for (String port_thr : all_ports_thr) {
String[] port_thr_ar = port_thr.split(":");
if (port_thr_ar.length == 2) {
try {
int port_no = Integer.parseInt(port_thr_ar[0]);
if (port_no == port) {
return Long.parseLong(port_thr_ar[1]);
}
} catch (Exception e) {
// bad configuration
log.log(Level.WARNING,
"Connections throttling configuration error, bad format, "
+ "check the documentation for a correct syntax, "
+ "port throttling config: {0}", port_thr);
}
} else {
// bad configuration
log.log(Level.WARNING, "Connections throttling configuration error, bad format, "
+ "check the documentation for a correct syntax, "
+ "port throttling config: {0}", port_thr);
}
}
}
return result;
}
//~--- inner classes --------------------------------------------------------
private class PortThrottlingData {
/** Field description */
protected long lastSecondConnections = 0;
/** Field description */
protected long throttling;
//~--- constructors -------------------------------------------------------
/**
* Constructs ...
*
*
* @param throttling_prop
*/
private PortThrottlingData(long throttling_prop) {
throttling = throttling_prop;
}
}
} // ConnectionOpenThread
//~ Formatted in Sun Code Convention
//~ Formatted by Jindent --- http://www.jindent.com