/******************************************************************************* * 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.socks_v0_001.multiplexer; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.security.SecureRandom; import java.util.concurrent.ConcurrentHashMap; import staticContent.framework.socket.socketInterfaces.StreamAnonSocket; import staticContent.framework.util.Util; import userGeneratedContent.testbedPlugIns.layerPlugIns.layer5application.socks_v0_001.Config; public class Multiplexer { private static SecureRandom rand = new SecureRandom(); //private String destAddress; //private int destPort; private StreamAnonSocket tunnelEntry; private ConcurrentHashMap<Integer, Socket> connections = new ConcurrentHashMap<Integer, Socket>(); // multiplexed connections private OutputStream toDemultiplexer; private InputStream fromDemultiplexer; private Object requestSynchronizer = new Object(); private Config config; /** * Multiplexes and forwards connections to destAddress:destPort. * Demultiplexes any data from destAddress:destPort. * DestAddress:destPort must be operated by Demultiplexer.java. * New connections (to be multiplexed) can be added with "addConnection()". * @param destAddress * @param destPort */ public Multiplexer(StreamAnonSocket anonSocket, Config config) { this.tunnelEntry = anonSocket; this.config = config; connect(); new ReplyForwarder().start(); } /** * Connects this multiplexer with the demultiplexer (through a tcp tunnel). */ private synchronized void connect() { try { this.toDemultiplexer = tunnelEntry.getOutputStream(); this.fromDemultiplexer = tunnelEntry.getInputStream(); } catch (IOException e) { System.out.println("socks-client: lost connection"); if (config.DEBUG) e.printStackTrace(); } System.out.println("client: multiplexed tunnel to mix established"); } /** Add new connection that shall be multiplexed. */ public void addConnection(Socket socketToMultiplex) { new RequestForwarder(socketToMultiplex).start(); } private void requestDisconnect(int id) { synchronized(requestSynchronizer) { // inform demultiplexer about disconnect; connection will be closed after the demultiplexer acknowledges the disconnect (see thread "ReplyForwarder" below) while (true) { try { toDemultiplexer.write(Util.intToByteArray(id), 0, 4); toDemultiplexer.write(Util.shortToByteArray(-1), 0, 2); // -1 means "DISCONNECT" toDemultiplexer.flush(); } catch (IOException e) { if (config.DEBUG) e.printStackTrace(); connect(); continue; } break; } } } /** * Forwards data from user applications through the tunnel (adds multiplex header). */ private class RequestForwarder extends Thread { byte[] buffer = new byte[config.BUFFER_SIZE]; Socket userApplication; InputStream fromUserApplication; int id; public RequestForwarder(Socket userApplication) { this.userApplication = userApplication; this.id = rand.nextInt(); connections.put(id, userApplication); } @Override public void run() { //System.out.println("Multiplexer: run started"); try { this.fromUserApplication = userApplication.getInputStream(); } catch (IOException e) { if (config.DEBUG) e.printStackTrace(); requestDisconnect(id); return; } while(true) { // try to read from user application: int readBytes; try { readBytes = fromUserApplication.read(buffer); //System.out.println("Multiplexer: read "+ readBytes +" bytes from application"); if (readBytes < 1) { requestDisconnect(id); return; } } catch (IOException e) { if (config.DEBUG) e.printStackTrace(); requestDisconnect(id); return; } // try to send to demultiplexer: while(true) { try { synchronized (requestSynchronizer) { // send data toDemultiplexer.write(Util.intToByteArray(id), 0, 4); // send id of this multiplexed stream toDemultiplexer.write(Util.shortToByteArray(readBytes), 0, 2); // send length of the data to come toDemultiplexer.write(buffer, 0, readBytes); // send the data toDemultiplexer.flush(); } } catch (IOException e) { System.out.println("lost connection to demultiplexer"); if (config.DEBUG) e.printStackTrace(); connect(); } break; } } } } /** * Forwards data from the tunnel to user applications (removes multiplex header). */ private class ReplyForwarder extends Thread { @Override public void run() { while (true) { int id; int len; byte[] message = null; try { id = Util.forceReadInt(fromDemultiplexer); // read id len = Util.forceReadShort(fromDemultiplexer); // read length if (len == -1) { // disconnect acknowledged -> close connection try { connections.get(id).close(); } catch (IOException e) {} connections.remove(id); } else { // no disconnect message = Util.forceRead(fromDemultiplexer, len); } } catch (IOException e) { // connection do demultiplexer lost System.out.println("lost connection to demultiplexer"); if (config.DEBUG) e.printStackTrace(); connect(); continue; } try { Socket s = connections.get(id); if (s == null || message == null) // already closed continue; OutputStream osToUserApplication = s.getOutputStream(); osToUserApplication.write(message); osToUserApplication.flush(); } catch (IOException e) { // disconnect handling is done by RequestForwarder -> read next message if (config.DEBUG) e.printStackTrace(); continue; } } } } }