com.adito.agent.client.tunneling.LocalTunnelServer.java Source code

Java tutorial

Introduction

Here is the source code for com.adito.agent.client.tunneling.LocalTunnelServer.java

Source

/*
*  Adito
*
*  Copyright (C) 2003-2006 3SP LTD. All Rights Reserved
*
*  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 2 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, write to the Free Software
*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

package com.adito.agent.client.tunneling;

import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Enumeration;
import java.util.Vector;

import com.maverick.multiplex.Channel;
import com.maverick.multiplex.channels.LocalForwardingChannel;
import com.adito.agent.client.Agent;
import com.adito.agent.client.util.IOStreamConnectorListener;
import com.adito.agent.client.util.TunnelConfiguration;

/**
 * Sets up maintains a single listening server socket to support <i>Local
 * Tunnels</i>. 
 * <p>
 * This listener will accept connections from either only localhost or from
 * any host (depending on how the tunnel was configured) and forward them
 * to the Adito server the Agent is connected to.
 * <p>
 * When constructed, two {@link IOStreamConnectorListener}s must be provided.
 * These are used to monitor events such as when data travels 
 * through the listener in either direction.
 * <p>
 * You must also provide a {@link TunnelConfiguration}. The listener is configured from
 * the details obtained from this object. The listener will be running on
 * the port specified in {@link TunnelConfiguration#getSourcePort()}.
 * <p>
 * Before any connections can be made to this listener, it must be started.
 * Invoked the {@link #start()} method. The listener may be stopped at
 * any time using the {@link #stop()} method.
 * <p>
 * NOTE UDP tunneling does not currently work.
 */
public class LocalTunnelServer implements LocalTunnelConnectionEventListener {

    // Private instance variables
    private Agent vpn;
    private ServerSocket server;
    private Thread thread;
    private boolean listening;
    private Vector activeTunnels;
    private IOStreamConnectorListener txListener;
    private IOStreamConnectorListener rxListener;
    private String ticket;
    private TunnelConfiguration listeningSocketConfiguration;
    private long dataLastTransferred;
    private DatagramSocket datagramSocket;
    private boolean stopping = false;
    private int totalTunnels;
    private Vector listeners;

    // #ifdef DEBUG
    static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
            .getLog(LocalTunnelServer.class);
    // #endif

    /**
     * Constructor.
     *
     * @param vpn vpn
     * @param txListener transmit listener
     * @param rxListener receive listener
     * @param listeningSocketConfiguration tunnel to configure listener from.
     */
    public LocalTunnelServer(Agent vpn, IOStreamConnectorListener txListener, IOStreamConnectorListener rxListener,
            TunnelConfiguration listeningSocketConfiguration) {
        this.vpn = vpn;
        this.listeningSocketConfiguration = listeningSocketConfiguration;
        this.txListener = txListener;
        this.rxListener = rxListener;
        listeners = new Vector();
        this.activeTunnels = new Vector();
        dataLastTransferred = System.currentTimeMillis();
    }

    /**
     * Add a listener
     * 
     * @param listener
     */
    public void addListener(LocalTunnelServerListener listener) {
        if (listener != null)
            listeners.addElement(listener);
    }

    /**
     * Get the number of tunnels that are currently active on this listener.
     * 
     * @return active tunnel count
     */
    public int getActiveTunnelCount() {
        return activeTunnels.size();
    }

    /**
     * Get the total number of tunnels that have ever been connected to
     * this listener.
     * 
     * @return total tunnel count
     */
    public int getTotalTunnelCount() {
        return totalTunnels;
    }

    /**
     * Get the tunnel that was used to configured this listener.
     * 
     * @return tunnel
     */
    public TunnelConfiguration getTunnel() {
        return listeningSocketConfiguration;
    }

    /**
     * Get the resource ID of the tunnel. This will not be known until the
     * tunnel has been started.
     * 
     * @return resource ID
     */
    public int getId() {
        return listeningSocketConfiguration.getId();
    }

    /**
     * Get the port on which the listener is running. This should be the
     * same as the source port specified in the {@link TunnelConfiguration} provided during
     * construction.
     * 
     * @return local port
     */
    public int getLocalPort() {
        return (server == null) ? (datagramSocket == null ? -1 : datagramSocket.getLocalPort())
                : server.getLocalPort();
    }

    /**
     * Get if this listener is currently accepting connections. Note, it may
     * be possible for a listener <b>not</b> to be listening but to still
     * be running ({@link #isRunning()}. This may happen while the listener
     * is shutting down.
     * 
     * @return listening
     */
    public boolean isListening() {
        return listening;
    }

    /**
     * Get the time (in MS since Jan. 1 1970) data was last transferred over
     * this listener.
     * 
     * @return time (in MS since Jan. 1 1970) data was last transferred over
     * this listener.
     */
    public long getDataLastTransferredTime() {
        return dataLastTransferred;
    }

