Package de.fhkn.in.uce.stun.server.connectionhandling

Source Code of de.fhkn.in.uce.stun.server.connectionhandling.HandleMessageTask

/*
* Copyright (c) 2012 Alexander Diener,
*
* 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 de.fhkn.in.uce.stun.server.connectionhandling;

import java.io.EOFException;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.fhkn.in.uce.stun.attribute.ChangeRequest;
import de.fhkn.in.uce.stun.attribute.OtherAddress;
import de.fhkn.in.uce.stun.attribute.XorMappedAddress;
import de.fhkn.in.uce.stun.header.STUNMessageClass;
import de.fhkn.in.uce.stun.header.STUNMessageMethod;
import de.fhkn.in.uce.stun.message.Message;
import de.fhkn.in.uce.stun.message.MessageReader;
import de.fhkn.in.uce.stun.message.MessageStaticFactory;

/**
* The {@link HandleMessageTask} handles STUN messages according to RFC 5780 to
* examine the behavior of a NAT-device. The server only handles TCP connections
* and instantiates connections to examine the filtering behavior of a NAT. For
* this purpose the stun server sends indications to the stun client because it
* is forbidden to send a response via a new connection.
*
* @author Alexander Diener (aldiener@htwg-konstanz.de)
*
*/
public final class HandleMessageTask implements Runnable {
    private static final Logger logger = LoggerFactory.getLogger(HandleMessageTask.class);
    private static final int TIMEOUT_IN_SECONDS = 10 * 1000;
    private final Socket socket;
    private final InetSocketAddress primaryAddress;
    private final InetSocketAddress secondaryAddress;
    private final MessageReader messageReader;

    private final int thirdPort;

    /**
     * Creates a {@link HandleMessageTask} to handle STUN messages over TCP.
     *
     * @param s
     *            the socket to read messages
     * @param primaryAddress
     *            the primary (local) address of the stun server
     * @param secondaryAddress
     *            the secondary/alternate (local) address of the stun server
     */
    public HandleMessageTask(final Socket s, final InetSocketAddress primaryAddress,
            final InetSocketAddress secondaryAddress) {
        this.socket = s;
        this.primaryAddress = primaryAddress;
        this.secondaryAddress = secondaryAddress;
        this.messageReader = MessageReader.createMessageReader();
        this.thirdPort = secondaryAddress.getPort() + 1;
    }

    @Override
    public void run() {
        while (this.socket.isConnected() && !this.socket.isClosed()) {
            try {
                final Message inMessage = this.receiveMessage();
                logger.debug("received message at local {}:{}", this.socket.getLocalAddress(), //$NON-NLS-1$
                        this.socket.getLocalPort());
                this.handleMessage(inMessage);
            } catch (final EOFException eofe) {
                // TODO why is this exception thrown?
            } catch (final Exception e) {
                logger.error(e.getMessage(), e);
                if (!this.socket.isClosed()) {
                    logger.debug("Closing socket"); //$NON-NLS-1$
                    try {
                        this.socket.close();
                    } catch (final IOException e1) {
                        // logger.error(e.getMessage(), e);
                    }
                }
                return;
            }
        }
    }

    private Message receiveMessage() throws Exception {
        return this.messageReader.readSTUNMessage(this.socket.getInputStream());
    }

    private final void handleMessage(final Message toHandle) throws Exception {
        if (toHandle.isMethod(STUNMessageMethod.BINDING)) {
            this.handleBindingMessage(toHandle);
        } else {
            logger.debug("Can not handle message with method {}", toHandle.getMessageMethod()); //$NON-NLS-1$
        }
    }

    private void handleBindingMessage(final Message toHandle) throws Exception {
        if (toHandle.isRequest() && toHandle.isMethod(STUNMessageMethod.BINDING)) {
            logger.debug("handling binding request"); //$NON-NLS-1$
            this.handleSimpleBindingRequest(toHandle);
        } else if (this.isPrimaryAddress() && toHandle.hasAttribute(ChangeRequest.class)) {
            final InetSocketAddress remoteAddress = new InetSocketAddress(this.socket.getInetAddress(),
                    this.socket.getPort());
            logger.debug("handling message with change request attribute"); //$NON-NLS-1$
            this.handleMessageWithChangeRequestAttribute(toHandle, remoteAddress);
        }
    }

