/******************************************************************************* * 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.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; import java.util.Vector; import java.util.concurrent.ArrayBlockingQueue; import staticContent.framework.controller.SubImplementation; import staticContent.framework.message.MixMessage; import staticContent.framework.message.Reply; import staticContent.framework.message.Request; import staticContent.framework.userDatabase.DatabaseEventListener; import staticContent.framework.userDatabase.User; import staticContent.framework.util.Util; public class PrevMixHandler_TCP_RR_multiplexed_sync extends SubImplementation implements DatabaseEventListener, ClientReplyProvider { private int port; private InetAddress bindAddress; private ServerSocket serverSocket; private int maxRequestLength; private HashMap<User,Integer> thisToPrevMixIDs; private int requestBufferSize; private int replyBufferSize; private RequestThread requestThread; private ReplyThread replyThread; private AcceptorThread acceptorThread; private HashMap<Integer, MixConnection> connectionsMap; // used by ReplyThread private Vector<MixConnection> connections; // used by RequestThread private Vector<MixConnection> newConnections; // used for synchronization between RequestThread and ReplyThread private ArrayBlockingQueue<Reply> repliesForClients; @Override public void constructor() { this.bindAddress = settings.getPropertyAsInetAddress("GLOBAL_MIX_BIND_ADDRESS"); this.port = settings.getPropertyAsInt("GLOBAL_MIX_BIND_PORT") +1000; this.maxRequestLength = settings.getPropertyAsInt("MAX_REQUEST_LENGTH"); this.thisToPrevMixIDs = new HashMap<User,Integer>((int)Math.round((double)settings.getPropertyAsInt("MAX_CONNECTIONS") * 1.3d)); this.connectionsMap = new HashMap<Integer, MixConnection>(); this.connections = new Vector<MixConnection>(); this.newConnections = new Vector<MixConnection>(); this.acceptorThread = new AcceptorThread(); this.requestThread = new RequestThread(); if (anonNode.IS_DUPLEX) this.replyThread = new ReplyThread(); this.requestBufferSize = settings.getPropertyAsInt("MULTIPLEXED_REQUEST_BUFFER_SIZE"); this.replyBufferSize = settings.getPropertyAsInt("MULTIPLEXED_REPLY_BUFFER_SIZE"); this.repliesForClients = new ArrayBlockingQueue<Reply>(settings.getPropertyAsInt("GLOBAL_REPLY_OUTPUT_QUEUE_SIZE")); } @Override public void initialize() { } @Override public void begin() { this.acceptorThread.start(); this.requestThread.start(); if (anonNode.IS_DUPLEX) this.replyThread.start(); } private class AcceptorThread extends Thread { @Override public void run() { try { serverSocket = new ServerSocket(port, 5, bindAddress); System.out.println(anonNode + " bound to " +bindAddress + ":" +port); } catch (IOException e) { System.out.println(anonNode + " couldn't bind socket " +bindAddress + ":" +port); e.printStackTrace(); System.exit(1); } while (true) { try { Socket socket = serverSocket.accept(); socket.setKeepAlive(true); MixConnection con = new MixConnection(); con.previousMixInputStream = new BufferedInputStream(socket.getInputStream(), requestBufferSize); con.previousMixOutputStream = new BufferedOutputStream(socket.getOutputStream(), replyBufferSize); con.mixId = Util.forceReadInt(con.previousMixInputStream); // TODO: check certificate if (anonNode.mixList.getAddress(con.mixId) == null) { // TODO: reload (possibly updated) list from info-service... System.err.println("received connection from unknown host: " +socket.getInetAddress() +":" +socket.getPort()); socket.close(); continue; } synchronized (newConnections) { newConnections.add(con); } synchronized (connectionsMap) { connectionsMap.put(con.mixId, con); } System.out.println(anonNode +" connection accepted from " +socket.getInetAddress() +":" +socket.getPort()); } catch (IOException e) { System.err.println(anonNode +" connection attempt failed"); e.printStackTrace(); continue; } } } } private class RequestThread extends Thread { @Override public void run() { int maxReadsPerChannelInARow = settings.getPropertyAsInt("MAX_READS_IN_A_ROW_MIX"); int maxMessageBlockSize = anonNode.QUEUE_BLOCK_SIZE; while (true) { // receive messages from "prev" mixes synchronized (newConnections) { // handle new connections if (newConnections.size() != 0) { connections.addAll(newConnections); newConnections = new Vector<MixConnection>(); } } Vector<Vector<Request>> newRequests = null; Vector<Request> newRequestsForCurrentConnection = null; for (MixConnection con:connections) {// try to read data from existing connections try { for (int i=0; i<maxReadsPerChannelInARow; i++) { if (con.currentUserIdentifier == Util.NOT_SET) { // try to read id if (con.previousMixInputStream.available() >= 4) { byte[] id = new byte[4]; int read = con.previousMixInputStream.read(id); assert read == 4; // should not be different due to buffered stream; check anyways... con.currentUserIdentifier = Util.byteArrayToInt(id); User user = userDatabase.getUser(con.currentUserIdentifier); if (user == null) { user = userDatabase.generateUser(con.currentUserIdentifier); userDatabase.addUser(user); thisToPrevMixIDs.put(user, con.currentUserIdentifier); user.prevHopAddress = con.mixId; } con.currentUser = user; } else { break; } } if (con.currentRequestLength == Util.NOT_SET) { // try to read length-header if (con.previousMixInputStream.available() >= 4) { byte[] len = new byte[4]; int read = con.previousMixInputStream.read(len); assert read == 4; // should not be different due to buffered stream; check anyways... con.currentRequestLength = Util.byteArrayToInt(len); if (con.currentRequestLength < 1 || con.currentRequestLength > maxRequestLength) { System.out.println(anonNode +" wrong size for request received"); dropConncetion(con); continue; } } else { break; } } if (con.currentRequestLength != Util.NOT_SET) { // length header already read -> try to read message if (con.previousMixInputStream.available() >= con.currentRequestLength) { byte[] msg = new byte[con.currentRequestLength]; int read = con.previousMixInputStream.read(msg); assert read == con.currentRequestLength; // should not be different due to buffered stream; check anyways... //System.err.println("next mix received: id: " +con.currentUserIdentifier +"; msg-length: " +con.currentRequestLength +"; msg: " +Util.md5(msg)); con.currentUserIdentifier = Util.NOT_SET; con.currentRequestLength = Util.NOT_SET; Request r = MixMessage.getInstanceRequest(msg, con.currentUser); if (con.currentUser.prevHopAddress == Util.NOT_SET) con.currentUser.prevHopAddress = con.mixId; if (anonNode.DISPLAY_ROUTE_INFO) System.out.println("" +anonNode +" received request on layer 1 from prev mix (mix " +con.mixId +")"); assert con.currentUser != null; if (newRequestsForCurrentConnection == null) newRequestsForCurrentConnection = new Vector<Request>(maxReadsPerChannelInARow); newRequestsForCurrentConnection.add(r); } else { break; } } } if (newRequestsForCurrentConnection != null) { if (newRequests == null) newRequests = new Vector<Vector<Request>>(maxMessageBlockSize); newRequests.add(newRequestsForCurrentConnection); newRequestsForCurrentConnection = null; } } catch (IOException e) { e.printStackTrace(); dropConncetion(con); 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 ReplyThread extends Thread { @Override public void run() { while (true) { Reply[] replies = anonNode.getFromReplyOutputQueue(); MixConnection con = null; for (int i=0; i<replies.length; i++) { if (replies[i].nextHopAddress == MixMessage.CLIENT) { putInRepliesForClient(replies[i]); continue; } Integer id; synchronized (thisToPrevMixIDs) { id = thisToPrevMixIDs.get(replies[i].getOwner()); } if (id == null) { System.err.println(anonNode +" no id for " +replies[i].getOwner() +" available" ); continue; } try { synchronized (connectionsMap) { con = connectionsMap.get(replies[i].nextHopAddress); } if (con == null) { // no connection to that mix System.err.println("received reply with unknown next hop address: " +replies[i].nextHopAddress); continue; } if (anonNode.DISPLAY_ROUTE_INFO) System.out.println("" +anonNode +" sending reply on layer 1 to prev mix (mix " +con.mixId +")"); con.previousMixOutputStream.write(Util.intToByteArray(id)); con.previousMixOutputStream.write(Util.intToByteArray(replies[i].getByteMessage().length)); con.previousMixOutputStream.write(replies[i].getByteMessage()); con.previousMixOutputStream.flush(); } catch (IOException e) { System.out.println(anonNode +" connection to prev mix (" +con.mixId +") lost"); continue; } catch (NullPointerException e) { e.printStackTrace(); continue; } } } } private void putInRepliesForClient(Reply reply) { try { repliesForClients.put(reply); } catch (InterruptedException e) { e.printStackTrace(); putInRepliesForClient(reply); } } } @Override public Reply getReplyForClient() { try { return repliesForClients.take(); } catch (InterruptedException e) { e.printStackTrace(); return getReplyForClient(); } } @Override public void userAdded(User user) { } @Override public void userRemoved(User user) { thisToPrevMixIDs.remove(user); } private synchronized void dropConncetion(MixConnection con) { connections.remove(con); try { con.previousMixOutputStream.close(); con.previousMixInputStream.close(); con.previousMixSocket.close(); } catch (IOException e) { e.printStackTrace(); } } private class MixConnection { private int mixId; private Socket previousMixSocket; private BufferedInputStream previousMixInputStream; private BufferedOutputStream previousMixOutputStream; private int currentRequestLength = Util.NOT_SET; private int currentUserIdentifier = Util.NOT_SET; private User currentUser; } }