    /**
     * Get if this listener is currently running. Note, it may
     * be possible for a listener to be running but to <b>not</b>
     * be listenign ({@link #isListening()}. This may happen while the listener
     * is shutting down.
     *  
     * @return running
     */
    public boolean isRunning() {
        return (thread != null) && thread.isAlive();
    }

    /**
     * Get the ticket assigned to this listener by the Adito server.
     * This will be <code>null</code> until the listener has been started.
     *  
     * @return ticket
     */
    public String getTicket() {
        return ticket;
    }

    /**
     * Start listening for incoming connections to this listener. When
     * successful, this method will return immediately. 
        
     * @throws IOException
     */
    public void start() throws IOException {
        if (stopping) {
            throw new IOException("Local forwarding is currently stopping.");
        }
        if (isListening()) {
            throw new IOException("Local forwarding is already listening.");
        }

        dataLastTransferred = System.currentTimeMillis();

        // #ifdef DEBUG
        if (listeningSocketConfiguration.isPermanent()) {
            log.info("Starting permanent listening socket on port " + listeningSocketConfiguration.getSourcePort()); //$NON-NLS-1$
        } else {
            log.info("Starting temporary listening socket on port " + listeningSocketConfiguration.getSourcePort()); //$NON-NLS-1$            
        }
        // #endif

        /* Bind server socket */
        if (listeningSocketConfiguration.getTransport().equals(TunnelConfiguration.UDP_TUNNEL)) {
            // #ifdef DEBUG
            log.info("Creating UDP server socket on port " + listeningSocketConfiguration.getSourcePort()); //$NON-NLS-1$
            // #endif
            datagramSocket = new DatagramSocket(listeningSocketConfiguration.getSourcePort());
        } else {
            // #ifdef DEBUG
            if (listeningSocketConfiguration.getSourcePort() == 0)
                log.info("Creating TCP server socket random port"); //$NON-NLS-1$
            else
                log.info("Creating TCP server socket on port " + listeningSocketConfiguration.getSourcePort()); //$NON-NLS-1$
            // #endif
            /* If the specified port is 0 then ServerSocket will select the
             * next free port. We then need to store the port actually used
             * back into the configuration so application launching can
             * work.
             */
            boolean resetPort = listeningSocketConfiguration.getSourcePort() == 0;
            server = new ServerSocket(listeningSocketConfiguration.getSourcePort(), 50,
                    InetAddress.getByName(getAddressToBind()));
            if (resetPort) {
                // #ifdef DEBUG
                log.info("Chosen port " + server.getLocalPort()); //$NON-NLS-1$
                // #endif
                listeningSocketConfiguration.setSourcePort(server.getLocalPort());
            }
        }

        fireLocalTunnelServerStarted();
        thread = new Thread(new Runnable() {
            public void run() {
                tunnelTCP();
            }
        });
        thread.setDaemon(true);
        thread.setName("SocketListener " + getAddressToBind() + ":" //$NON-NLS-1$//$NON-NLS-2$
                + String.valueOf(listeningSocketConfiguration.getSourcePort()));
        thread.start();
    }

    /**
     * Get if this listener is currently stopping
     * 
     * @return stopping
     */
    public boolean isStopping() {
        return stopping;
    }

    /**
     * Stop accepting connections to this listener. All current connections
     * will be severed.
     * <p>
     * When this method exist, the listener will no longer be listening,
     * ({@link #isListening()})  but may still be running ({@link #isRunning()}).
     */
    public void stop() {
        try {
            stopping = true;

            // #ifdef DEBUG
            if (listeningSocketConfiguration.isPermanent()) {
                log.info("Stopping permanent listening socket on port " //$NON-NLS-1$
                        + listeningSocketConfiguration.getSourcePort());
            } else {
                log.info("Stopping temporary listening socket on port " //$NON-NLS-1$
                        + listeningSocketConfiguration.getSourcePort());
            }
            // #endif

            /* Stop all of the tunnels */
            /******
             * LDP - Why close all the tunnels??? This will kill connections open that are active. 
             */
            //            for (Enumeration e = new Vector(activeTunnels).elements(); e.hasMoreElements();) {
            //                ((LocalTunnelConnection) e.nextElement()).stop();
            //            }

            /* Close the server socket to prevent new connections */
            if (server != null) {
                // #ifdef DEBUG
                log.info("Closing server socket on port " + server.getLocalPort());
                // #endif 
                server.close();
            }
        } catch (IOException ioe) {
        }

        server = null;
        thread = null;
        listening = false;
        fireLocalTunnelServerStopped();
        stopping = false;
    }

    /* (non-Javadoc)
     * @see java.lang.Runnable#run()
     */
    public void run() {
        tunnelTCP();
    }

    private Channel openChannel(TunnelConfiguration conf) throws IOException {

        try {
            LocalForwardingChannel channel = new LocalForwardingChannel(conf.getDestinationHost(),
                    conf.getDestinationPort());
            vpn.getConnection().openChannel(channel);
            return channel;
        } catch (Exception e) {
            throw new IOException("Failed to open direct-tcpip channel to " + conf.getDestinationHost() + ":"
                    + conf.getDestinationPort());
        }
    }