    private void handleSimpleBindingRequest(final Message toHandle) throws Exception {
        final Message response = toHandle.buildSuccessResponse();
        final OtherAddress otherAddress = new OtherAddress(this.secondaryAddress);
        response.addAttribute(otherAddress);
        final XorMappedAddress clientAddress = this.getPublicClientAddressAsAttribute(toHandle);
        response.addAttribute(clientAddress);
        response.writeTo(this.socket.getOutputStream());
    }

    private void handleMessageWithChangeRequestAttribute(final Message toHandle, final InetSocketAddress remoteAddress)
            throws Exception {
        final ChangeRequest changeRequest = toHandle.getAttribute(ChangeRequest.class);
        logger.debug("Getting indication with change request flag = {}", changeRequest.getFlag()); //$NON-NLS-1$
        switch (changeRequest.getFlag()) {
        case ChangeRequest.CHANGE_IP_AND_PORT:
            this.sendIndicationViaNewSocket(new InetSocketAddress(this.secondaryAddress.getAddress(), this.thirdPort),
                    toHandle, remoteAddress);
            break;
        case ChangeRequest.CHANGE_PORT:
            this.sendIndicationViaNewSocket(new InetSocketAddress(this.primaryAddress.getAddress(), this.thirdPort),
                    toHandle, remoteAddress);
            break;
        case ChangeRequest.FLAGS_NOT_SET:
            // for checking connection dependent filtering, not part of RFC 5780
            // the server should try to establish a connection from the primary
            // address but this does not work under linux
            // this.socket.close();
            // logger.debug("socket to client closed");
            // this.waitForSocketClosed();
            // this.sendIndicationViaNewSocket(this.primaryAddress, toHandle,
            // remoteAddress);
            break;
        case ChangeRequest.CHANGE_IP:
            this.sendIndicationViaNewSocket(new InetSocketAddress(this.secondaryAddress.getAddress(),
                    this.primaryAddress.getPort()), toHandle, remoteAddress);
            break;
        default:
            break;
        }
    }

    private void sendIndicationViaNewSocket(final InetSocketAddress bindAddress, final Message message,
            final InetSocketAddress remoteAddress) {
        try {
            final Message indication = MessageStaticFactory.newSTUNMessageInstance(STUNMessageClass.INDICATION,
                    STUNMessageMethod.BINDING);
            indication.addAttribute(this.getPublicClientAddressAsAttribute(message));
            final Socket newSocket = new Socket();
            newSocket.setReuseAddress(true);
            logger.debug("Binding new socket to {}", bindAddress); //$NON-NLS-1$
            newSocket.bind(bindAddress);
            logger.debug("Connecting to {}", remoteAddress.toString()); //$NON-NLS-1$
            newSocket.connect(remoteAddress, TIMEOUT_IN_SECONDS);
            logger.debug("Wrtiting indication"); //$NON-NLS-1$
            indication.writeTo(newSocket.getOutputStream());
            newSocket.close();
        } catch (IOException e) {
            logger.error("Connection to client not successfully established: {}", e.getMessage()); //$NON-NLS-1$
        }
    }

    private boolean isPrimaryAddress() {
        return this.socket.getLocalAddress().equals(this.primaryAddress.getAddress())
                && this.socket.getLocalPort() == this.primaryAddress.getPort();
    }

    private XorMappedAddress getPublicClientAddressAsAttribute(final Message message) {
        XorMappedAddress clientAddress;
        final InetSocketAddress publicClientAddress = new InetSocketAddress(this.socket.getInetAddress(),
                this.socket.getPort());
        if (publicClientAddress.getAddress() instanceof Inet4Address) {
            clientAddress = new XorMappedAddress(publicClientAddress);
        } else {
            clientAddress = new XorMappedAddress(publicClientAddress, ByteBuffer.wrap(
                    message.getHeader().getTransactionId()).getInt());
        }
        return clientAddress;
    }
}
TOP

Related Classes of de.fhkn.in.uce.stun.server.connectionhandling.HandleMessageTask

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.