/* * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.mobicents.servlet.sip.core.dispatchers; import gov.nist.javax.sip.message.SIPMessage; import gov.nist.javax.sip.stack.SIPTransaction; import java.io.IOException; import java.util.ListIterator; import javax.servlet.ServletException; import javax.sip.ClientTransaction; import javax.sip.Dialog; import javax.sip.InvalidArgumentException; import javax.sip.ServerTransaction; import javax.sip.SipException; import javax.sip.SipProvider; import javax.sip.header.ViaHeader; import javax.sip.message.Request; import javax.sip.message.Response; import org.apache.log4j.Logger; import org.mobicents.servlet.sip.JainSipUtils; import org.mobicents.servlet.sip.core.session.MobicentsSipSession; import org.mobicents.servlet.sip.core.session.SessionManagerUtil; import org.mobicents.servlet.sip.core.session.SipManager; import org.mobicents.servlet.sip.core.session.SipSessionKey; import org.mobicents.servlet.sip.message.SipFactoryImpl; import org.mobicents.servlet.sip.message.SipServletMessageImpl; import org.mobicents.servlet.sip.message.SipServletRequestImpl; import org.mobicents.servlet.sip.message.SipServletResponseImpl; import org.mobicents.servlet.sip.message.TransactionApplicationData; import org.mobicents.servlet.sip.proxy.ProxyBranchImpl; import org.mobicents.servlet.sip.startup.SipContext; /** * This class is responsible for routing and dispatching responses to applications according to JSR 289 Section * 15.6 Responses, Subsequent Requests and Application Path * * It uses via header parameters that were previously set by the container to know which app has to be called * * @author <A HREF="mailto:jean.deruelle@gmail.com">Jean Deruelle</A> * */ public class ResponseDispatcher extends MessageDispatcher { private static transient Logger logger = Logger.getLogger(ResponseDispatcher.class); public ResponseDispatcher() {} // public ResponseDispatcher(SipApplicationDispatcher sipApplicationDispatcher) { // super(sipApplicationDispatcher); // } /** * {@inheritDoc} */ public void dispatchMessage(final SipProvider sipProvider, SipServletMessageImpl sipServletMessage) throws DispatcherException { final SipFactoryImpl sipFactoryImpl = sipApplicationDispatcher.getSipFactory(); final SipServletResponseImpl sipServletResponse = (SipServletResponseImpl) sipServletMessage; final Response response = sipServletResponse.getResponse(); final ListIterator<ViaHeader> viaHeaders = response.getHeaders(ViaHeader.NAME); final ViaHeader viaHeader = viaHeaders.next(); if(logger.isInfoEnabled()) { logger.info("viaHeader = " + viaHeader.toString()); } //response meant for the container if(!sipApplicationDispatcher.isViaHeaderExternal(viaHeader)) { final ClientTransaction clientTransaction = (ClientTransaction) sipServletResponse.getTransaction(); final Dialog dialog = sipServletResponse.getDialog(); TransactionApplicationData applicationData = null; SipServletRequestImpl tmpOriginalRequest = null; if(clientTransaction != null) { applicationData = (TransactionApplicationData)clientTransaction.getApplicationData(); if(applicationData.getSipServletMessage() instanceof SipServletRequestImpl) { tmpOriginalRequest = (SipServletRequestImpl)applicationData.getSipServletMessage(); } //add the response for access from B2BUAHelper.getPendingMessages applicationData.addSipServletResponse(sipServletResponse); // ViaHeader nextViaHeader = null; if(viaHeaders.hasNext()) { nextViaHeader = viaHeaders.next(); } checkInitialRemoteInformation(sipServletMessage, nextViaHeader); } //there is no client transaction associated with it, it means that this is a retransmission else if(dialog != null) { applicationData = (TransactionApplicationData)dialog.getApplicationData(); if(applicationData.getSipServletMessage() instanceof SipServletRequestImpl) { tmpOriginalRequest = (SipServletRequestImpl)applicationData.getSipServletMessage(); } final ProxyBranchImpl proxyBranch = applicationData.getProxyBranch(); if(proxyBranch == null) { if(logger.isDebugEnabled()) { logger.debug("retransmission received for a non proxy application, dropping the response " + response); } // forwardResponseStatefully(sipServletResponse); return ; } } final SipServletRequestImpl originalRequest = tmpOriginalRequest; sipServletResponse.setOriginalRequest(originalRequest); final String appNameNotDeployed = viaHeader.getParameter(APP_NOT_DEPLOYED); if(appNameNotDeployed != null && appNameNotDeployed.length() > 0) { forwardResponseStatefully(sipServletResponse); return ; } final String noAppReturned = viaHeader.getParameter(NO_APP_RETURNED); if(noAppReturned != null && noAppReturned.length() > 0) { forwardResponseStatefully(sipServletResponse); return ; } final String modifier = viaHeader.getParameter(MODIFIER); if(modifier != null && modifier.length() > 0) { forwardResponseStatefully(sipServletResponse); return ; } final String appId = viaHeader.getParameter(APP_ID); if(appId == null) { throw new DispatcherException("the via header " + viaHeader + " for the response is missing the appid parameter previsouly set by the container"); } final String appNameHashed = viaHeader.getParameter(RR_PARAM_APPLICATION_NAME); if(appNameHashed == null) { throw new DispatcherException("the via header " + viaHeader + " for the response is missing the appname parameter previsouly set by the container"); } final String appName = sipApplicationDispatcher.getApplicationNameFromHash(appNameHashed); boolean inverted = false; if(dialog != null && dialog.isServer()) { inverted = true; } final SipContext sipContext = sipApplicationDispatcher.findSipApplication(appName); final SipManager sipManager = (SipManager)sipContext.getManager(); SipSessionKey sessionKey = SessionManagerUtil.getSipSessionKey(appId, appName, response, inverted); if(logger.isDebugEnabled()) { logger.debug("Trying to find session with following session key " + sessionKey); } MobicentsSipSession tmpSession = null; tmpSession = sipManager.getSipSession(sessionKey, false, sipFactoryImpl, null); //needed in the case of RE-INVITE by example if(tmpSession == null) { sessionKey = SessionManagerUtil.getSipSessionKey(appId, appName, response, !inverted); if(logger.isDebugEnabled()) { logger.debug("Trying to find session with following session key " + sessionKey); } tmpSession = sipManager.getSipSession(sessionKey, false, sipFactoryImpl, null); } if(logger.isDebugEnabled()) { logger.debug("session found is " + tmpSession); if(tmpSession == null) { sipManager.dumpSipSessions(); } } if(tmpSession == null) { logger.error("Dropping the response since no active sip session has been found for it : " + response); return ; } else { sipServletResponse.setSipSessionKey(sessionKey); } if(logger.isInfoEnabled()) { logger.info("route response on following session " + tmpSession.getId()); } final MobicentsSipSession session = tmpSession; final TransactionApplicationData finalApplicationData = applicationData; final DispatchTask dispatchTask = new DispatchTask(sipServletResponse, sipProvider) { public void dispatch() throws DispatcherException { sipContext.enterSipApp(null, sipServletResponse, sipManager, true, true); try { try { session.setSessionCreatingTransaction(clientTransaction); session.setSessionCreatingDialog(dialog); if(originalRequest != null) { originalRequest.setResponse(sipServletResponse); } // RFC 3265 : If a 200-class response matches such a SUBSCRIBE or REFER request, // it creates a new subscription and a new dialog. if(Request.SUBSCRIBE.equals(sipServletResponse.getMethod()) && sipServletResponse.getStatus() >= 200 && sipServletResponse.getStatus() <= 300) { session.addSubscription(sipServletResponse); } // See if this is a response to a proxied request // We can not use session.getProxyBranch() because all branches belong to the same session // and the session.proxyBranch is overwritten each time there is activity on the branch. ProxyBranchImpl proxyBranch = null; if(finalApplicationData != null) { proxyBranch = finalApplicationData.getProxyBranch(); } else if(session.getProxy() != null) { // the final Application data is null meaning that the CTX was null, so it's a retransmission proxyBranch = session.getProxy().getFinalBranchForSubsequentRequests(); } if(proxyBranch != null) { sipServletResponse.setProxyBranch(proxyBranch); // Update Session state session.updateStateOnResponse(sipServletResponse, true); proxyBranch.setResponse(sipServletResponse); // Notfiy the servlet if(logger.isDebugEnabled()) { logger.debug("Is Supervised enabled for this proxy branch ? " + proxyBranch.getProxy().getSupervised()); } if(proxyBranch.getProxy().getSupervised() && response.getStatusCode() != Response.TRYING) { callServlet(sipServletResponse); } // Handle it at the branch proxyBranch.onResponse(sipServletResponse); //we don't forward the response here since this has been done by the proxy } else { //if this is a trying response, the response is dropped if(Response.TRYING == response.getStatusCode()) { if(logger.isDebugEnabled()) { logger.debug("the response is dropped accordingly to JSR 289 " + "since this a 100"); } return; } // in non proxy case we drop the retransmissions if(sipServletResponse.getTransaction() == null && sipServletResponse.getDialog() == null) { if(logger.isDebugEnabled()) { logger.debug("the following response is dropped since there is no client transaction nor dialog for it : " + response); } return; } // Update Session state session.updateStateOnResponse(sipServletResponse, true); callServlet(sipServletResponse); forwardResponseStatefully(sipServletResponse); } } catch (ServletException e) { throw new DispatcherException("Unexpected servlet exception while processing the response : " + response, e); // Sends a 500 Internal server error and stops processing. // JainSipUtils.sendErrorResponse(Response.SERVER_INTERNAL_ERROR, clientTransaction, request, sipProvider); } catch (IOException e) { throw new DispatcherException("Unexpected io exception while processing the response : " + response, e); // Sends a 500 Internal server error and stops processing. // JainSipUtils.sendErrorResponse(Response.SERVER_INTERNAL_ERROR, clientTransaction, request, sipProvider); } catch (Throwable e) { throw new DispatcherException("Unexpected exception while processing response : " + response, e); // Sends a 500 Internal server error and stops processing. // JainSipUtils.sendErrorResponse(Response.SERVER_INTERNAL_ERROR, clientTransaction, request, sipProvider); } } finally { sipContext.exitSipApp(null, sipServletResponse); } } }; // if the flag is set we bypass the executor if(sipApplicationDispatcher.isBypassResponseExecutor()) { dispatchTask.dispatchAndHandleExceptions(); } else { getConcurrencyModelExecutorService(sipContext, sipServletMessage).execute(dispatchTask); } } else { // No sessions here and no servlets called, no need for asynchronicity forwardResponseStatefully(sipServletResponse); } } /** * this method is called when * a B2BUA got the response so we don't have anything to do here * or an app that didn't do anything with it * or when handling the request an app had to be called but wasn't deployed * the topmost via header is stripped from the response and the response is forwarded statefully * @param sipServletResponse */ private final void forwardResponseStatefully(final SipServletResponseImpl sipServletResponse) { final Response response = sipServletResponse.getResponse(); final ListIterator<ViaHeader> viaHeadersLeft = response.getHeaders(ViaHeader.NAME); // we cannot remove the via header on the original response (and we don't to proactively clone the response for perf reasons) // otherwise it will make subsequent request creation fails (because JSIP dialog check the topmostviaHeader of the response) if(viaHeadersLeft.hasNext()) { viaHeadersLeft.next(); } if(viaHeadersLeft.hasNext()) { final ClientTransaction clientTransaction = (ClientTransaction) sipServletResponse.getTransaction(); final Dialog dialog = sipServletResponse.getDialog(); final Response newResponse = (Response) response.clone(); ((SIPMessage)newResponse).setApplicationData(null); newResponse.removeFirst(ViaHeader.NAME); //forward it statefully //TODO should decrease the max forward header to avoid infinite loop if(logger.isDebugEnabled()) { logger.debug("forwarding the response statefully " + newResponse); } TransactionApplicationData applicationData = null; if(clientTransaction != null) { applicationData = (TransactionApplicationData)clientTransaction.getApplicationData(); if(logger.isDebugEnabled()) { logger.debug("ctx application Data " + applicationData); } } //there is no client transaction associated with it, it means that this is a retransmission else if(dialog != null){ applicationData = (TransactionApplicationData)dialog.getApplicationData(); if(logger.isDebugEnabled()) { logger.debug("dialog application data " + applicationData); } } if(applicationData != null) { // non retransmission case final ServerTransaction serverTransaction = (ServerTransaction) applicationData.getTransaction(); try { serverTransaction.sendResponse(newResponse); } catch (SipException e) { logger.error("cannot forward the response statefully" , e); } catch (InvalidArgumentException e) { logger.error("cannot forward the response statefully" , e); } } else { // retransmission case try { String transport = JainSipUtils.findTransport(newResponse); SipProvider sipProvider = sipApplicationDispatcher.getSipNetworkInterfaceManager().findMatchingListeningPoint( transport, false).getSipProvider(); sipProvider.sendResponse(newResponse); } catch (SipException e) { logger.error("cannot forward the response statelessly" , e); } } } else { //B2BUA case we don't have to do anything here //no more via header B2BUA is the end point if(logger.isDebugEnabled()) { logger.debug("Not forwarding the response statefully. " + "It was either an endpoint or a B2BUA, ie an endpoint too " + response); } } } /** * This method checks if the initial remote information as specified by SIP Servlets 1.1 Section 15.7 * is available. If not we add it as headers only if the next via header is for the container * (so that this information stays within the container boundaries) * @param sipServletMessage * @param nextViaHeader */ public void checkInitialRemoteInformation(SipServletMessageImpl sipServletMessage, ViaHeader nextViaHeader) { final SIPTransaction transaction = (SIPTransaction)sipServletMessage.getTransaction(); String remoteAddr = transaction.getPeerAddress(); int remotePort = transaction.getPeerPort(); if(transaction.getPeerPacketSourceAddress() != null) { remoteAddr = transaction.getPeerPacketSourceAddress().getHostAddress(); remotePort = transaction.getPeerPacketSourcePort(); } final String initialRemoteAddr = remoteAddr; final int initialRemotePort = remotePort; final String initialRemoteTransport = transaction.getTransport(); final TransactionApplicationData transactionApplicationData = sipServletMessage.getTransactionApplicationData(); // if the message comes from an external source we add the initial remote info from the transaction if(sipApplicationDispatcher.isExternal(initialRemoteAddr, initialRemotePort, initialRemoteTransport)) { transactionApplicationData.setInitialRemoteHostAddress(initialRemoteAddr); transactionApplicationData.setInitialRemotePort(initialRemotePort); transactionApplicationData.setInitialRemoteTransport(initialRemoteTransport); // there is no other way to pass the information to the next applications in chain // (to avoid maintaining in memory information that would be to be clustered as well...) // than adding it as a custom information, we add it as headers only if // the next via header is for the container if(nextViaHeader != null && !sipApplicationDispatcher.isViaHeaderExternal(nextViaHeader)) { sipServletMessage.addHeaderInternal(JainSipUtils.INITIAL_REMOTE_ADDR_HEADER_NAME, initialRemoteAddr, true); sipServletMessage.addHeaderInternal(JainSipUtils.INITIAL_REMOTE_PORT_HEADER_NAME, "" + initialRemotePort, true); sipServletMessage.addHeaderInternal(JainSipUtils.INITIAL_REMOTE_TRANSPORT_HEADER_NAME, initialRemoteTransport, true); } } else { // if the message comes from an internal source we add the initial remote info from the previously added headers transactionApplicationData.setInitialRemoteHostAddress(sipServletMessage.getHeader(JainSipUtils.INITIAL_REMOTE_ADDR_HEADER_NAME)); String remotePortHeader = sipServletMessage.getHeader(JainSipUtils.INITIAL_REMOTE_PORT_HEADER_NAME); int intRemotePort = -1; if(remotePortHeader != null) { intRemotePort = Integer.parseInt(remotePortHeader); } transactionApplicationData.setInitialRemotePort(intRemotePort); transactionApplicationData.setInitialRemoteTransport(sipServletMessage.getHeader(JainSipUtils.INITIAL_REMOTE_TRANSPORT_HEADER_NAME)); if(nextViaHeader == null || sipApplicationDispatcher.isViaHeaderExternal(nextViaHeader)) { sipServletMessage.removeHeaderInternal(JainSipUtils.INITIAL_REMOTE_ADDR_HEADER_NAME, true); sipServletMessage.removeHeaderInternal(JainSipUtils.INITIAL_REMOTE_PORT_HEADER_NAME, true); sipServletMessage.removeHeaderInternal(JainSipUtils.INITIAL_REMOTE_TRANSPORT_HEADER_NAME, true); } } } }