/* * Conditions Of Use * * This software was developed by employees of the National Institute of * Standards and Technology (NIST), an agency of the Federal Government. * Pursuant to title 15 Untied States Code Section 105, works of NIST * employees are not subject to copyright protection in the United States * and are considered to be in the public domain. As a result, a formal * license is not needed to use the software. * * This software is provided by NIST as a service and is expressly * provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT * AND DATA ACCURACY. NIST does not warrant or make any representations * regarding the use of the software or the results thereof, including but * not limited to the correctness, accuracy, reliability or usefulness of * the software. * * Permission to use this software is contingent upon your acceptance * of the terms of this agreement * * . * */ package gov.nist.javax.sip.stack; import gov.nist.core.CommonLogger; import gov.nist.core.InternalErrorHandler; import gov.nist.core.LogWriter; import gov.nist.core.StackLogger; import gov.nist.javax.sip.header.CSeq; import gov.nist.javax.sip.header.CallID; import gov.nist.javax.sip.header.ContentLength; import gov.nist.javax.sip.header.From; import gov.nist.javax.sip.header.RequestLine; import gov.nist.javax.sip.header.StatusLine; import gov.nist.javax.sip.header.To; import gov.nist.javax.sip.header.Via; import gov.nist.javax.sip.message.SIPMessage; import gov.nist.javax.sip.parser.NioPipelineParser; import java.io.IOException; import java.net.InetAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.text.ParseException; import java.util.HashMap; import javax.net.ssl.SSLException; public class NioTcpMessageChannel extends ConnectionOrientedMessageChannel { private static StackLogger logger = CommonLogger .getLogger(NioTcpMessageChannel.class); protected static HashMap<SocketChannel, NioTcpMessageChannel> channelMap = new HashMap<SocketChannel, NioTcpMessageChannel>(); protected SocketChannel socketChannel; protected long lastActivityTimeStamp; NioPipelineParser nioParser = null; public static NioTcpMessageChannel create( NioTcpMessageProcessor nioTcpMessageProcessor, SocketChannel socketChannel) throws IOException { NioTcpMessageChannel retval = channelMap.get(socketChannel); if (retval == null) { retval = new NioTcpMessageChannel(nioTcpMessageProcessor, socketChannel); channelMap.put(socketChannel, retval); } retval.messageProcessor = nioTcpMessageProcessor; retval.myClientInputStream = socketChannel.socket().getInputStream(); return retval; } public static NioTcpMessageChannel getMessageChannel(SocketChannel socketChannel) { return channelMap.get(socketChannel); } public static void putMessageChannel(SocketChannel socketChannel, NioTcpMessageChannel nioTcpMessageChannel) { channelMap.put(socketChannel, nioTcpMessageChannel); } public static void removeMessageChannel(SocketChannel socketChannel) { channelMap.remove(socketChannel); } private static final int BUF_SIZE = 4096; private final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUF_SIZE); public void readChannel() { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("NioTcpMessageChannel::readChannel"); } this.isRunning = true; try { int nbytes = this.socketChannel.read(byteBuffer); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("Read " + nbytes + " from socketChannel"); } boolean streamError = nbytes == -1; if(streamError) throw new IOException("End-of-stream read (-1). " + "This is usually an indication we are stuck and it is better to disconnect."); // This prevents us from getting stuck in a selector thread spinloop when socket is constantly ready for reading but there are no bytes. if(nbytes == 0) throw new IOException("The socket is giving us empty TCP packets. " + "This is usually an indication we are stuck and it is better to disconnect."); byteBuffer.flip(); byte[] msg = new byte[byteBuffer.remaining()]; byteBuffer.get(msg); byteBuffer.clear(); // Otherwise just add the bytes to queue addBytes(msg); lastActivityTimeStamp = System.currentTimeMillis(); } catch (Exception ex) { // https://java.net/jira/browse/JSIP-464 make sure to close connections on all exceptions to avoid the stack to hang // Terminate the message. if(ex instanceof IOException && !(ex instanceof SSLException)) { try { nioParser.addBytes("\r\n\r\n".getBytes("UTF-8")); } catch (Exception e) { // InternalErrorHandler.handleException(e); } } try { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("I/O Issue closing sock " + ex.getMessage() + "myAddress:myport " + myAddress + ":" + myPort + ", remoteAddress:remotePort " + peerAddress + ":" + peerPort); logger.logStackTrace(); } close(true, false); } catch (Exception ex1) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug("Issue closing the socket " + ex1); } } // catch (Exception ex) { // InternalErrorHandler.handleException(ex, logger); // } } // TLS will override here to add decryption protected void addBytes(byte[] bytes) throws Exception { nioParser.addBytes(bytes); } protected NioTcpMessageChannel(NioTcpMessageProcessor nioTcpMessageProcessor, SocketChannel socketChannel) throws IOException { super(nioTcpMessageProcessor.getSIPStack()); super.myClientInputStream = socketChannel.socket().getInputStream(); try { this.peerAddress = socketChannel.socket().getInetAddress(); this.peerPort = socketChannel.socket().getPort(); this.socketChannel = socketChannel; super.mySock = socketChannel.socket(); // messages that we write out to him. nioParser = new NioPipelineParser(sipStack, this, this.sipStack.getMaxMessageSize()); this.peerProtocol = nioTcpMessageProcessor.transport; lastActivityTimeStamp = System.currentTimeMillis(); super.key = MessageChannel.getKey(peerAddress, peerPort, nioTcpMessageProcessor.transport); myAddress = nioTcpMessageProcessor.getIpAddress().getHostAddress(); myPort = nioTcpMessageProcessor.getPort(); } finally { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("Done creating NioTcpMessageChannel " + this + " socketChannel = " +socketChannel); } } } public NioTcpMessageChannel(InetAddress inetAddress, int port, SIPTransactionStack sipStack, NioTcpMessageProcessor nioTcpMessageProcessor) throws IOException { super(sipStack); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("NioTcpMessageChannel::NioTcpMessageChannel: " + inetAddress.getHostAddress() + ":" + port); } try { messageProcessor = nioTcpMessageProcessor; // Take a cached socket to the destination, if none create a new one and cache it socketChannel = nioTcpMessageProcessor.nioHandler.createOrReuseSocket( inetAddress, port); peerAddress = socketChannel.socket().getInetAddress(); peerPort = socketChannel.socket().getPort(); super.mySock = socketChannel.socket(); peerProtocol = getTransport(); nioParser = new NioPipelineParser(sipStack, this, this.sipStack.getMaxMessageSize()); putMessageChannel(socketChannel, this); lastActivityTimeStamp = System.currentTimeMillis(); super.key = MessageChannel.getKey(peerAddress, peerPort, getTransport()); myAddress = nioTcpMessageProcessor.getIpAddress().getHostAddress(); myPort = nioTcpMessageProcessor.getPort(); } finally { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("NioTcpMessageChannel::NioTcpMessageChannel: Done creating NioTcpMessageChannel " + this + " socketChannel = " + socketChannel); } } } public SocketChannel getSocketChannel() { return socketChannel; } @Override protected void close(boolean removeSocket, boolean stopKeepAliveTask) { try { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("Closing NioTcpMessageChannel " + this + " socketChannel = " + socketChannel); } removeMessageChannel(socketChannel); if(socketChannel != null) { socketChannel.close(); } if(nioParser != null) { nioParser.close(); } this.isRunning = false; if(removeSocket) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("Removing NioTcpMessageChannel " + this + " socketChannel = " + socketChannel); } ((NioTcpMessageProcessor) this.messageProcessor).nioHandler.removeSocket(socketChannel); ((ConnectionOrientedMessageProcessor) this.messageProcessor).remove(this); } if(stopKeepAliveTask) { cancelPingKeepAliveTimeoutTaskIfStarted(); } } catch (IOException e) { logger.logError("Problem occured while closing", e); } } /** * get the transport string. * * @return "tcp" in this case. */ public String getTransport() { return this.messageProcessor.transport; } /** * Send message to whoever is connected to us. Uses the topmost via address * to send to. * * @param msg * is the message to send. * @param isClient */ protected void sendMessage(byte[] msg, boolean isClient) throws IOException { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("sendMessage isClient = " + isClient + " this = " + this); } lastActivityTimeStamp = System.currentTimeMillis(); NIOHandler nioHandler = ((NioTcpMessageProcessor) messageProcessor).nioHandler; if(this.socketChannel != null && this.socketChannel.isConnected() && this.socketChannel.isOpen()) { nioHandler.putSocket(NIOHandler.makeKey(this.peerAddress, this.peerPort), this.socketChannel); } sendTCPMessage(msg, this.peerAddress, this.peerPort, isClient); } /** * Send a message to a specified address. * * @param message * Pre-formatted message to send. * @param receiverAddress * Address to send it to. * @param receiverPort * Receiver port. * @throws IOException * If there is a problem connecting or sending. */ public void sendMessage(byte message[], InetAddress receiverAddress, int receiverPort, boolean retry) throws IOException { sendTCPMessage(message, receiverAddress, receiverPort, retry); } /** * Send a message to a specified address. * * @param message * Pre-formatted message to send. * @param receiverAddress * Address to send it to. * @param receiverPort * Receiver port. * @throws IOException * If there is a problem connecting or sending. */ public void sendTCPMessage(byte message[], InetAddress receiverAddress, int receiverPort, boolean retry) throws IOException { if (message == null || receiverAddress == null) { logger.logError("receiverAddress = " + receiverAddress); throw new IllegalArgumentException("Null argument"); } lastActivityTimeStamp = System.currentTimeMillis(); if (peerPortAdvertisedInHeaders <= 0) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("receiver port = " + receiverPort + " for this channel " + this + " key " + key); } if (receiverPort <= 0) { // if port is 0 we assume the default port for TCP this.peerPortAdvertisedInHeaders = 5060; } else { this.peerPortAdvertisedInHeaders = receiverPort; } if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("2.Storing peerPortAdvertisedInHeaders = " + peerPortAdvertisedInHeaders + " for this channel " + this + " key " + key); } } NIOHandler nioHandler = ((NioTcpMessageProcessor) messageProcessor).nioHandler; SocketChannel sock = nioHandler.sendBytes(this.messageProcessor .getIpAddress(), receiverAddress, receiverPort, this.messageProcessor.transport, message, retry, this); if (sock != socketChannel && sock != null) { if (socketChannel != null) { if (logger.isLoggingEnabled(LogWriter.TRACE_WARN)) { logger .logWarning("[2] Old socket different than new socket on channel " + key + socketChannel + " " + sock); logger.logStackTrace(); logger.logWarning("Old socket local ip address " + socketChannel.socket().getLocalSocketAddress()); logger.logWarning("Old socket remote ip address " + socketChannel.socket().getRemoteSocketAddress()); logger.logWarning("New socket local ip address " + sock.socket().getLocalSocketAddress()); logger.logWarning("New socket remote ip address " + sock.socket().getRemoteSocketAddress()); } close(false, false); // we can call socketChannel.close() directly but we better use the inherited method socketChannel = sock; putMessageChannel(socketChannel, this); onNewSocket(message); } if (socketChannel != null) { if (logger.isLoggingEnabled(LogWriter.TRACE_WARN)) { logger .logWarning("There was no exception for the retry mechanism so we keep going " + key); } } socketChannel = sock; } } public void onNewSocket(byte[] message) { } /** * Exception processor for exceptions detected from the parser. (This is * invoked by the parser when an error is detected). * * @param sipMessage * -- the message that incurred the error. * @param ex * -- parse exception detected by the parser. * @param header * -- header that caused the error. * @throws ParseException * Thrown if we want to reject the message. */ public void handleException(ParseException ex, SIPMessage sipMessage, Class hdrClass, String header, String message) throws ParseException { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug("Parsing Exception: " , ex); // Log the bad message for later reference. if ((hdrClass != null) && (hdrClass.equals(From.class) || hdrClass.equals(To.class) || hdrClass.equals(CSeq.class) || hdrClass.equals(Via.class) || hdrClass.equals(CallID.class) || hdrClass.equals(ContentLength.class) || hdrClass.equals(RequestLine.class) || hdrClass .equals(StatusLine.class))) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("Encountered Bad Message \n" + sipMessage.toString()); } // JvB: send a 400 response for requests (except ACK) // Currently only UDP, @todo also other transports String msgString = sipMessage.toString(); if (!msgString.startsWith("SIP/") && !msgString.startsWith("ACK ")) { if (socketChannel != null) { if (logger.isLoggingEnabled(LogWriter.TRACE_ERROR)) { logger .logError("Malformed mandatory headers: closing socket! :" + socketChannel.toString()); } try { socketChannel.close(); } catch (IOException ie) { if (logger.isLoggingEnabled(LogWriter.TRACE_ERROR)) { logger.logError("Exception while closing socket! :" + socketChannel.toString() + ":" + ie.toString()); } } } } throw ex; } else { sipMessage.addUnparsed(header); } } /** * Equals predicate. * * @param other * is the other object to compare ourselves to for equals */ public boolean equals(Object other) { if (!this.getClass().equals(other.getClass())) return false; else { NioTcpMessageChannel that = (NioTcpMessageChannel) other; if (this.socketChannel != that.socketChannel) return false; else return true; } } /** * TCP Is not a secure protocol. */ public boolean isSecure() { return false; } public long getLastActivityTimestamp() { return lastActivityTimeStamp; } }