/******************************************************************************* * 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.layer1network.sourceRouting_TCP_v0_001; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.Vector; import staticContent.framework.controller.SubImplementation; import staticContent.framework.message.MixMessage; import staticContent.framework.message.Reply; import staticContent.framework.message.Request; import staticContent.framework.routing.RoutingMode; import staticContent.framework.userDatabase.User; import staticContent.framework.userDatabase.UserAttachment; import staticContent.framework.util.Util; public class ClientHandler_TCP_RR_sync extends SubImplementation implements ClientReplyReceiver { //TODO: add timeout for inactive users private int port; private InetAddress bindAddress; private int backlog; private ServerSocket serverSocket; private int maxRequestLength; //private int maxReplyLength; // TODO private Vector<ChannelData> channels; private Vector<ChannelData> newConnections; private int expectedConnections; private int requestBufferSize; private int replyBufferSize; private Object attachmentKey = new Object(); //private int queueBlockSize; // for connection based mixes: private ConnectionBasedAcceptorThread acceptorThread; private ConnectionBasedRequestThread requestThread; private ConnectionBasedReplyThread replyThread; // for message-based (none-connection-based) mixes: private MessageReceiverThread receiverThread; private MessageSenderThread senderThread; private ClientReplyProvider clientReplyProvider; @Override public void constructor() { if (anonNode.ROUTING_MODE == RoutingMode.GLOBAL_ROUTING) throw new RuntimeException("this is a free route plug-in; ROUTING_MODE is set to GLOBAL_ROUTING -> will exit now"); this.bindAddress = settings.getPropertyAsInetAddress("GLOBAL_MIX_BIND_ADDRESS"); this.port = settings.getPropertyAsInt("GLOBAL_MIX_BIND_PORT"); this.backlog = settings.getPropertyAsInt("BACKLOG"); this.maxRequestLength = settings.getPropertyAsInt("MAX_REQUEST_LENGTH"); //this.maxReplyLength = settings.getPropertyAsInt("MAX_REPLY_LENGTH"); this.requestBufferSize = settings.getPropertyAsInt("REQUEST_BUFFER_SIZE"); this.replyBufferSize = settings.getPropertyAsInt("REPLY_BUFFER_SIZE"); this.expectedConnections = anonNode.EXPECTED_NUMBER_OF_USERS; //this.queueBlockSize = settings.getPropertyAsInt("QUEUE_BLOCK_SIZE"); this.channels = new Vector<ChannelData>(expectedConnections); this.newConnections = new Vector<ChannelData>(100); } @Override public void initialize() { } @Override public void begin() { try { this.serverSocket = new ServerSocket(port, backlog, bindAddress); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("could not open ServerSocket"); } System.out.println(anonNode +" listening on " +bindAddress +":" +port); if (anonNode.IS_CONNECTION_BASED) { this.acceptorThread = new ConnectionBasedAcceptorThread(); this.requestThread = new ConnectionBasedRequestThread(); if (anonNode.IS_DUPLEX) this.replyThread = new ConnectionBasedReplyThread(); this.acceptorThread.start(); this.requestThread.start(); if (anonNode.IS_DUPLEX) this.replyThread.start(); } else { // not connection-based this.receiverThread = new MessageReceiverThread(); if (anonNode.IS_DUPLEX) this.senderThread = new MessageSenderThread(); this.receiverThread.start(); if (anonNode.IS_DUPLEX) this.senderThread.start(); } } @Override public void setClientReplyProvider(ClientReplyProvider provider) { this.clientReplyProvider = provider; } private class MessageReceiverThread extends Thread { @Override public void run() { while (true) { try { Socket client = serverSocket.accept(); User user = userDatabase.generateUser(); ChannelData channelData = new ChannelData(user); userDatabase.addUser(channelData.user); InputStream in = client.getInputStream(); if (anonNode.IS_DUPLEX) { channelData.replyAddress = client.getInetAddress(); channelData.replyPort = Util.forceReadShort(in); } int len = Util.forceReadInt(in); if (len > maxRequestLength) { System.err.println("warning: user " +user +" sent a too large message"); client.close(); continue; } byte[] msg = Util.forceRead(in, len); client.close(); Request r = MixMessage.getInstanceRequest(msg, user); user.prevHopAddress = MixMessage.CLIENT; if (anonNode.DISPLAY_ROUTE_INFO) System.out.println("" +anonNode +" received message on layer1"); anonNode.putInRequestInputQueue(r); } catch (IOException e) { e.printStackTrace(); continue; } } } } private class MessageSenderThread extends Thread { @Override public void run() { while (true) { Reply[] replies = anonNode.getFromReplyOutputQueue(); for (Reply reply: replies) { try { assert reply != null; assert reply.getOwner() != null; //System.out.println("sende auf layer 0 fuer " +reply.getOwner().toString() +": " +Util.md5(reply.getByteMessage())); ChannelData channel = reply.getOwner().getAttachment(attachmentKey, ChannelData.class); assert channel != null; Socket client = new Socket(channel.replyAddress, channel.replyPort); if (anonNode.DISPLAY_ROUTE_INFO) System.out.println("" +anonNode +" sending reply on layer 1 to client (" +channel.replyAddress +":" +channel.replyPort +")"); client.getOutputStream().write(Util.intToByteArray(reply.getByteMessage().length)); client.getOutputStream().write(reply.getByteMessage()); client.getOutputStream().flush(); client.close(); } catch (IOException e) { System.err.println("warning: connection to " +reply.getOwner() +" lost"); e.printStackTrace(); continue; } } } } } private class ConnectionBasedAcceptorThread extends Thread { @Override public void run() { int counter = 0; while (true) { try { Socket client = serverSocket.accept(); if (++counter%100 == 0) System.out.println(counter +" connections"); User user = userDatabase.generateUser(); ChannelData channelData = new ChannelData(user); userDatabase.addUser(channelData.user); channelData.socket = client; channelData.inputStream = new BufferedInputStream(client.getInputStream(), requestBufferSize); if (anonNode.IS_DUPLEX) channelData.outputStream = new BufferedOutputStream(client.getOutputStream(), replyBufferSize); synchronized (acceptorThread) { newConnections.add(channelData); } } catch (IOException e) { e.printStackTrace(); continue; } } } } private synchronized void dropChannel(ChannelData ch) { channels.remove(ch); try { ch.inputStream.close(); ch.outputStream.close(); ch.socket.close(); } catch (IOException e) { e.printStackTrace(); } } private class ConnectionBasedRequestThread extends Thread { @Override public void run() { int maxReadsPerChannelInARow = settings.getPropertyAsInt("MAX_READS_IN_A_ROW"); int maxMessageBlockSize = anonNode.QUEUE_BLOCK_SIZE; while (true) { synchronized (acceptorThread) { // handle new connections if (newConnections.size() != 0) { channels.addAll(newConnections); newConnections = new Vector<ChannelData>(50); // TODO 50? } } Vector<Vector<Request>> newRequests = null; Vector<Request> newRequestsForCurrentChannel = null; for (ChannelData ch:channels) {// try to read data from existing channels try { for (int i=0; i<maxReadsPerChannelInARow; i++) { if (ch.requestLength == ChannelData.NOT_SET) { if (ch.inputStream.available() >= 4) { byte[] len = new byte[4]; int read = ch.inputStream.read(len); assert read == 4; // should not be different due to buffered stream; check anyways... ch.requestLength = Util.byteArrayToInt(len); if (ch.requestLength > maxRequestLength) { System.err.println("warning: user " +ch.user +" sent a too large message"); } } else { break; } } if (ch.requestLength != ChannelData.NOT_SET) { // length header already read if (ch.inputStream.available() >= ch.requestLength) { byte[] msg = new byte[ch.requestLength]; int read = ch.inputStream.read(msg); assert read == ch.requestLength; // should not be different due to buffered stream; check anyways... ch.requestLength = ChannelData.NOT_SET; Request r = MixMessage.getInstanceRequest(msg, ch.user); if (ch.user.prevHopAddress == Util.NOT_SET) ch.user.prevHopAddress = MixMessage.CLIENT; if (anonNode.DISPLAY_ROUTE_INFO) System.out.println("" +anonNode +" received message on layer1"); if (newRequestsForCurrentChannel == null) newRequestsForCurrentChannel = new Vector<Request>(maxReadsPerChannelInARow); newRequestsForCurrentChannel.add(r); } else { break; } } } if (newRequestsForCurrentChannel != null) { if (newRequests == null) newRequests = new Vector<Vector<Request>>(maxMessageBlockSize); newRequests.add(newRequestsForCurrentChannel); newRequestsForCurrentChannel = null; } } catch (IOException e) { e.printStackTrace(); dropChannel(ch); continue; } if (newRequests != null && newRequests.size() >= maxMessageBlockSize) { for (Vector<Request> requests:newRequests) anonNode.putInRequestInputQueue(requests.toArray(new Request[0])); // might block newRequests = new Vector<Vector<Request>>(maxMessageBlockSize); } } if (newRequests == null) { try {Thread.sleep(1);} catch (InterruptedException e) {continue;} // TODO: 1 ms adequate? continue; } for (Vector<Request> requests:newRequests) anonNode.putInRequestInputQueue(requests.toArray(new Request[0])); // might block } } } private class ConnectionBasedReplyThread extends Thread { @Override public void run() { if (anonNode.NUMBER_OF_MIXES == 1) { // all replies are for clients while (true) { Reply[] replies = anonNode.getFromReplyOutputQueue(); for (Reply reply: replies) writeReply(reply); } } else { // only replies not for other mixes are for clients (prevMixHandler decides) while (true) writeReply(clientReplyProvider.getReplyForClient()); } } private void writeReply(Reply reply) { try { assert reply != null; assert reply.getOwner() != null; //System.out.println("sende auf layer 0 fuer " +reply.getOwner().toString() +": " +Util.md5(reply.getByteMessage())); ChannelData channel = reply.getOwner().getAttachment(attachmentKey, ChannelData.class); assert channel != null; assert channel.inputStream != null; if (anonNode.DISPLAY_ROUTE_INFO) System.out.println("" +anonNode +" sending reply on layer 1 to client (" +channel.socket.getRemoteSocketAddress() +")"); channel.outputStream.write(Util.intToByteArray(reply.getByteMessage().length)); channel.outputStream.write(reply.getByteMessage()); channel.outputStream.flush(); } catch (IOException e) { System.err.println("warning: connection to " +reply.getOwner() +" lost"); e.printStackTrace(); System.exit(0); // TODO: disconnect etc } } } public class ChannelData extends UserAttachment { final static int NOT_SET = -2; BufferedInputStream inputStream; BufferedOutputStream outputStream; Socket socket; User user; int requestLength = NOT_SET; int replyLength = NOT_SET; InetAddress replyAddress; short replyPort; public ChannelData(User user) { super(user, attachmentKey); this.user = user; } } }