    void tunnelTCP() {
        stopping = false;
        Socket socket = null;
        try {

            listening = true;

            while (listening) {
                try {
                    socket = server.accept();
                    if (!listening || (socket == null)) {
                        break;
                    }

                    try {
                        // Open an SSL tunnel and connect the socket to the tunnel
                        LocalTunnelConnection vpntunnel = new LocalTunnelConnection(this,
                                openChannel(listeningSocketConfiguration), socket, getTunnel(), txListener,
                                rxListener);
                        vpntunnel.addListener(this);
                        vpntunnel.start();

                    } catch (Throwable ex) {
                        // #ifdef DEBUG
                        log.info(
                                Messages.getString("LocalTunnelConnectionListener.failedToConnectTunnelingRequest"), //$NON-NLS-1$
                                ex);
                        // #endif
                        try {
                            socket.close();
                        } catch (IOException ioe) {
                        }

                        if (listeningSocketConfiguration.isTemporarySingleConnect()) {
                            throw ex;
                        }
                    }

                    if (listeningSocketConfiguration.isTemporarySingleConnect()) {
                        // #ifdef DEBUG
                        log.info(Messages.getString("LocalTunnelConnectionListener.notAcceptingMoreAsTemp")); //$NON-NLS-1$
                        // #endif
                        break;
                    }
                } catch (IOException ioe) {
                    // #ifdef DEBUG
                    log.info(Messages.getString("LocalTunnelConnectionListener.failedToConnectTunnelingRequest"), //$NON-NLS-1$
                            ioe);
                    // #endif
                }
            }
        } catch (Throwable ex) {
            if (!stopping) {
                // #ifdef DEBUG
                log.info(Messages.getString("LocalTunnelConnectionListener.connectionListenerThreadFailed"), ex); //$NON-NLS-1$
                // #endif
                stop();
            }
        }
    }

    void fireLocalTunnelServerStopped() {
        for (Enumeration e = listeners.elements(); e.hasMoreElements();) {
            ((LocalTunnelServerListener) e.nextElement()).localTunnelStopped(this);
        }
    }

    void fireLocalTunnelServerStarted() {
        for (Enumeration e = listeners.elements(); e.hasMoreElements();) {
            ((LocalTunnelServerListener) e.nextElement()).localTunnelServerStarted(this);
        }
    }

    void fireLocalTunnelDataTransferred(byte[] buf, int count, boolean sent) {
        for (Enumeration e = listeners.elements(); e.hasMoreElements();) {
            ((LocalTunnelServerListener) e.nextElement()).localTunnelDataTransferred(this, buf, count, sent);
        }
    }

    void fireActiveTunnelStarted(LocalTunnelConnection activeTunnel) {
        for (Enumeration e = listeners.elements(); e.hasMoreElements();) {
            ((LocalTunnelServerListener) e.nextElement()).localTunnelConnectionStarted(this, activeTunnel);
        }
    }

    void fireActiveTunnelDataTransferred(LocalTunnelConnection activeTunnel, byte[] buf, int count, boolean sent) {
        for (Enumeration e = listeners.elements(); e.hasMoreElements();) {
            ((LocalTunnelServerListener) e.nextElement()).localTunnelConnectionDataTransferred(this, activeTunnel,
                    buf, count, sent);
        }
    }

    void fireActiveTunnelStopped(LocalTunnelConnection activeTunnel) {
        for (Enumeration e = listeners.elements(); e.hasMoreElements();) {
            ((LocalTunnelServerListener) e.nextElement()).localTunnelConnectionStopped(this, activeTunnel);
        }
    }

    String getAddressToBind() {
        if (listeningSocketConfiguration.getSourceInterface() != null
                && !listeningSocketConfiguration.getSourceInterface().equals("")) { //$NON-NLS-1$
            return listeningSocketConfiguration.getSourceInterface();
        } else {
            return "0.0.0.0";
        }
    }

    public void localTunnelConnectionStarted(LocalTunnelConnection tunnel) {
        synchronized (activeTunnels) {
            totalTunnels++;
            activeTunnels.addElement(tunnel);
            fireActiveTunnelStarted(tunnel);
        }
    }

    public void localTunnelConnectionStopped(LocalTunnelConnection tunnel) {
        synchronized (activeTunnels) {
            if (!stopping)
                activeTunnels.removeElement(tunnel);
            fireActiveTunnelStopped(tunnel);
        }
    }

    public void localTunnelConnectionDataTransferred(LocalTunnelConnection tunnel, byte[] buffer, int count,
            boolean sent) {
        dataLastTransferred = System.currentTimeMillis();
        fireActiveTunnelDataTransferred(tunnel, buffer, count, sent);
        fireLocalTunnelDataTransferred(buffer, count, sent);
    }
}