/******************************************************************************* * 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.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; import java.security.SecureRandom; import java.util.HashMap; import java.util.Vector; import java.util.concurrent.atomic.AtomicBoolean; import staticContent.framework.controller.SubImplementation; import staticContent.framework.message.MixMessage; import staticContent.framework.message.Reply; import staticContent.framework.message.Request; import staticContent.framework.routing.MixList; import staticContent.framework.userDatabase.DatabaseEventListener; import staticContent.framework.userDatabase.User; import staticContent.framework.util.Util; public class NextMixHandler_TCP_RR_multiplexed_sync extends SubImplementation implements DatabaseEventListener { private AtomicBoolean replyThreadWaiting = new AtomicBoolean(false); private RequestThread requestThread; private ReplyThread replyThread; private int requestBufferSize; private int replyBufferSize; private int maxReplyLength; private HashMap<User,Integer> thisToNextMixIDs; private HashMap<Integer,User> nextMixToThisIDs; private SecureRandom random = new SecureRandom(); private HashMap<Integer, MixConnection> connectionsMap; // used by RequestThread private Vector<MixConnection> connections; // used by ReplyThread private Vector<MixConnection> newConnections; // used for synchronization between RequestThread and ReplyThread @Override public void constructor() { this.requestThread = new RequestThread(); this.replyThread = new ReplyThread(); this.thisToNextMixIDs = new HashMap<User,Integer>((int)Math.round((double)anonNode.EXPECTED_NUMBER_OF_USERS * 1.3d)); this.nextMixToThisIDs = new HashMap<Integer,User>((int)Math.round((double)anonNode.EXPECTED_NUMBER_OF_USERS * 1.3d)); this.connectionsMap = new HashMap<Integer, MixConnection>(); this.connections = new Vector<MixConnection>(); this.newConnections = new Vector<MixConnection>(); this.requestBufferSize = settings.getPropertyAsInt("MULTIPLEXED_REQUEST_BUFFER_SIZE"); this.replyBufferSize = settings.getPropertyAsInt("MULTIPLEXED_REPLY_BUFFER_SIZE"); this.maxReplyLength = settings.getPropertyAsInt("MAX_REPLY_LENGTH"); } @Override public void initialize() { userDatabase.registerEventListener(this); } @Override public void begin() { // establish connections to all known mixes: MixList mixList = anonNode.mixList; for (int i=0; i<mixList.addresses.length; i++) if (mixList.mixIDs[i] != anonNode.PUBLIC_PSEUDONYM) // don't connect to yourself... connectTo(mixList.mixIDs[i]); this.requestThread.start(); if (anonNode.IS_DUPLEX) this.replyThread.start(); } private void connectTo(int mixId) { MixConnection con = null; synchronized (replyThreadWaiting) { MixList mixList = anonNode.mixList; con = new MixConnection(); con.mixId = mixId; con.nextMixAddress = mixList.addresses[mixId]; con.nextMixPort = mixList.ports[mixId] + 1000; System.out.println(anonNode +" trying to connect to next mix (" +con.nextMixAddress +":" +con.nextMixPort +")"); while (true) { // try to connect try { con.nextMixSocket = new Socket(); con.nextMixSocket.setKeepAlive(true); // permanent connection SocketAddress receiverAddress = new InetSocketAddress(con.nextMixAddress, con.nextMixPort); con.nextMixSocket.connect(receiverAddress); con.nextMixOutputStream = new BufferedOutputStream(con.nextMixSocket.getOutputStream(), requestBufferSize); con.nextMixInputStream = new BufferedInputStream(con.nextMixSocket.getInputStream(), replyBufferSize); con.nextMixOutputStream.write(Util.intToByteArray(anonNode.PUBLIC_PSEUDONYM)); con.nextMixOutputStream.flush(); //System.out.println("."); if (replyThreadWaiting.get() == true) replyThreadWaiting.notify(); break; // exit loop, when no IOException has occurred // (= connection is established successfully) } catch (Exception e) { // connection failed System.out.println(anonNode +" connection to next mix (" +con.nextMixAddress +":" +con.nextMixPort +") could not be established; trying to connect again"); //e.printStackTrace(); // TODO: remove try { Thread.sleep(1000); } catch (InterruptedException e1) { continue; } } } System.out.println(anonNode +" connection to next mix (" +con.nextMixAddress +":" +con.nextMixPort +") established"); } connectionsMap.put(con.mixId, con); if (anonNode.IS_DUPLEX) { synchronized (newConnections) { newConnections.add(con); } } } @Override public void userAdded(User user) { synchronized (nextMixToThisIDs) { while (true) { int idForNextMix = random.nextInt(); if (nextMixToThisIDs.get(idForNextMix) != null) continue; this.thisToNextMixIDs.put(user, idForNextMix); this.nextMixToThisIDs.put(idForNextMix, user); break; } } } @Override public void userRemoved(User user) { synchronized (nextMixToThisIDs) { this.nextMixToThisIDs.remove(thisToNextMixIDs.get(user)); this.thisToNextMixIDs.remove(user); } } private class RequestThread extends Thread { @Override public void run() { while (true) { Request[] requests = anonNode.getFromRequestOutputQueue(); MixConnection con = null; for (int i=0; i<requests.length; i++) { try { assert requests[i].nextHopAddress != Util.NOT_SET; con = connectionsMap.get(requests[i].nextHopAddress); if (con == null) { // no connection to that mix -> try to connect connectTo(requests[i].nextHopAddress); con = connectionsMap.get(requests[i].nextHopAddress); if (con == null) { // could not connect to that mix System.err.println("received message with unknown next hop address: " +requests[i].nextHopAddress); continue; } } if (anonNode.DISPLAY_ROUTE_INFO) System.out.println("" +anonNode +" sending request on layer 1 to next mix (mix " +con.mixId +"); " +Util.md5(requests[i].getByteMessage())); synchronized (nextMixToThisIDs) { con.nextMixOutputStream.write(Util.intToByteArray(thisToNextMixIDs.get(requests[i].getOwner()))); } con.nextMixOutputStream.write(Util.intToByteArray(requests[i].getByteMessage().length)); //System.out.println("req-len: " +requests[i].getByteMessage().length); // TODO: remove con.nextMixOutputStream.write(requests[i].getByteMessage()); con.nextMixOutputStream.flush(); } catch (IOException e) { System.out.println(anonNode +" connection to next mix (" +con.nextMixAddress +":" +con.nextMixPort +") lost"); connectTo(con.mixId); // reestablish connection continue; } catch (NullPointerException e) { e.printStackTrace(); continue; } } } } } private class ReplyThread 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 "next" mixes synchronized (newConnections) { // handle new connections if (newConnections.size() != 0) { connections.addAll(newConnections); newConnections = new Vector<MixConnection>(); } } Vector<Vector<Reply>> newReplies = null; Vector<Reply> newRepliesForCurrentConnection = 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.nextMixInputStream.available() >= 4) { byte[] id = new byte[4]; int read = con.nextMixInputStream.read(id); assert read == 4; // should not be different due to buffered stream; check anyways... con.currentUserIdentifier = Util.byteArrayToInt(id); synchronized (nextMixToThisIDs) { con.currentUser = nextMixToThisIDs.get(con.currentUserIdentifier); } if (con.currentUser == null) { System.out.println(anonNode +" received reply from (" +con.nextMixAddress +":" +con.nextMixPort +") for an unknown channel id"); dropConncetion(con); continue; } } else { break; } } if (con.currentReplyLength == Util.NOT_SET) { // try to read length-header if (con.nextMixInputStream.available() >= 4) { byte[] len = new byte[4]; int read = con.nextMixInputStream.read(len); assert read == 4; // should not be different due to buffered stream; check anyways... con.currentReplyLength = Util.byteArrayToInt(len); //System.out.println("rep-len: " +con.currentReplyLength); // TODO: remove if (con.currentReplyLength > maxReplyLength) { System.err.println("warning: mix " +con.mixId +" sent a too large message"); dropConncetion(con); continue; } else if (con.currentReplyLength < 1) { System.err.println("warning: mix " +con.mixId +" sent a too small message"); dropConncetion(con); continue; } } else { break; } } if (con.currentReplyLength != Util.NOT_SET) { // length header already read -> try to read message if (con.nextMixInputStream.available() >= con.currentReplyLength) { byte[] msg = new byte[con.currentReplyLength]; int read = con.nextMixInputStream.read(msg); assert read == con.currentReplyLength; // should not be different due to buffered stream; check anyways... con.currentUserIdentifier = Util.NOT_SET; con.currentReplyLength = Util.NOT_SET; if (anonNode.DISPLAY_ROUTE_INFO) System.out.println("" +anonNode +" received reply on layer 1 from next mix (mix " +con.mixId +")"); Reply r = MixMessage.getInstanceReply(msg, con.currentUser); if (newRepliesForCurrentConnection == null) newRepliesForCurrentConnection = new Vector<Reply>(maxReadsPerChannelInARow); newRepliesForCurrentConnection.add(r); } else { break; } } } if (newRepliesForCurrentConnection != null) { if (newReplies == null) newReplies = new Vector<Vector<Reply>>(maxMessageBlockSize); newReplies.add(newRepliesForCurrentConnection); newRepliesForCurrentConnection = null; } } catch (IOException e) { e.printStackTrace(); dropConncetion(con); continue; } if (newReplies != null && newReplies.size() >= maxMessageBlockSize) { for (Vector<Reply> replies:newReplies) anonNode.putInReplyInputQueue(replies.toArray(new Reply[0])); // might block newReplies = new Vector<Vector<Reply>>(maxMessageBlockSize); } } if (newReplies == null) { try {Thread.sleep(1);} catch (InterruptedException e) {continue;} // TODO: 1 ms adequate? continue; } for (Vector<Reply> requests:newReplies) anonNode.putInReplyInputQueue(requests.toArray(new Reply[0])); // might block } } } private synchronized void dropConncetion(MixConnection con) { connections.remove(con); try { con.nextMixInputStream.close(); con.nextMixOutputStream.close(); con.nextMixSocket.close(); } catch (IOException e) { e.printStackTrace(); } } private class MixConnection { private int mixId; private InetAddress nextMixAddress; private int nextMixPort; private Socket nextMixSocket; // TODO: ssl private BufferedInputStream nextMixInputStream; private BufferedOutputStream nextMixOutputStream; private int currentUserIdentifier = Util.NOT_SET; private int currentReplyLength = Util.NOT_SET; private User currentUser; } }