/*******************************************************************************
* gMix open source project - https://svs.informatik.uni-hamburg.de/gmix/
* Copyright (C) 2014 SVS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*******************************************************************************/
package userGeneratedContent.testbedPlugIns.layerPlugIns.layer5application.httpPush_v0_001.exitClient;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import staticContent.framework.clock.Clock;
import staticContent.framework.config.Settings;
import staticContent.framework.util.Util;
import userGeneratedContent.testbedPlugIns.layerPlugIns.layer5application.httpPush_v0_001.dataObjects.Cache;
import userGeneratedContent.testbedPlugIns.layerPlugIns.layer5application.httpPush_v0_001.dataObjects.Connection;
import userGeneratedContent.testbedPlugIns.layerPlugIns.layer5application.httpPush_v0_001.dataObjects.HttpInfo;
import userGeneratedContent.testbedPlugIns.layerPlugIns.layer5application.httpPush_v0_001.dataObjects.HttpPartType;
import userGeneratedContent.testbedPlugIns.layerPlugIns.layer5application.httpPush_v0_001.helper.SocksHandler;
import userGeneratedContent.testbedPlugIns.layerPlugIns.layer5application.httpPush_v0_001.mix.ConnectionPoolInterface;
/**
* This class manages all connections to a webserver.
*
* It generates a new connection if a socks requests arrives and stores it in the connection map.
*
* If data arrives on a connection socket, it adds the connection to the readable connections queue.
*
* @author bash
*
*/
public class ExitConnectionPool extends Thread implements ConnectionPoolInterface {
private Cache cache;
private LinkedBlockingQueue<Connection> readableConnections;
private ConcurrentHashMap<Integer, Connection> connectionMap;
private MixExitConnection mixConnection;
private Selector selector = null;
private Clock clock;
private Settings settings;
/**
* @param bufferSize
* @param readableConnections
* @param connectionMap
*/
public ExitConnectionPool(int bufferSize, LinkedBlockingQueue<Connection> readableConnections,
ConcurrentHashMap<Integer, Connection> connectionMap, MixExitConnection mixConnection, Settings settings) {
this.mixConnection = mixConnection;
this.readableConnections = readableConnections;
this.connectionMap = connectionMap;
clock = new Clock(settings);
this.settings = settings;
cache = new Cache(settings.getPropertyAsInt("HP_CACHE_SIZE"), connectionMap);
try {
selector = Selector.open();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* @return the mixConnection
*/
public MixExitConnection getMixConnection() {
return mixConnection;
}
/**
* @param mixConnection
* the mixConnection to set
*/
public void setMixConnection(MixExitConnection mixConnection) {
this.mixConnection = mixConnection;
}
/**
* Generate new Connection from Socks Request
*
* @param id
* @param socksRequest
*/
public void generateConnectionFromSocks(int id, byte[] socksRequest) {
byte aTyp = socksRequest[3];
InetSocketAddress address = SocksHandler.getInetAddress(socksRequest);
// System.out.println("mix: connection to " + address.toString() + ":" + address.getPort());
SocketChannel socketChannel = null;
try {
socketChannel = SocketChannel.open();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
byte[] socksReply;
try {
if (socketChannel.connect(address)) {
byte code = 0x00;
// System.out.println("mix: connection to " + address.toString() + ":" + address.getPort()
// + " established");
socksReply = SocksHandler.sendSocks5ConnectionReply(code, aTyp, address);
socketChannel.configureBlocking(false);
Connection connection = new Connection(socketChannel, id, clock, settings, this, cache);
connection.setStatusHTTPFromMix(new HttpInfo(HttpPartType.Header, 0));
connection.setStatusHTTPFromApp(new HttpInfo(HttpPartType.Header, 0));
connectionMap.put(id, connection);
// System.out.println("mix: send socksrequestresponse");
registerSocket(socketChannel, connection, SelectionKey.OP_READ);
int len = socksReply.length;
byte[] sendMessage = Util.concatArrays(
Util.concatArrays(Util.intToByteArray(id), Util.intToByteArray(len)), socksReply);
// mixConnection.writeChunk(sendMessage);
} else {
byte code = 0x04;
socksReply = SocksHandler.sendSocks5ConnectionReply(code, aTyp, address);
}
//System.out.println("mix: return socksreply");
} catch (IOException e) {
byte code = 0x04;
socksReply = SocksHandler.sendSocks5ConnectionReply(code, aTyp, address);
}
}
@Override
public void run() {
while (true) {
try {
selector.select();
// Synchronized is necessary to prevent a race condition
// with the registerConnection Method
} catch (IOException e) {
System.err.println("Critical Error! No selector registered! System halt!");
e.printStackTrace();
break;
}
synchronized (this) {
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
while (it.hasNext()) {
SelectionKey key = (SelectionKey) it.next();
if (key.isReadable()) {
try {
// System.out.println("Antwort erhalten");
this.readableConnections.add((Connection) key.attachment());
key.cancel();
} catch (Exception e) {
e.printStackTrace();
}
key.cancel();
it.remove();
}
}
}
}
}
/**
* Reads a couple of bytes depending on the address type and returns this
* address as an InetAddress-Object.
*
* @param aTyp
* valid values: 0x01, 0x03, 0x04
* @return InetAddress
*/
@SuppressWarnings("unused")
private InetSocketAddress getInetAddress(byte aTyp, byte[] message) {
ByteBuffer wrapped;
byte[] byteAddress = null;
int length;
int port = 0;
InetAddress address = null;
try {
switch (aTyp) {
case 0x01: // IP v4
length = 4;
byteAddress = Arrays.copyOfRange(message, 4, 8);
address = InetAddress.getByAddress(byteAddress);
wrapped = ByteBuffer.wrap(Arrays.copyOfRange(message, 9, 10));
port = wrapped.getInt();
break;
case 0x03: // Domain, first byte defines the length of it
length = Util.unsignedByteToShort(message[4]);
byteAddress = Arrays.copyOfRange(message, 5, 5 + length);
String stringAddress = Util.getStringWithoutNewLines(byteAddress);
address = InetAddress.getByName(stringAddress);
wrapped = ByteBuffer.wrap(Arrays.copyOfRange(message, 5 + length, 6 + length));
port = wrapped.getInt();
break;
case 0x04: // IP v6
length = 16;
byteAddress = Arrays.copyOfRange(message, 4, 20);
address = InetAddress.getByAddress(byteAddress);
wrapped = ByteBuffer.wrap(Arrays.copyOfRange(message, 21, 22));
port = wrapped.getInt();
break;
}
} catch (UnknownHostException e) {
e.printStackTrace();
}
InetSocketAddress socketAdress = new InetSocketAddress(address, port);
return socketAdress;
}
@Override
public Selector getSelector() {
return selector;
}
/**
* Method to register a read event for the underlying connection
*
* @return SelectionKey (null if fails)
* @throws IOException
*/
public SelectionKey registerSocket(SocketChannel serverSocket, Connection connection, int event) throws IOException {
SelectionKey newKey = null;
synchronized (this) {
selector.wakeup();
selector.selectNow();
newKey = serverSocket.register(selector, event, connection);
return newKey;
}
}
/**
* @return the connectionMap
*/
public ConcurrentHashMap<Integer, Connection> getConnectionMap() {
return connectionMap;
}
/**
* @param connectionMap the connectionMap to set
*/
public void setConnectionMap(ConcurrentHashMap<Integer, Connection> connectionMap) {
this.connectionMap = connectionMap;
}
}