/** @author [email protected] */ public abstract class ConnectionOrientedMessageChannel extends MessageChannel implements SIPMessageListener, Runnable, RawMessageChannel { private static StackLogger logger = CommonLogger.getLogger(ConnectionOrientedMessageChannel.class); protected SIPTransactionStack sipStack; protected Socket mySock; protected PipelinedMsgParser myParser; protected String key; protected InputStream myClientInputStream; // just to pass to thread. // Set here on initialization to avoid thread leak. See issue 266 protected boolean isRunning = true; protected boolean isCached; protected Thread mythread; protected String myAddress; protected int myPort; protected InetAddress peerAddress; // This is the port and adress that we will find in the headers of the messages from the peer protected int peerPortAdvertisedInHeaders = -1; protected String peerAddressAdvertisedInHeaders; protected int peerPort; protected String peerProtocol; private volatile long lastKeepAliveReceivedTime; private SIPStackTimerTask pingKeepAliveTimeoutTask; private Semaphore keepAliveSemaphore; private long keepAliveTimeout; public ConnectionOrientedMessageChannel(SIPTransactionStack sipStack) { this.sipStack = sipStack; this.keepAliveTimeout = sipStack.getReliableConnectionKeepAliveTimeout(); if (keepAliveTimeout > 0) { keepAliveSemaphore = new Semaphore(1); } } /** Returns "true" as this is a reliable transport. */ public boolean isReliable() { return true; } /** Close the message channel. */ public void close() { close(true, true); } protected abstract void close(boolean removeSocket, boolean stopKeepAliveTask); /** * Get my SIP Stack. * * @return The SIP Stack for this message channel. */ public SIPTransactionStack getSIPStack() { return sipStack; } /** * get the address of the client that sent the data to us. * * @return Address of the client that sent us data that resulted in this channel being created. */ public String getPeerAddress() { if (peerAddress != null) { return peerAddress.getHostAddress(); } else return getHost(); } protected InetAddress getPeerInetAddress() { return peerAddress; } public String getPeerProtocol() { return this.peerProtocol; } /** * Return a formatted message to the client. We try to re-connect with the peer on the other end * if possible. * * @param sipMessage Message to send. * @throws IOException If there is an error sending the message */ public void sendMessage(final SIPMessage sipMessage) throws IOException { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG) && !sipMessage.isNullRequest()) { logger.logDebug( "sendMessage:: " + sipMessage.getFirstLine() + " cseq method = " + sipMessage.getCSeq().getMethod()); } for (MessageProcessor messageProcessor : getSIPStack().getMessageProcessors()) { if (messageProcessor.getIpAddress().getHostAddress().equals(this.getPeerAddress()) && messageProcessor.getPort() == this.getPeerPort() && messageProcessor.getTransport().equalsIgnoreCase(this.getPeerProtocol())) { Runnable processMessageTask = new Runnable() { public void run() { try { processMessage((SIPMessage) sipMessage.clone()); } catch (Exception ex) { if (logger.isLoggingEnabled(ServerLogger.TRACE_ERROR)) { logger.logError("Error self routing message cause by: ", ex); } } } }; getSIPStack().getSelfRoutingThreadpoolExecutor().execute(processMessageTask); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug("Self routing message"); return; } } byte[] msg = sipMessage.encodeAsBytes(this.getTransport()); long time = System.currentTimeMillis(); // need to store the peerPortAdvertisedInHeaders in case the response has an rport (ephemeral) // that failed to retry on the regular via port // for responses, no need to store anything for subsequent requests. if (peerPortAdvertisedInHeaders <= 0) { if (sipMessage instanceof SIPResponse) { SIPResponse sipResponse = (SIPResponse) sipMessage; Via via = sipResponse.getTopmostVia(); if (via.getRPort() > 0) { if (via.getPort() <= 0) { // if port is 0 we assume the default port for TCP this.peerPortAdvertisedInHeaders = 5060; } else { this.peerPortAdvertisedInHeaders = via.getPort(); } if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "1.Storing peerPortAdvertisedInHeaders = " + peerPortAdvertisedInHeaders + " for via port = " + via.getPort() + " via rport = " + via.getRPort() + " and peer port = " + peerPort + " for this channel " + this + " key " + key); } } } } // JvB: also retry for responses, if the connection is gone we should // try to reconnect this.sendMessage(msg, sipMessage instanceof SIPRequest); // message was sent without any exception so let's set set port and // address before we feed it to the logger sipMessage.setRemoteAddress(this.peerAddress); sipMessage.setRemotePort(this.peerPort); sipMessage.setLocalAddress(this.getMessageProcessor().getIpAddress()); sipMessage.setLocalPort(this.getPort()); if (logger.isLoggingEnabled(ServerLogger.TRACE_MESSAGES)) logMessage(sipMessage, peerAddress, peerPort, time); } protected abstract void sendMessage(byte[] msg, boolean b) throws IOException; public void processMessage(SIPMessage sipMessage, InetAddress address) { this.peerAddress = address; try { processMessage(sipMessage); } catch (Exception e) { if (logger.isLoggingEnabled(ServerLog.TRACE_ERROR)) { logger.logError("ERROR processing self routing", e); } } } /** * Gets invoked by the parser as a callback on successful message parsing (i.e. no parser errors). * * @param sipMessage Message to process (this calls the application for processing the message). * <p>Jvb: note that this code is identical to TCPMessageChannel, refactor some day */ public void processMessage(SIPMessage sipMessage) throws Exception { try { if (sipMessage.getFrom() == null || sipMessage.getTo() == null || sipMessage.getCallId() == null || sipMessage.getCSeq() == null || sipMessage.getViaHeaders() == null) { if (logger.isLoggingEnabled()) { String badmsg = sipMessage.encode(); logger.logError("bad message " + badmsg); logger.logError(">>> Dropped Bad Msg"); } return; } sipMessage.setRemoteAddress(this.peerAddress); sipMessage.setRemotePort(this.getPeerPort()); sipMessage.setLocalAddress(this.getMessageProcessor().getIpAddress()); sipMessage.setLocalPort(this.getPort()); // Issue 3: https://telestax.atlassian.net/browse/JSIP-3 sipMessage.setPeerPacketSourceAddress(this.peerAddress); sipMessage.setPeerPacketSourcePort(this.peerPort); ViaList viaList = sipMessage.getViaHeaders(); // For a request // first via header tells where the message is coming from. // For response, this has already been recorded in the outgoing // message. if (sipMessage instanceof SIPRequest) { Via v = (Via) viaList.getFirst(); // the peer address and tag it appropriately. Hop hop = sipStack.addressResolver.resolveAddress(v.getHop()); this.peerProtocol = v.getTransport(); // if(peerPortAdvertisedInHeaders <= 0) { int hopPort = v.getPort(); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "hop port = " + hopPort + " for request " + sipMessage + " for this channel " + this + " key " + key); } if (hopPort <= 0) { // if port is 0 we assume the default port for TCP this.peerPortAdvertisedInHeaders = 5060; } else { this.peerPortAdvertisedInHeaders = hopPort; } if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "3.Storing peerPortAdvertisedInHeaders = " + peerPortAdvertisedInHeaders + " for this channel " + this + " key " + key); } // } // may be needed to reconnect, when diff than peer address if (peerAddressAdvertisedInHeaders == null) { peerAddressAdvertisedInHeaders = hop.getHost(); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "3.Storing peerAddressAdvertisedInHeaders = " + peerAddressAdvertisedInHeaders + " for this channel " + this + " key " + key); } } try { if (mySock != null) { // selfrouting makes socket = null // https://jain-sip.dev.java.net/issues/show_bug.cgi?id=297 this.peerAddress = mySock.getInetAddress(); } // Check to see if the received parameter matches // the peer address and tag it appropriately. // JvB: dont do this. It is both costly and incorrect // Must set received also when it is a FQDN, regardless // whether // it resolves to the correct IP address // InetAddress sentByAddress = // InetAddress.getByName(hop.getHost()); // JvB: if sender added 'rport', must always set received boolean hasRPort = v.hasParameter(Via.RPORT); if (!hasRPort && v.getPort() != peerPort) { // https://github.com/RestComm/jain-sip/issues/79 if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "setting rport since viaPort " + v.getPort() + " different than peerPacketSourcePort " + peerPort + " so that the response can be routed back"); } hasRPort = true; } if (hasRPort || !hop.getHost().equals(this.peerAddress.getHostAddress())) { v.setParameter(Via.RECEIVED, this.peerAddress.getHostAddress()); } // @@@ hagai // JvB: technically, may only do this when Via already // contains // rport v.setParameter(Via.RPORT, Integer.toString(this.peerPort)); } catch (java.text.ParseException ex) { InternalErrorHandler.handleException(ex); } // Use this for outgoing messages as well. if (!this.isCached && mySock != null) { // self routing makes // mySock=null // https://jain-sip.dev.java.net/issues/show_bug.cgi?id=297 this.isCached = true; int remotePort = ((java.net.InetSocketAddress) mySock.getRemoteSocketAddress()).getPort(); String key = IOHandler.makeKey(mySock.getInetAddress(), remotePort); if (this.messageProcessor instanceof NioTcpMessageProcessor) { // https://java.net/jira/browse/JSIP-475 don't use iohandler in case of NIO // communications of the socket will leak in the iohandler sockettable ((NioTcpMessageProcessor) this.messageProcessor) .nioHandler.putSocket(key, mySock.getChannel()); } else { sipStack.ioHandler.putSocket(key, mySock); } // since it can close the socket it needs to be after the mySock usage otherwise // it the socket will be disconnected and NPE will be thrown in some edge cases ((ConnectionOrientedMessageProcessor) this.messageProcessor).cacheMessageChannel(this); } } // Foreach part of the request header, fetch it and process it long receptionTime = System.currentTimeMillis(); // if (sipMessage instanceof SIPRequest) { // This is a request - process the request. SIPRequest sipRequest = (SIPRequest) sipMessage; // Create a new sever side request processor for this // message and let it handle the rest. if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("----Processing Message---"); } if (logger.isLoggingEnabled(ServerLogger.TRACE_MESSAGES)) { sipStack.serverLogger.logMessage( sipMessage, this.getPeerHostPort().toString(), this.messageProcessor.getIpAddress().getHostAddress() + ":" + this.messageProcessor.getPort(), false, receptionTime); } // Check for reasonable size - reject message // if it is too long. if (sipStack.getMaxMessageSize() > 0 && sipRequest.getSize() + (sipRequest.getContentLength() == null ? 0 : sipRequest.getContentLength().getContentLength()) > sipStack.getMaxMessageSize()) { SIPResponse sipResponse = sipRequest.createResponse(SIPResponse.MESSAGE_TOO_LARGE); byte[] resp = sipResponse.encodeAsBytes(this.getTransport()); this.sendMessage(resp, false); throw new Exception("Message size exceeded"); } String sipVersion = ((SIPRequest) sipMessage).getRequestLine().getSipVersion(); if (!sipVersion.equals("SIP/2.0")) { SIPResponse versionNotSupported = ((SIPRequest) sipMessage) .createResponse(Response.VERSION_NOT_SUPPORTED, "Bad SIP version " + sipVersion); this.sendMessage(versionNotSupported.encodeAsBytes(this.getTransport()), false); throw new Exception("Bad version "); } String method = ((SIPRequest) sipMessage).getMethod(); String cseqMethod = ((SIPRequest) sipMessage).getCSeqHeader().getMethod(); if (!method.equalsIgnoreCase(cseqMethod)) { SIPResponse sipResponse = sipRequest.createResponse(SIPResponse.BAD_REQUEST); byte[] resp = sipResponse.encodeAsBytes(this.getTransport()); this.sendMessage(resp, false); throw new Exception("Bad CSeq method" + sipMessage + " method " + method); } // Stack could not create a new server request interface. // maybe not enough resources. ServerRequestInterface sipServerRequest = sipStack.newSIPServerRequest(sipRequest, this); if (sipServerRequest != null) { try { sipServerRequest.processRequest(sipRequest, this); } finally { if (sipServerRequest instanceof SIPTransaction) { SIPServerTransaction sipServerTx = (SIPServerTransaction) sipServerRequest; if (!sipServerTx.passToListener()) ((SIPTransaction) sipServerRequest).releaseSem(); } } } else { if (sipStack.sipMessageValve == null) { // Allow message valves to nullify messages without error SIPResponse response = sipRequest.createResponse(Response.SERVICE_UNAVAILABLE); RetryAfter retryAfter = new RetryAfter(); // Be a good citizen and send a decent response code back. try { retryAfter.setRetryAfter((int) (10 * (Math.random()))); response.setHeader(retryAfter); this.sendMessage(response); } catch (Exception e) { // IGNore } if (logger.isLoggingEnabled()) logger.logWarning("Dropping message -- could not acquire semaphore"); } } } else { SIPResponse sipResponse = (SIPResponse) sipMessage; // JvB: dont do this // if (sipResponse.getStatusCode() == 100) // sipResponse.getTo().removeParameter("tag"); try { sipResponse.checkHeaders(); } catch (ParseException ex) { if (logger.isLoggingEnabled()) logger.logError("Dropping Badly formatted response message >>> " + sipResponse); return; } // This is a response message - process it. // Check the size of the response. // If it is too large dump it silently. if (sipStack.getMaxMessageSize() > 0 && sipResponse.getSize() + (sipResponse.getContentLength() == null ? 0 : sipResponse.getContentLength().getContentLength()) > sipStack.getMaxMessageSize()) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug("Message size exceeded"); return; } ServerResponseInterface sipServerResponse = sipStack.newSIPServerResponse(sipResponse, this); if (sipServerResponse != null) { try { if (sipServerResponse instanceof SIPClientTransaction && !((SIPClientTransaction) sipServerResponse).checkFromTag(sipResponse)) { if (logger.isLoggingEnabled()) logger.logError("Dropping response message with invalid tag >>> " + sipResponse); return; } sipServerResponse.processResponse(sipResponse, this); } finally { if (sipServerResponse instanceof SIPTransaction && !((SIPTransaction) sipServerResponse).passToListener()) { // Note that the semaphore is released in event // scanner if the // request is actually processed by the Listener. ((SIPTransaction) sipServerResponse).releaseSem(); } } } else { logger.logWarning( "Application is blocked -- could not acquire semaphore -- dropping response"); } } } finally { } } /** * This gets invoked when thread.start is called from the constructor. Implements a message loop - * reading the tcp connection and processing messages until we are done or the other end has * closed. */ public void run() { Pipeline hispipe = null; // Create a pipeline to connect to our message parser. hispipe = new Pipeline( myClientInputStream, sipStack.readTimeout, ((SIPTransactionStack) sipStack).getTimer()); // Create a pipelined message parser to read and parse // messages that we write out to him. myParser = new PipelinedMsgParser(sipStack, this, hispipe, this.sipStack.getMaxMessageSize()); // Start running the parser thread. myParser.processInput(); // bug fix by Emmanuel Proulx int bufferSize = 4096; ((ConnectionOrientedMessageProcessor) this.messageProcessor).useCount++; this.isRunning = true; try { while (true) { try { byte[] msg = new byte[bufferSize]; int nbytes = myClientInputStream.read(msg, 0, bufferSize); // no more bytes to read... if (nbytes == -1) { hispipe.write("\r\n\r\n".getBytes("UTF-8")); try { if (sipStack.maxConnections != -1) { synchronized (messageProcessor) { ((ConnectionOrientedMessageProcessor) this.messageProcessor).nConnections--; messageProcessor.notify(); } } hispipe.close(); close(); } catch (IOException ioex) { } return; } hispipe.write(msg, 0, nbytes); } catch (IOException ex) { // Terminate the message. try { hispipe.write("\r\n\r\n".getBytes("UTF-8")); } catch (Exception e) { // InternalErrorHandler.handleException(e); } try { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug("IOException closing sock " + ex); try { if (sipStack.maxConnections != -1) { synchronized (messageProcessor) { ((ConnectionOrientedMessageProcessor) this.messageProcessor).nConnections--; messageProcessor.notify(); } } close(); hispipe.close(); } catch (IOException ioex) { } } catch (Exception ex1) { // Do nothing. } return; } catch (Exception ex) { InternalErrorHandler.handleException(ex, logger); } } } finally { this.isRunning = false; ((ConnectionOrientedMessageProcessor) this.messageProcessor).remove(this); ((ConnectionOrientedMessageProcessor) this.messageProcessor).useCount--; // parser could be null if the socket was closed by the remote end already if (myParser != null) { myParser.close(); } } } protected void uncache() { if (isCached && !isRunning) { ((ConnectionOrientedMessageProcessor) this.messageProcessor).remove(this); } } /** * Get an identifying key. This key is used to cache the connection and re-use it if necessary. */ public String getKey() { if (this.key != null) { return this.key; } else { this.key = MessageChannel.getKey(this.peerAddress, this.peerPort, getTransport()); return this.key; } } /** * Get the host to assign to outgoing messages. * * @return the host to assign to the via header. */ public String getViaHost() { return myAddress; } /** * Get the port for outgoing messages sent from the channel. * * @return the port to assign to the via header. */ public int getViaPort() { return myPort; } /** * Get the port of the peer to whom we are sending messages. * * @return the peer port. */ public int getPeerPort() { return peerPort; } public int getPeerPacketSourcePort() { return this.peerPort; } public InetAddress getPeerPacketSourceAddress() { return this.peerAddress; } /* * (non-Javadoc) * @see gov.nist.javax.sip.parser.SIPMessageListener#sendSingleCLRF() */ public void sendSingleCLRF() throws Exception { lastKeepAliveReceivedTime = System.currentTimeMillis(); if (mySock != null && !mySock.isClosed()) { sendMessage("\r\n".getBytes("UTF-8"), false); } synchronized (this) { if (isRunning) { if (keepAliveTimeout > 0) { rescheduleKeepAliveTimeout(keepAliveTimeout); } } } } public void cancelPingKeepAliveTimeoutTaskIfStarted() { if (pingKeepAliveTimeoutTask != null && pingKeepAliveTimeoutTask.getSipTimerTask() != null) { try { keepAliveSemaphore.acquire(); } catch (InterruptedException e) { logger.logError("Couldn't acquire keepAliveSemaphore"); return; } try { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "~~~ cancelPingKeepAliveTimeoutTaskIfStarted for MessageChannel(key=" + key + "), clientAddress=" + peerAddress + ", clientPort=" + peerPort + ", timeout=" + keepAliveTimeout + ")"); } sipStack.getTimer().cancel(pingKeepAliveTimeoutTask); } finally { keepAliveSemaphore.release(); } } } public void setKeepAliveTimeout(long keepAliveTimeout) { if (keepAliveTimeout < 0) { cancelPingKeepAliveTimeoutTaskIfStarted(); } if (keepAliveTimeout == 0) { keepAliveTimeout = messageProcessor.getSIPStack().getReliableConnectionKeepAliveTimeout(); } if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "~~~ setKeepAliveTimeout for MessageChannel(key=" + key + "), clientAddress=" + peerAddress + ", clientPort=" + peerPort + ", timeout=" + keepAliveTimeout + ")"); } this.keepAliveTimeout = keepAliveTimeout; if (keepAliveSemaphore == null) { keepAliveSemaphore = new Semaphore(1); } boolean isKeepAliveTimeoutTaskScheduled = pingKeepAliveTimeoutTask != null; if (isKeepAliveTimeoutTaskScheduled && keepAliveTimeout > 0) { rescheduleKeepAliveTimeout(keepAliveTimeout); } } public long getKeepAliveTimeout() { return keepAliveTimeout; } public void rescheduleKeepAliveTimeout(long newKeepAliveTimeout) { // long now = System.currentTimeMillis(); // long lastKeepAliveReceivedTimeOrNow = lastKeepAliveReceivedTime == 0 ? now : // lastKeepAliveReceivedTime; // // long newScheduledTime = lastKeepAliveReceivedTimeOrNow + newKeepAliveTimeout; StringBuilder methodLog = new StringBuilder(); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { methodLog.append( "~~~ rescheduleKeepAliveTimeout for MessageChannel(key=" + key + "), clientAddress=" + peerAddress + ", clientPort=" + peerPort + ", timeout=" + keepAliveTimeout + "): newKeepAliveTimeout="); if (newKeepAliveTimeout == Long.MAX_VALUE) { methodLog.append("Long.MAX_VALUE"); } else { methodLog.append(newKeepAliveTimeout); } // methodLog.append(", lastKeepAliveReceivedTimeOrNow="); // methodLog.append(lastKeepAliveReceivedTimeOrNow); // methodLog.append(", newScheduledTime="); // methodLog.append(newScheduledTime); } // long delay = newScheduledTime > now ? newScheduledTime - now : 1; try { keepAliveSemaphore.acquire(); } catch (InterruptedException e) { logger.logWarning("Couldn't acquire keepAliveSemaphore"); return; } try { if (pingKeepAliveTimeoutTask == null) { pingKeepAliveTimeoutTask = new KeepAliveTimeoutTimerTask(); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { methodLog.append(", scheduling pingKeepAliveTimeoutTask to execute after "); methodLog.append(keepAliveTimeout / 1000); methodLog.append(" seconds"); logger.logDebug(methodLog.toString()); } sipStack.getTimer().schedule(pingKeepAliveTimeoutTask, keepAliveTimeout); } else { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "~~~ cancelPingKeepAliveTimeout for MessageChannel(key=" + key + "), clientAddress=" + peerAddress + ", clientPort=" + peerPort + ", timeout=" + keepAliveTimeout + ")"); } sipStack.getTimer().cancel(pingKeepAliveTimeoutTask); pingKeepAliveTimeoutTask = new KeepAliveTimeoutTimerTask(); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { methodLog.append(", scheduling pingKeepAliveTimeoutTask to execute after "); methodLog.append(keepAliveTimeout / 1000); methodLog.append(" seconds"); logger.logDebug(methodLog.toString()); } sipStack.getTimer().schedule(pingKeepAliveTimeoutTask, keepAliveTimeout); } } finally { keepAliveSemaphore.release(); } } class KeepAliveTimeoutTimerTask extends SIPStackTimerTask { public void runTask() { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "~~~ Starting processing of KeepAliveTimeoutEvent( " + peerAddress.getHostAddress() + "," + peerPort + ")..."); } close(true, true); if (sipStack instanceof SipStackImpl) { for (Iterator<SipProviderImpl> it = ((SipStackImpl) sipStack).getSipProviders(); it.hasNext(); ) { SipProviderImpl nextProvider = (SipProviderImpl) it.next(); SipListener sipListener = nextProvider.getSipListener(); ListeningPoint[] listeningPoints = nextProvider.getListeningPoints(); for (ListeningPoint listeningPoint : listeningPoints) { if (sipListener != null && sipListener instanceof SipListenerExt // making sure that we don't notify each listening point but only the one on which // the timeout happened && listeningPoint.getIPAddress().equalsIgnoreCase(myAddress) && listeningPoint.getPort() == myPort && listeningPoint.getTransport().equalsIgnoreCase(getTransport())) { ((SipListenerExt) sipListener) .processIOException( new IOExceptionEventExt( nextProvider, Reason.KeepAliveTimeout, myAddress, myPort, peerAddress.getHostAddress(), peerPort, getTransport())); } } } } else { SipListener sipListener = sipStack.getSipListener(); if (sipListener instanceof SipListenerExt) { ((SipListenerExt) sipListener) .processIOException( new IOExceptionEventExt( this, Reason.KeepAliveTimeout, myAddress, myPort, peerAddress.getHostAddress(), peerPort, getTransport())); } } } } }
/** * This is a stack abstraction for TCP connections. This abstracts a stream of parsed messages. The * SIP sipStack starts this from the main SIPStack class for each connection that it accepts. It * starts a message parser in its own thread and talks to the message parser via a pipe. The message * parser calls back via the parseError or processMessage functions that are defined as part of the * SIPMessageListener interface. * * @see gov.nist.javax.sip.parser.PipelinedMsgParser * @author M. Ranganathan <br> * @version 1.2 $Revision: 1.83 $ $Date: 2010-12-02 22:44:53 $ */ public class TCPMessageChannel extends ConnectionOrientedMessageChannel { private static StackLogger logger = CommonLogger.getLogger(TCPMessageChannel.class); protected OutputStream myClientOutputStream; protected TCPMessageChannel(SIPTransactionStack sipStack) { super(sipStack); } /** * Constructor - gets called from the SIPStack class with a socket on accepting a new client. All * the processing of the message is done here with the sipStack being freed up to handle new * connections. The sock input is the socket that is returned from the accept. Global data that is * shared by all threads is accessible in the Server structure. * * @param sock Socket from which to read and write messages. The socket is already connected (was * created as a result of an accept). * @param sipStack Ptr to SIP Stack */ protected TCPMessageChannel( Socket sock, SIPTransactionStack sipStack, TCPMessageProcessor msgProcessor, String threadName) throws IOException { super(sipStack); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("creating new TCPMessageChannel "); logger.logStackTrace(); } mySock = sock; peerAddress = mySock.getInetAddress(); myAddress = msgProcessor.getIpAddress().getHostAddress(); myClientInputStream = mySock.getInputStream(); myClientOutputStream = mySock.getOutputStream(); mythread = new Thread(this); mythread.setDaemon(true); mythread.setName(threadName); this.peerPort = mySock.getPort(); this.key = MessageChannel.getKey(peerAddress, peerPort, "TCP"); this.myPort = msgProcessor.getPort(); // Bug report by Vishwashanti Raj Kadiayl super.messageProcessor = msgProcessor; // Can drop this after response is sent potentially. mythread.start(); } /** * Constructor - connects to the given inet address. Acknowledgement -- Lamine Brahimi (IBM * Zurich) sent in a bug fix for this method. A thread was being uncessarily created. * * @param inetAddr inet address to connect to. * @param sipStack is the sip sipStack from which we are created. * @throws IOException if we cannot connect. */ protected TCPMessageChannel( InetAddress inetAddr, int port, SIPTransactionStack sipStack, TCPMessageProcessor messageProcessor) throws IOException { super(sipStack); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("creating new TCPMessageChannel "); logger.logStackTrace(); } this.peerAddress = inetAddr; this.peerPort = port; this.myPort = messageProcessor.getPort(); this.peerProtocol = "TCP"; this.myAddress = messageProcessor.getIpAddress().getHostAddress(); // Bug report by Vishwashanti Raj Kadiayl this.key = MessageChannel.getKey(peerAddress, peerPort, "TCP"); super.messageProcessor = messageProcessor; } /** Close the message channel. */ public void close(boolean removeSocket, boolean stopKeepAliveTask) { isRunning = false; // we need to close everything because the socket may be closed by the other end // like in LB scenarios sending OPTIONS and killing the socket after it gets the response if (mySock != null) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug("Closing socket " + key); try { mySock.close(); mySock = null; } catch (IOException ex) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug("Error closing socket " + ex); } } if (myParser != null) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug("Closing my parser " + myParser); myParser.close(); } // no need to close myClientInputStream since myParser.close() above will do it if (myClientOutputStream != null) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug("Closing client output stream " + myClientOutputStream); try { myClientOutputStream.close(); } catch (IOException ex) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug("Error closing client output stream" + ex); } } if (removeSocket) { // remove the "tcp:" part of the key to cleanup the ioHandler hashmap String ioHandlerKey = key.substring(4); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug("Closing TCP socket " + ioHandlerKey); // Issue 358 : remove socket and semaphore on close to avoid leaking sipStack.ioHandler.removeSocket(ioHandlerKey); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("Closing message Channel (key = " + key + ")" + this); } cancelPingKeepAliveTimeoutTaskIfStarted(); } else { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { String ioHandlerKey = key.substring(4); logger.logDebug( "not removing socket key from the cached map since it has already been updated by the iohandler.sendBytes " + ioHandlerKey); } } } /** * get the transport string. * * @return "tcp" in this case. */ public String getTransport() { return "TCP"; } /** * 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 synchronized void sendMessage(byte[] msg, boolean isClient) throws IOException { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("sendMessage isClient = " + isClient); } Socket sock = null; IOException problem = null; try { sock = this.sipStack.ioHandler.sendBytes( this.messageProcessor.getIpAddress(), this.peerAddress, this.peerPort, this.peerProtocol, msg, isClient, this); } catch (IOException any) { problem = any; logger.logWarning( "Failed to connect " + this.peerAddress + ":" + this.peerPort + " but trying the advertised port=" + this.peerPortAdvertisedInHeaders + " if it's different than the port we just failed on"); } if (sock == null) { // http://java.net/jira/browse/JSIP-362 If we couldn't connect to the host, try // the advertised host and port as failsafe if (peerAddressAdvertisedInHeaders != null && peerPortAdvertisedInHeaders > 0) { if (logger.isLoggingEnabled(LogWriter.TRACE_WARN)) { logger.logWarning( "Couldn't connect to peerAddress = " + peerAddress + " peerPort = " + peerPort + " key = " + key + " retrying on peerPortAdvertisedInHeaders " + peerPortAdvertisedInHeaders); } InetAddress address = InetAddress.getByName(peerAddressAdvertisedInHeaders); sock = this.sipStack.ioHandler.sendBytes( this.messageProcessor.getIpAddress(), address, this.peerPortAdvertisedInHeaders, this.peerProtocol, msg, isClient, this); this.peerPort = this.peerPortAdvertisedInHeaders; this.peerAddress = address; this.key = MessageChannel.getKey(peerAddress, peerPort, "TCP"); if (logger.isLoggingEnabled(LogWriter.TRACE_WARN)) { logger.logWarning( "retry suceeded to peerAddress = " + peerAddress + " peerPortAdvertisedInHeaders = " + peerPortAdvertisedInHeaders + " key = " + key); } } else { throw problem; // throw the original excpetion we had from the first attempt } } // Created a new socket so close the old one and stick the new // one in its place but dont do this if it is a datagram socket. // (could have replied via udp but received via tcp!). // if (mySock == null && s != null) { // this.uncache(); // } else if (sock != mySock && sock != null) { if (mySock != null) { if (logger.isLoggingEnabled(LogWriter.TRACE_WARN)) { logger.logWarning("Old socket different than new socket on channel " + key); logger.logStackTrace(); logger.logWarning("Old socket local ip address " + mySock.getLocalSocketAddress()); logger.logWarning("Old socket remote ip address " + mySock.getRemoteSocketAddress()); logger.logWarning("New socket local ip address " + sock.getLocalSocketAddress()); logger.logWarning("New socket remote ip address " + sock.getRemoteSocketAddress()); } close(false, false); } if (problem == null) { if (mySock != null) { if (logger.isLoggingEnabled(LogWriter.TRACE_WARN)) { logger.logWarning( "There was no exception for the retry mechanism so creating a new thread based on the new socket for incoming " + key); } } mySock = sock; this.myClientInputStream = mySock.getInputStream(); this.myClientOutputStream = mySock.getOutputStream(); Thread thread = new Thread(this); thread.setDaemon(true); thread.setName("TCPMessageChannelThread"); thread.start(); } else { if (logger.isLoggingEnabled(LogWriter.TRACE_WARN)) { logger.logWarning( "There was an exception for the retry mechanism so not creating a new thread based on the new socket for incoming " + key); } mySock = sock; } } } /** * 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 synchronized void sendMessage( byte message[], InetAddress receiverAddress, int receiverPort, boolean retry) throws IOException { if (message == null || receiverAddress == null) throw new IllegalArgumentException("Null argument"); 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); } } Socket sock = null; IOException problem = null; try { sock = this.sipStack.ioHandler.sendBytes( this.messageProcessor.getIpAddress(), receiverAddress, receiverPort, "TCP", message, retry, this); } catch (IOException any) { problem = any; logger.logWarning( "Failed to connect " + this.peerAddress + ":" + receiverPort + " but trying the advertised port=" + this.peerPortAdvertisedInHeaders + " if it's different than the port we just failed on"); logger.logError("Error is ", any); } if (sock == null) { // http://java.net/jira/browse/JSIP-362 If we couldn't connect to the host, try // the advertised host:port as failsafe if (peerAddressAdvertisedInHeaders != null && peerPortAdvertisedInHeaders > 0) { if (logger.isLoggingEnabled(LogWriter.TRACE_WARN)) { logger.logWarning( "Couldn't connect to receiverAddress = " + receiverAddress + " receiverPort = " + receiverPort + " key = " + key + " retrying on peerPortAdvertisedInHeaders " + peerPortAdvertisedInHeaders); } InetAddress address = InetAddress.getByName(peerAddressAdvertisedInHeaders); sock = this.sipStack.ioHandler.sendBytes( this.messageProcessor.getIpAddress(), address, this.peerPortAdvertisedInHeaders, "TCP", message, retry, this); this.peerPort = this.peerPortAdvertisedInHeaders; this.peerAddress = address; this.key = MessageChannel.getKey(peerAddress, peerPort, "TCP"); if (logger.isLoggingEnabled(LogWriter.TRACE_WARN)) { logger.logWarning( "retry suceeded to peerAddress = " + peerAddress + " peerPort = " + peerPort + " key = " + key); } } else { throw problem; // throw the original excpetion we had from the first attempt } } if (sock != mySock && sock != null) { if (mySock != null) { if (logger.isLoggingEnabled(LogWriter.TRACE_WARN)) { logger.logWarning("Old socket different than new socket on channel " + key); logger.logStackTrace(); logger.logWarning("Old socket local ip address " + mySock.getLocalSocketAddress()); logger.logWarning("Old socket remote ip address " + mySock.getRemoteSocketAddress()); logger.logWarning("New socket local ip address " + sock.getLocalSocketAddress()); logger.logWarning("New socket remote ip address " + sock.getRemoteSocketAddress()); } close(false, false); } if (problem == null) { if (mySock != null) { if (logger.isLoggingEnabled(LogWriter.TRACE_WARN)) { logger.logWarning( "There was no exception for the retry mechanism so creating a new thread based on the new socket for incoming " + key); } } mySock = sock; this.myClientInputStream = mySock.getInputStream(); this.myClientOutputStream = mySock.getOutputStream(); // start a new reader on this end of the pipe. Thread mythread = new Thread(this); mythread.setDaemon(true); mythread.setName("TCPMessageChannelThread"); mythread.start(); } else { if (logger.isLoggingEnabled(LogWriter.TRACE_WARN)) { logger.logWarning( "There was an exception for the retry mechanism so not creating a new thread based on the new socket for incoming " + key); } mySock = sock; } } } /** * 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()) logger.logException(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 (mySock != null) { if (logger.isLoggingEnabled(LogWriter.TRACE_ERROR)) { logger.logError("Malformed mandatory headers: closing socket! :" + mySock.toString()); } try { mySock.close(); } catch (IOException ie) { if (logger.isLoggingEnabled(LogWriter.TRACE_ERROR)) { logger.logError( "Exception while closing socket! :" + mySock.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 { TCPMessageChannel that = (TCPMessageChannel) other; if (this.mySock != that.mySock) return false; else return true; } } /** TCP Is not a secure protocol. */ public boolean isSecure() { return false; } }
public class NioTlsMessageProcessor extends NioTcpMessageProcessor { private static StackLogger logger = CommonLogger.getLogger(NioTlsMessageProcessor.class); // Create a trust manager that does not validate certificate chains TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { public java.security.cert.X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } public void checkClientTrusted(X509Certificate[] certs, String authType) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "checkClientTrusted : Not validating certs " + certs + " authType " + authType); } } public void checkServerTrusted(X509Certificate[] certs, String authType) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "checkServerTrusted : Not validating certs " + certs + " authType " + authType); } } } }; SSLContext sslServerCtx; SSLContext sslClientCtx; public NioTlsMessageProcessor(InetAddress ipAddress, SIPTransactionStack sipStack, int port) { super(ipAddress, sipStack, port); transport = "TLS"; try { init(); } catch (Exception e) { throw new RuntimeException(e); } } @Override public NioTcpMessageChannel createMessageChannel( NioTcpMessageProcessor nioTcpMessageProcessor, SocketChannel client) throws IOException { return NioTlsMessageChannel.create(NioTlsMessageProcessor.this, client); } @Override public MessageChannel createMessageChannel(HostPort targetHostPort) throws IOException { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("NioTlsMessageProcessor::createMessageChannel: " + targetHostPort); } NioTlsMessageChannel retval = null; try { String key = MessageChannel.getKey(targetHostPort, "TLS"); if (messageChannels.get(key) != null) { retval = (NioTlsMessageChannel) this.messageChannels.get(key); return retval; } else { retval = new NioTlsMessageChannel( targetHostPort.getInetAddress(), targetHostPort.getPort(), sipStack, this); // retval.getSocketChannel().register(selector, SelectionKey.OP_READ); synchronized (messageChannels) { this.messageChannels.put(key, retval); } retval.isCached = true; if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("key " + key); logger.logDebug("Creating " + retval); } selector.wakeup(); return retval; } } finally { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("MessageChannel::createMessageChannel - exit " + retval); } } } @Override public MessageChannel createMessageChannel(InetAddress targetHost, int port) throws IOException { String key = MessageChannel.getKey(targetHost, port, "TLS"); if (messageChannels.get(key) != null) { return this.messageChannels.get(key); } else { NioTlsMessageChannel retval = new NioTlsMessageChannel(targetHost, port, sipStack, this); selector.wakeup(); // retval.getSocketChannel().register(selector, SelectionKey.OP_READ); this.messageChannels.put(key, retval); retval.isCached = true; if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("key " + key); logger.logDebug("Creating " + retval); } return retval; } } public void init() throws Exception, CertificateException, FileNotFoundException, IOException { if (sipStack.securityManagerProvider.getKeyManagers(false) == null || sipStack.securityManagerProvider.getTrustManagers(false) == null || sipStack.securityManagerProvider.getTrustManagers(true) == null) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("TLS initialization failed due to NULL security config"); } return; // The settings } sslServerCtx = SSLContext.getInstance("TLS"); sslClientCtx = SSLContext.getInstance("TLS"); if (sipStack.getClientAuth() == ClientAuthType.DisabledAll) { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "ClientAuth " + sipStack.getClientAuth() + " bypassing all cert validations"); } sslServerCtx.init( sipStack.securityManagerProvider.getKeyManagers(false), trustAllCerts, null); sslClientCtx.init(sipStack.securityManagerProvider.getKeyManagers(true), trustAllCerts, null); } else { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("ClientAuth " + sipStack.getClientAuth()); } sslServerCtx.init( sipStack.securityManagerProvider.getKeyManagers(false), sipStack.securityManagerProvider.getTrustManagers(false), null); sslClientCtx.init( sipStack.securityManagerProvider.getKeyManagers(true), sipStack.securityManagerProvider.getTrustManagers(true), null); } } }
public class NioTlsWebSocketMessageChannel extends NioWebSocketMessageChannel implements NioTlsChannelInterface { private static StackLogger logger = CommonLogger.getLogger(NioTlsWebSocketMessageChannel.class); SSLStateMachine sslStateMachine; private int appBufferMax; private int netBufferMax; public static NioTlsWebSocketMessageChannel create( NioTlsWebSocketMessageProcessor nioTcpMessageProcessor, SocketChannel socketChannel) throws IOException { NioTlsWebSocketMessageChannel retval = (NioTlsWebSocketMessageChannel) channelMap.get(socketChannel); if (retval == null) { retval = new NioTlsWebSocketMessageChannel(nioTcpMessageProcessor, socketChannel); channelMap.put(socketChannel, retval); } return retval; } protected NioTlsWebSocketMessageChannel( NioTcpMessageProcessor nioTcpMessageProcessor, SocketChannel socketChannel) throws IOException { super(nioTcpMessageProcessor, socketChannel); messageProcessor = nioTcpMessageProcessor; myClientInputStream = socketChannel.socket().getInputStream(); try { this.init(false); createBuffers(); } catch (Exception e) { throw new IOException("Can't do TLS init", e); } } public void init(boolean clientMode) throws Exception, CertificateException, FileNotFoundException, IOException { SSLContext ctx = ((NioTlsWebSocketMessageProcessor) messageProcessor).sslServerCtx; sslStateMachine = new SSLStateMachine(ctx.createSSLEngine(), this); sslStateMachine.sslEngine.setUseClientMode(false); String auth = ((SipStackImpl) super.sipStack) .getConfigurationProperties() .getProperty("gov.nist.javax.sip.TLS_CLIENT_AUTH_TYPE"); sslStateMachine.sslEngine.setNeedClientAuth(false); sslStateMachine.sslEngine.setWantClientAuth(false); String clientProtocols = ((SipStackImpl) super.sipStack) .getConfigurationProperties() .getProperty("gov.nist.javax.sip.TLS_CLIENT_PROTOCOLS"); if (clientProtocols != null) { sslStateMachine.sslEngine.setEnabledProtocols(clientProtocols.split(",")); } } public ByteBuffer prepareEncryptedDataBuffer() { return ByteBufferFactory.getInstance().allocateDirect(netBufferMax); } public ByteBuffer prepareAppDataBuffer() { return ByteBufferFactory.getInstance().allocateDirect(appBufferMax); } public static class SSLReconnectedException extends IOException { private static final long serialVersionUID = 1L; } @Override protected void sendMessage(final byte[] msg, final boolean isClient) throws IOException { checkSocketState(); ByteBuffer b = ByteBuffer.wrap(msg); try { sslStateMachine.wrap( b, ByteBufferFactory.getInstance().allocateDirect(netBufferMax), new MessageSendCallback() { @Override public void doSend(byte[] bytes) throws IOException { NioTlsWebSocketMessageChannel.super.sendMessage(bytes, isClient); } }); } catch (Exception e) { throw new IOException("Can't send message", e); } } public void sendEncryptedData(byte[] msg) throws IOException { // bypass the encryption for already encrypted data or TLS metadata if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug( "sendEncryptedData " + " this = " + this + " peerPort = " + peerPort + " addr = " + peerAddress); } 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); } super.sendNonWebSocketMessage(msg, false); // super.sendMessage(msg, this.peerAddress, this.peerPort, true); } @Override public void sendMessage( final byte message[], final InetAddress receiverAddress, final int receiverPort, final boolean retry) throws IOException { checkSocketState(); ByteBuffer b = ByteBuffer.wrap(message); try { sslStateMachine.wrap( b, ByteBufferFactory.getInstance().allocateDirect(netBufferMax), new MessageSendCallback() { @Override public void doSend(byte[] bytes) throws IOException { NioTlsWebSocketMessageChannel.super.sendTCPMessage( bytes, receiverAddress, receiverPort, retry); } }); } catch (IOException e) { throw e; } } public void sendHttpMessage( final byte message[], final InetAddress receiverAddress, final int receiverPort, final boolean retry) throws IOException { checkSocketState(); ByteBuffer b = ByteBuffer.wrap(message); try { sslStateMachine.wrap( b, ByteBufferFactory.getInstance().allocateDirect(netBufferMax), new MessageSendCallback() { @Override public void doSend(byte[] bytes) throws IOException { NioTlsWebSocketMessageChannel.super.sendMessage( bytes, receiverAddress, receiverPort, retry); } }); } catch (IOException e) { throw e; } } private void createBuffers() { SSLSession session = sslStateMachine.sslEngine.getSession(); appBufferMax = session.getApplicationBufferSize(); netBufferMax = session.getPacketBufferSize(); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("appBufferMax=" + appBufferMax + " netBufferMax=" + netBufferMax); } } public NioTlsWebSocketMessageChannel( InetAddress inetAddress, int port, SIPTransactionStack sipStack, NioTcpMessageProcessor nioTcpMessageProcessor) throws IOException { super(inetAddress, port, sipStack, nioTcpMessageProcessor); try { init(true); createBuffers(); } catch (Exception e) { throw new IOException("Can't init the TLS channel", e); } } @Override protected void addBytes(byte[] bytes) throws Exception { if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("Adding WSS bytes for decryption " + bytes.length); } if (bytes.length <= 0) return; ByteBuffer buffer = ByteBuffer.wrap(bytes); sslStateMachine.unwrap(buffer); } @Override protected void sendNonWebSocketMessage(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); } sendMessage(msg, this.peerAddress, this.peerPort, isClient); } @Override public String getTransport() { return "WSS"; } @Override public void onNewSocket(byte[] message) { super.onNewSocket(message); try { if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) { String last = null; if (message != null) { last = new String(message, "UTF-8"); } logger.logDebug("New socket for " + this + " last message = " + last); } init(true); createBuffers(); sendMessage(message, false); } catch (Exception e) { logger.logError("Cant reinit", e); } } private void checkSocketState() throws IOException { if (socketChannel != null && (!socketChannel.isConnected() || !socketChannel.isOpen())) { if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) logger.logDebug("Need to reset SSL engine for socket " + socketChannel); try { init(sslStateMachine.sslEngine.getUseClientMode()); } catch (Exception ex) { logger.logError("Cannot reset SSL engine", ex); throw new IOException(ex); } } } @Override public boolean isSecure() { return true; } @Override public void addPlaintextBytes(byte[] bytes) throws Exception { super.addBytes(bytes); } }
/** * Implementation of the SipCache interface, backed by an Infinispan Cache * * <p>The configuration of Infinispan Cache can be set through the following Restcomm SIP Stack * property : <b>org.mobicents.ha.javax.sip.INFINISPAN_CACHE_CONFIG_PATH</b> * * <p>If there is an already existing Infinispan CacheManager instance to be used, then it can be * plugged in by specifying its JNDI name through the following property: * <b>org.mobicents.ha.javax.sip.INFINISPAN_CACHEMANAGER_JNDI_NAME</b> * * <p>If neither the Infinispan cache configuration path property, nor the CacheManager JNDI name * are specified, then a default Infinispan config will be used, which can be found at: * <b>META-INF/cache-configuration.xml</b> * * @author <A HREF="mailto:[email protected]">Gergely Posfai</A> * @author <A HREF="mailto:[email protected]">Andras Kokuti</A> */ public class InfinispanCache implements SipCache { public static final String INFINISPAN_CACHE_CONFIG_PATH = "org.mobicents.ha.javax.sip.INFINISPAN_CACHE_CONFIG_PATH"; public static final String DEFAULT_FILE_CONFIG_PATH = "META-INF/cache-configuration.xml"; public static final String INFINISPAN_CACHEMANAGER_JNDI_NAME = "org.mobicents.ha.javax.sip.INFINISPAN_CACHEMANAGER_JNDI_NAME"; private static StackLogger clusteredlogger = CommonLogger.getLogger(InfinispanCache.class); private ScheduledThreadPoolExecutor executor = null; private Properties configProperties = null; private ClusteredSipStack stack; private Cache<String, Object> dialogs; private Cache<String, Object> appDataMap; private Cache<String, Object> serverTransactions; private Cache<String, Object> serverTransactionsApp; private Cache<String, Object> clientTransactions; private Cache<String, Object> clientTransactionsApp; private CacheContainer cm; private SIPDialogCacheData dialogCacheData; private SIPServerTransactionCacheData serverTXCacheData; private SIPClientTransactionCacheData clientTXCacheData; public SIPDialog getDialog(String dialogId) throws SipCacheException { if (dialogId == null) throw new SipCacheException("No dialogId"); if (dialogCacheData != null) return dialogCacheData.getDialog(dialogId); else throw new SipCacheException("No SIPClientTransactionCache"); } public void putDialog(SIPDialog dialog) throws SipCacheException { if (dialog == null) throw new SipCacheException("SipDialog is null"); if (dialogCacheData != null) dialogCacheData.putDialog(dialog); else throw new SipCacheException("No SIPClientTransactionCache"); } public void updateDialog(SIPDialog dialog) throws SipCacheException { if (dialog == null) throw new SipCacheException("SipDialog is null"); if (dialogCacheData != null) dialogCacheData.updateDialog(dialog); else throw new SipCacheException("No SIPClientTransactionCache"); } public void removeDialog(String dialogId) throws SipCacheException { if (dialogId == null) throw new SipCacheException("No dialogId"); if (dialogCacheData != null) { dialogCacheData.removeDialog(dialogId); } else { throw new SipCacheException("No SIPClientTransactionCache"); } } public void evictDialog(String dialogId) { if (dialogCacheData != null) dialogCacheData.evictDialog(dialogId); } public SIPClientTransaction getClientTransaction(String txId) throws SipCacheException { if (clientTXCacheData != null) return clientTXCacheData.getClientTransaction(txId); else throw new SipCacheException("No SIPClientTransactionCache"); } public void putClientTransaction(SIPClientTransaction clientTransaction) throws SipCacheException { if (clientTXCacheData != null) clientTXCacheData.putClientTransaction(clientTransaction); else throw new SipCacheException("No SIPClientTransactionCache"); } public void removeClientTransaction(String txId) throws SipCacheException { if (clientTXCacheData != null) clientTXCacheData.removeClientTransaction(txId); else throw new SipCacheException("No SIPClientTransactionCache"); } public SIPServerTransaction getServerTransaction(String txId) throws SipCacheException { if (serverTXCacheData != null) return serverTXCacheData.getServerTransaction(txId); else throw new SipCacheException("No SIPServerTransactionCache"); } public void putServerTransaction(SIPServerTransaction serverTransaction) throws SipCacheException { if (serverTXCacheData != null) serverTXCacheData.putServerTransaction(serverTransaction); else throw new SipCacheException("No SIPServerTransactionCache"); } public void removeServerTransaction(String txId) throws SipCacheException { if (serverTXCacheData != null) serverTXCacheData.removeServerTransaction(txId); else throw new SipCacheException("No SIPServerTransactionCache"); } public void init() throws SipCacheException { executor = new ScheduledThreadPoolExecutor(1); final String configurationPath = configProperties.getProperty(INFINISPAN_CACHE_CONFIG_PATH, DEFAULT_FILE_CONFIG_PATH); if (configProperties.containsKey(INFINISPAN_CACHEMANAGER_JNDI_NAME)) { if (clusteredlogger.isLoggingEnabled(LogLevels.TRACE_INFO)) { clusteredlogger.logInfo( INFINISPAN_CACHEMANAGER_JNDI_NAME + " specified, trying to load Inifinispan CacheManager from JNDI " + configProperties.getProperty(INFINISPAN_CACHEMANAGER_JNDI_NAME)); } executor.scheduleAtFixedRate( new Runnable() { static final int MAX_ATTEMPTS = 30; int attempts = 0; public void run() { attempts++; // Init Infinispan CacheManager if (configProperties.containsKey(INFINISPAN_CACHEMANAGER_JNDI_NAME)) { try { InitialContext context = new InitialContext(); String cacheManagerJndiName = configProperties.getProperty(INFINISPAN_CACHEMANAGER_JNDI_NAME); cm = (CacheContainer) context.lookup(cacheManagerJndiName); if (clusteredlogger.isLoggingEnabled(LogLevels.TRACE_INFO)) { clusteredlogger.logInfo( "Found Inifinispan CacheManager: cacheManagerJndiName \"" + cacheManagerJndiName + "\" " + cm + " after attempts " + attempts); } executor.remove(this); executor.shutdown(); } catch (NamingException e) { // Inifinispan CacheManager JNDI lookup failed: could not get InitialContext or // lookup failed if (attempts > MAX_ATTEMPTS) { clusteredlogger.logError( "Inifinispan CacheManager JNDI lookup failed: could not get InitialContext or lookup failed after attempts " + attempts + " stopping there", e); executor.remove(this); executor.shutdown(); } else { if (clusteredlogger.isLoggingEnabled(LogLevels.TRACE_INFO)) { clusteredlogger.logInfo( "Inifinispan CacheManager JNDI lookup failed: could not get InitialContext or lookup failed after attempts " + attempts + ", retrying every second"); } } return; } } setupCacheStructures(); if (dialogCacheData != null) { dialogCacheData.setDialogs(dialogs); dialogCacheData.setAppDataMap(appDataMap); } if (serverTXCacheData != null) { serverTXCacheData.setServerTransactions(serverTransactions); serverTXCacheData.setServerTransactionsApp(serverTransactionsApp); } if (clientTXCacheData != null) { clientTXCacheData.setClientTransactions(clientTransactions); clientTXCacheData.setClientTransactionsApp(clientTransactionsApp); } } }, 0, 1, TimeUnit.SECONDS); } else { if (clusteredlogger.isLoggingEnabled(LogLevels.TRACE_INFO)) { clusteredlogger.logInfo( INFINISPAN_CACHEMANAGER_JNDI_NAME + " not specified, trying to load Inifinispan CacheManager from configuration file " + configurationPath); } try { if (cm == null) { cm = CacheManagerHolder.getManager(configurationPath); if (clusteredlogger.isLoggingEnabled(LogLevels.TRACE_INFO)) { clusteredlogger.logInfo( "Found Inifinispan CacheManager: configuration file from path \"" + configurationPath + "\" " + cm); } } setupCacheStructures(); } catch (IOException e) { clusteredlogger.logError( "Failed to init Inifinispan CacheManager: could not read configuration file from path \"" + configurationPath + "\"", e); } if (dialogCacheData != null) { dialogCacheData.setDialogs(dialogs); dialogCacheData.setAppDataMap(appDataMap); } if (serverTXCacheData != null) { serverTXCacheData.setServerTransactions(serverTransactions); serverTXCacheData.setServerTransactionsApp(serverTransactionsApp); } if (clientTXCacheData != null) { clientTXCacheData.setClientTransactions(clientTransactions); clientTXCacheData.setClientTransactionsApp(clientTransactionsApp); } } } private void setupCacheStructures() { InfinispanCacheListener dialogsCacheListener = new InfinispanCacheListener(stack); dialogs = cm.getCache("cache.dialogs"); appDataMap = cm.getCache("cache.appdata"); serverTransactions = cm.getCache("cache.serverTX"); serverTransactionsApp = cm.getCache("cache.serverTXApp"); clientTransactions = cm.getCache("cache.clientTX"); clientTransactionsApp = cm.getCache("cache.clientTXApp"); dialogs.addListener(dialogsCacheListener); } public void start() throws SipCacheException { dialogCacheData = new SIPDialogCacheData(stack, dialogs, appDataMap); serverTXCacheData = new SIPServerTransactionCacheData(stack, serverTransactions, serverTransactionsApp); clientTXCacheData = new SIPClientTransactionCacheData(stack, clientTransactions, clientTransactionsApp); } public void stop() throws SipCacheException { clientTXCacheData = null; serverTXCacheData = null; dialogCacheData = null; } public void setConfigurationProperties(Properties configurationProperties) { this.configProperties = configurationProperties; } public boolean inLocalMode() { return false; } public void setClusteredSipStack(ClusteredSipStack clusteredStack) { stack = clusteredStack; } public CacheContainer getCacheManager() { return cm; } }