/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.java.sip.communicator.impl.protocol.sip; import gov.nist.javax.sip.*; import gov.nist.javax.sip.header.*; import gov.nist.javax.sip.stack.*; import java.io.*; import java.util.*; import javax.sip.*; import javax.sip.address.*; import javax.sip.header.*; import javax.sip.message.*; import net.java.sip.communicator.service.netaddr.event.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; import net.java.sip.communicator.util.Logger; import org.jitsi.util.*; /** * This class is the SipListener for all JAIN-SIP * SipProviders. It is in charge of dispatching the received messages * to the suitable ProtocolProviderServiceSipImpls registered with * addSipListener. It also contains the JAIN-SIP pieces which are * common between all ProtocolProviderServiceSipImpls (namely 1 * SipStack, 2 SipProviders, 3 ListeningPoints). * * @author Emil Ivov * @author Lubomir Marinov * @author Alan Kelly * @author Sebastien Mazy */ public class SipStackSharing implements SipListener, NetworkConfigurationChangeListener { /** * We set a custom parameter in the contact address for registrar accounts, * so as to ease dispatching of incoming requests in case several accounts * have the same username in their contact address, eg: * sip:username@192.168.0.1:5060;transport=udp;registering_acc=example_com */ public static final String CONTACT_ADDRESS_CUSTOM_PARAM_NAME = "registering_acc"; /** * Logger for this class. */ private static final Logger logger = Logger.getLogger(SipStackSharing.class); /** * Our SIP stack (provided by JAIN-SIP). */ private final SipStack stack; /** * The JAIN-SIP provider that we use for clear UDP/TCP. */ private SipProvider clearJainSipProvider = null; /** * * The JAIN-SIP provider that we use for TLS. */ private SipProvider secureJainSipProvider = null; /** * The candidate recipients to choose from when dispatching messages * received from one the JAIN-SIP SipProvider-s. for thread safety * issues reasons, better iterate on a copy of that set using * getSipListeners(). */ private final Set listeners = new HashSet(); /** * The property indicating the preferred UDP and TCP * port to bind to for clear communications. */ private static final String PREFERRED_CLEAR_PORT_PROPERTY_NAME = "net.java.sip.communicator.SIP_PREFERRED_CLEAR_PORT"; /** * The property indicating the preferred TLS (TCP) * port to bind to for secure communications. */ private static final String PREFERRED_SECURE_PORT_PROPERTY_NAME = "net.java.sip.communicator.SIP_PREFERRED_SECURE_PORT"; /** * Constructor for this class. Creates the JAIN-SIP stack. * * @throws OperationFailedException if creating the stack fails. */ SipStackSharing() throws OperationFailedException { // init of the stack try { SipFactory sipFactory = SipFactory.getInstance(); /** * In android jsip libs exist in core android devices and * use different version as the one used in jitsi * We change jsip package name in order to use our libs. */ if(OSUtils.IS_ANDROID) sipFactory.setPathName("org.jitsi.gov.nist"); else sipFactory.setPathName("gov.nist"); Properties sipStackProperties = new SipStackProperties(); // Create SipStack object this.stack = sipFactory.createSipStack(sipStackProperties); if (logger.isTraceEnabled()) logger.trace("Created stack: " + this.stack); // set our custom address resolver managing SRV records AddressResolverImpl addressResolver = new AddressResolverImpl(); ((SIPTransactionStack) this.stack) .setAddressResolver(addressResolver); SipActivator.getNetworkAddressManagerService() .addNetworkConfigurationChangeListener(this); } catch(Exception ex) { logger.fatal("Failed to get SIP Factory.", ex); throw new OperationFailedException("Failed to get SIP Factory" , OperationFailedException.INTERNAL_ERROR , ex); } } /** * Adds this listener as a candidate recipient for the dispatching * of new messages received from the JAIN-SIP SipProviders. * * @param listener a new possible target for the dispatching process. * * @throws OperationFailedException if creating one of the underlying * SipProviders fails for whatever reason. */ public void addSipListener(ProtocolProviderServiceSipImpl listener) throws OperationFailedException { synchronized(this.listeners) { if(this.listeners.size() == 0) startListening(); this.listeners.add(listener); if (logger.isTraceEnabled()) logger.trace(this.listeners.size() + " listeners now"); } } /** * This listener will no longer be a candidate recipient for the * dispatching of new messages received from the JAIN-SIP * SipProviders. * * @param listener possible target to remove for the dispatching process. */ public void removeSipListener(ProtocolProviderServiceSipImpl listener) { synchronized(this.listeners) { this.listeners.remove(listener); int listenerCount = listeners.size(); if (logger.isTraceEnabled()) logger.trace(listenerCount + " listeners left"); if(listenerCount == 0) stopListening(); } } /** * Returns a copy of the listeners (= candidate recipients) set. * * @return a copy of the listeners set. */ private Set getSipListeners() { synchronized(this.listeners) { return new HashSet(this.listeners); } } /** * Returns the JAIN-SIP ListeningPoint associated to the given * transport string. * * @param transport a string like "UDP", "TCP" or "TLS". * @return the LP associated to the given transport. */ @SuppressWarnings("unchecked") //jain-sip legacy code public ListeningPoint getLP(String transport) { ListeningPoint lp; Iterator it = this.stack.getListeningPoints(); while(it.hasNext()) { lp = it.next(); // FIXME: JAIN-SIP stack is not consistent with case // (reported upstream) if(lp.getTransport().toLowerCase().equals(transport.toLowerCase())) return lp; } throw new IllegalArgumentException("Invalid transport: " + transport); } /** * Put the stack in a state where it can receive data on three UDP/TCP ports * (2 for clear communication, 1 for TLS). That is to say create the related * JAIN-SIP ListeningPoints and SipProviders. * * @throws OperationFailedException if creating one of the underlying * SipProviders fails for whatever reason. */ private void startListening() throws OperationFailedException { try { int bindRetriesValue = getBindRetriesValue(); this.createProvider(this.getPreferredClearPort(), bindRetriesValue, false); this.createProvider(this.getPreferredSecurePort(), bindRetriesValue, true); this.stack.start(); if (logger.isTraceEnabled()) logger.trace("started listening"); } catch(Exception ex) { logger.error("An unexpected error happened while creating the" + "SipProviders and ListeningPoints."); throw new OperationFailedException("An unexpected error hapenned" + "while initializing the SIP stack" , OperationFailedException.INTERNAL_ERROR , ex); } } /** * Attach JAIN-SIP SipProvider and ListeningPoint to the * stack either for clear communications or TLS. Clear UDP and TCP * ListeningPoints are not handled separately as the former is a * fallback for the latter (depending on the size of the data transmitted). * Both ListeningPoints must be bound to the same address and port * in order for the related SipProvider to be created. If a UDP or * TCP ListeningPoint cannot bind, retry for both on another port. * * @param preferredPort which port to try first to bind. * @param retries how many times should we try to find a free port to bind * @param secure whether to create the TLS SipProvider. * or the clear UDP/TCP one. * * @throws TransportNotSupportedException in case we try to create a * provider for a transport not currently supported by jain-sip * @throws InvalidArgumentException if we try binding to an illegal port * (which we won't) * @throws ObjectInUseException if another SipProvider is already * associated with this ListeningPoint. * @throws TransportAlreadySupportedException if there is already a * ListeningPoint associated to this SipProvider with the same * transport of the ListeningPoint. * @throws TooManyListenersException if we try to add a new * SipListener with a SipProvider when one was already * registered. * */ private void createProvider(int preferredPort, int retries, boolean secure) throws TransportNotSupportedException, InvalidArgumentException, ObjectInUseException, TransportAlreadySupportedException, TooManyListenersException { String context = (secure ? "TLS: " : "clear UDP/TCP: "); if(retries < 0) { // very unlikely to happen with the default 50 retries logger.error(context + "couldn't find free ports to listen on."); return; } ListeningPoint tlsLP = null; ListeningPoint udpLP = null; ListeningPoint tcpLP = null; try { if(secure) { tlsLP = this.stack.createListeningPoint( NetworkUtils.IN_ADDR_ANY , preferredPort , ListeningPoint.TLS); if (logger.isTraceEnabled()) logger.trace("TLS secure ListeningPoint has been created."); this.secureJainSipProvider = this.stack.createSipProvider(tlsLP); this.secureJainSipProvider.addSipListener(this); } else { udpLP = this.stack.createListeningPoint( NetworkUtils.IN_ADDR_ANY , preferredPort , ListeningPoint.UDP); tcpLP = this.stack.createListeningPoint( NetworkUtils.IN_ADDR_ANY , preferredPort , ListeningPoint.TCP); if (logger.isTraceEnabled()) logger.trace("UDP and TCP clear ListeningPoints have " + "been created."); this.clearJainSipProvider = this.stack.createSipProvider(udpLP); this.clearJainSipProvider. addListeningPoint(tcpLP); this.clearJainSipProvider.addSipListener(this); } if (logger.isTraceEnabled()) logger.trace(context + "SipProvider has been created."); } catch(InvalidArgumentException ex) { // The TLS lp tries to bind to the socket after the lp is created // and added to the list. So despite being invalid and not // returned from createLP, it still lingers around. Search it // by obtaining all LPs and then destroy it. if(secure) { if (tlsLP != null) this.stack.deleteListeningPoint(tlsLP); Set lpsToDelete = new HashSet(); @SuppressWarnings("rawtypes") Iterator lps = this.stack.getListeningPoints(); while (lps.hasNext()) { ListeningPoint lp = (ListeningPoint) lps.next(); if (ListeningPoint.TLS.equalsIgnoreCase(lp.getTransport()) && lp.getPort() == preferredPort) { lpsToDelete.add(lp); } } for (ListeningPoint lp : lpsToDelete) { this.stack.deleteListeningPoint(lp); } } // makes sure we didn't leave an open listener // as both UDP and TCP listener have to bind to the same port if(udpLP != null) this.stack.deleteListeningPoint(udpLP); if(tcpLP != null) this.stack.deleteListeningPoint(tcpLP); // FIXME: "Address already in use" is not working // as ex.getMessage() displays in the locale language in SC // (getMessage() is always supposed to be English though) // this should be a temporary workaround //if (ex.getMessage().indexOf("Address already in use") != -1) // another software is probably using the port if(ex.getCause() instanceof java.io.IOException) { if (logger.isDebugEnabled()) logger.debug("Port " + preferredPort + " seems in use for either TCP or UDP."); // tries again on a new random port int currentlyTriedPort = NetworkUtils.getRandomPortNumber(); if (logger.isDebugEnabled()) logger.debug("Retrying bind on port " + currentlyTriedPort); this.createProvider(currentlyTriedPort, retries-1, secure); } else throw ex; } } /** * Put the JAIN-SIP stack in a state where it cannot receive any data and * frees the network ports used. That is to say remove JAIN-SIP * ListeningPoints and SipProviders. */ @SuppressWarnings("unchecked") //jain-sip legacy code private void stopListening() { try { if (this.secureJainSipProvider != null) { this.secureJainSipProvider.removeSipListener(this); this.stack.deleteSipProvider(this.secureJainSipProvider); this.secureJainSipProvider = null; } if (this.clearJainSipProvider != null) { this.clearJainSipProvider.removeSipListener(this); this.stack.deleteSipProvider(this.clearJainSipProvider); this.clearJainSipProvider = null; } Iterator it = this.stack.getListeningPoints(); Vector lpointsToRemove = new Vector(); while(it.hasNext()) { lpointsToRemove.add(it.next()); } it = lpointsToRemove.iterator(); while (it.hasNext()) { this.stack.deleteListeningPoint(it.next()); } this.stack.stop(); if (logger.isTraceEnabled()) logger.trace("stopped listening"); } catch(ObjectInUseException ex) { logger.fatal("Failed to stop listening", ex); } } /** * Returns the JAIN-SIP SipProvider in charge of this * transport. * * @param transport a String like "TCP", "UDP" or "TLS" * @return the corresponding SipProvider */ public SipProvider getJainSipProvider(String transport) { SipProvider sp = null; if(transport.equalsIgnoreCase(ListeningPoint.UDP) || transport.equalsIgnoreCase(ListeningPoint.TCP)) sp = this.clearJainSipProvider; else if(transport.equalsIgnoreCase(ListeningPoint.TLS)) sp = this.secureJainSipProvider; if(sp == null) throw new IllegalArgumentException("invalid transport"); return sp; } /** * Fetches the preferred UDP and TCP port for clear communications in the * user preferences or search is default value set in settings or * fallback on a default value. * * @return the preferred network port for clear communications. */ private int getPreferredClearPort() { int preferredPort = SipActivator.getConfigurationService().getInt( PREFERRED_CLEAR_PORT_PROPERTY_NAME, -1); if(preferredPort <= 1) { // check for default value preferredPort = SipActivator.getResources().getSettingsInt( PREFERRED_CLEAR_PORT_PROPERTY_NAME); } if(preferredPort <= 1) return ListeningPoint.PORT_5060; else return preferredPort; } /** * Fetches the preferred TLS (TCP) port for secure communications in the * user preferences or search is default value set in settings or * fallback on a default value. * * @return the preferred network port for secure communications. */ private int getPreferredSecurePort() { int preferredPort = SipActivator.getConfigurationService().getInt( PREFERRED_SECURE_PORT_PROPERTY_NAME, -1); if(preferredPort <= 1) { // check for default value preferredPort = SipActivator.getResources().getSettingsInt( PREFERRED_SECURE_PORT_PROPERTY_NAME); } if(preferredPort <= 1) return ListeningPoint.PORT_5061; else return preferredPort; } /** * Fetches the number of times to retry when the binding of a JAIN-SIP * ListeningPoint fails. Looks in the user preferences or * fallbacks on a default value. * * @return the number of times to retry a failed bind. */ private int getBindRetriesValue() { return SipActivator.getConfigurationService().getInt( ProtocolProviderService.BIND_RETRIES_PROPERTY_NAME, ProtocolProviderService.BIND_RETRIES_DEFAULT_VALUE); } /** * Dispatches the event received from a JAIN-SIP SipProvider to one * of our "candidate recipient" listeners. * * @param event the event received for a * SipProvider. */ public void processDialogTerminated(DialogTerminatedEvent event) { try { ProtocolProviderServiceSipImpl recipient = (ProtocolProviderServiceSipImpl) SipApplicationData .getApplicationData(event.getDialog(), SipApplicationData.KEY_SERVICE); if(recipient == null) { logger.error("Dialog wasn't marked, please report this to " + "dev@jitsi.org"); } else { if (logger.isTraceEnabled()) logger.trace("service was found with dialog data"); recipient.processDialogTerminated(event); } } catch(Throwable exc) { //any exception thrown within our code should be caught here //so that we could log it rather than interrupt stack activity with //it. this.logApplicationException(DialogTerminatedEvent.class, exc); } } /** * Dispatches the event received from a JAIN-SIP SipProvider to one * of our "candidate recipient" listeners. * * @param event the event received for a SipProvider. */ public void processIOException(IOExceptionEvent event) { try { if (logger.isTraceEnabled()) logger.trace(event); // impossible to dispatch, log here if (logger.isDebugEnabled()) logger.debug("@todo implement processIOException()"); } catch(Throwable exc) { //any exception thrown within our code should be caught here //so that we could log it rather than interrupt stack activity with //it. this.logApplicationException(DialogTerminatedEvent.class, exc); } } /** * Dispatches the event received from a JAIN-SIP SipProvider to one * of our "candidate recipient" listeners. * * @param event the event received for a SipProvider. */ public void processRequest(RequestEvent event) { try { Request request = event.getRequest(); if (logger.isTraceEnabled()) logger.trace("received request: " + request.getMethod()); /* * Create the transaction if it doesn't exist yet. If it is a * dialog-creating request, the dialog will also be automatically * created by the stack. */ if (event.getServerTransaction() == null) { try { // apply some hacks if needed on incoming request // to be compliant with some servers/clients // if needed stop further processing. if(applyNonConformanceHacks(event)) return; SipProvider source = (SipProvider) event.getSource(); ServerTransaction transaction = source.getNewServerTransaction(request); /* * Update the event, otherwise getServerTransaction() and * getDialog() will still return their previous value. */ event = new RequestEvent( source, transaction, transaction.getDialog(), request); } catch (SipException ex) { logger.error( "couldn't create transaction, please report " + "this to dev@jitsi.org", ex); } } ProtocolProviderServiceSipImpl service = getServiceData(event.getServerTransaction()); if (service != null) { service.processRequest(event); } else { service = findTargetFor(request); if (service == null) { logger.error( "couldn't find a ProtocolProviderServiceSipImpl " + "to dispatch to"); if (event.getServerTransaction() != null) event.getServerTransaction().terminate(); } else { /* * Mark the dialog for the dispatching of later in-dialog * requests. If there is no dialog, we need to mark the * request to dispatch a possible timeout when sending the * response. */ Object container = event.getDialog(); if (container == null) container = request; SipApplicationData.setApplicationData( container, SipApplicationData.KEY_SERVICE, service); service.processRequest(event); } } } catch(Throwable exc) { /* * Any exception thrown within our code should be caught here so * that we could log it rather than interrupt stack activity with * it. */ this.logApplicationException(DialogTerminatedEvent.class, exc); // Unfortunately, death can hardly be ignored. if (exc instanceof ThreadDeath) throw (ThreadDeath) exc; } } /** * Dispatches the event received from a JAIN-SIP SipProvider to one * of our "candidate recipient" listeners. * * @param event the event received for a SipProvider. */ public void processResponse(ResponseEvent event) { try { // we don't have to accept the transaction since we //created the request ClientTransaction transaction = event.getClientTransaction(); if (logger.isTraceEnabled()) logger.trace("received response: " + event.getResponse().getStatusCode() + " " + event.getResponse().getReasonPhrase()); if(transaction == null) { logger.warn("Transaction is null, probably already expired! " + "Status=" + event.getResponse().getStatusCode()); return; } ProtocolProviderServiceSipImpl service = getServiceData(transaction); if (service != null) { // Mark the dialog for the dispatching of later in-dialog // responses. If there is no dialog then the initial request // sure is marked otherwise we won't have found the service with // getServiceData(). The request has to be marked in case we // receive one more response in an out-of-dialog transaction. if (event.getDialog() != null) { SipApplicationData.setApplicationData(event.getDialog(), SipApplicationData.KEY_SERVICE, service); } service.processResponse(event); } else { logger.error("We received a response which " + "wasn't marked, please report this to " + "dev@jitsi.org"); } } catch(Throwable exc) { //any exception thrown within our code should be caught here //so that we could log it rather than interrupt stack activity with //it. this.logApplicationException(DialogTerminatedEvent.class, exc); } } /** * Dispatches the event received from a JAIN-SIP SipProvider to one * of our "candidate recipient" listeners. * * @param event the event received for a SipProvider. */ public void processTimeout(TimeoutEvent event) { try { Transaction transaction; if (event.isServerTransaction()) { transaction = event.getServerTransaction(); } else { transaction = event.getClientTransaction(); } ProtocolProviderServiceSipImpl recipient = getServiceData(transaction); if (recipient == null) { logger.error("We received a timeout which wasn't " + "marked, please report this to " + "dev@jitsi.org"); } else { recipient.processTimeout(event); } } catch(Throwable exc) { //any exception thrown within our code should be caught here //so that we could log it rather than interrupt stack activity with //it. this.logApplicationException(DialogTerminatedEvent.class, exc); } } /** * Dispatches the event received from a JAIN-SIP SipProvider to one * of our "candidate recipient" listeners. * * @param event the event received for a * SipProvider. */ public void processTransactionTerminated(TransactionTerminatedEvent event) { try { Transaction transaction; if (event.isServerTransaction()) transaction = event.getServerTransaction(); else transaction = event.getClientTransaction(); ProtocolProviderServiceSipImpl recipient = getServiceData(transaction); if (recipient == null) { logger.error("We received a transaction terminated which wasn't" + " marked, please report this to" + " dev@jitsi.org"); } else { recipient.processTransactionTerminated(event); } } catch(Throwable exc) { //any exception thrown within our code should be caught here //so that we could log it rather than interrupt stack activity with //it. this.logApplicationException(DialogTerminatedEvent.class, exc); } } /** * Find the ProtocolProviderServiceSipImpl (one of our * "candidate recipient" listeners) which this request should be * dispatched to. The strategy is to look first at the request URI, and * then at the To field to find a matching candidate for dispatching. * Note that this method takes a Request as param, and not a * ServerTransaction, because sometimes RequestEvents * have no associated ServerTransaction. * * @param request the Request to find a recipient for. * @return a suitable ProtocolProviderServiceSipImpl. */ private ProtocolProviderServiceSipImpl findTargetFor(Request request) { if(request == null) { logger.error("request shouldn't be null."); return null; } List currentListenersCopy = new ArrayList( this.getSipListeners()); // Let's first narrow down candidate choice by comparing // addresses and ports (no point in delivering to a provider with a // non matching IP address since they will reject it anyway). filterByAddress(currentListenersCopy, request); if(currentListenersCopy.size() == 0) { logger.error("no listeners"); return null; } URI requestURI = request.getRequestURI(); if(requestURI.isSipURI()) { String requestUser = ((SipURI) requestURI).getUser(); List candidates = new ArrayList(); // check if the Request-URI username is // one of ours usernames for(ProtocolProviderServiceSipImpl listener : currentListenersCopy) { String ourUserID = listener.getAccountID().getUserID(); //logger.trace(ourUserID + " *** " + requestUser); if(ourUserID.equals(requestUser)) { if (logger.isTraceEnabled()) logger.trace("suitable candidate found: " + listener.getAccountID()); candidates.add(listener); } } // the perfect match // every other case is approximation if(candidates.size() == 1) { ProtocolProviderServiceSipImpl perfectMatch = candidates.get(0); if (logger.isTraceEnabled()) logger.trace("Will dispatch to \"" + perfectMatch.getAccountID() + "\""); return perfectMatch; } // more than one account match if(candidates.size() > 1) { // check if a custom param exists in the contact // address (set for registrar accounts) for (ProtocolProviderServiceSipImpl candidate : candidates) { String hostValue = ((SipURI) requestURI).getParameter( SipStackSharing.CONTACT_ADDRESS_CUSTOM_PARAM_NAME); if (hostValue == null) continue; if (hostValue.equals(candidate .getContactAddressCustomParamValue())) { if (logger.isTraceEnabled()) logger.trace("Will dispatch to \"" + candidate.getAccountID() + "\" because " + "\" the custom param was set"); return candidate; } } // Past this point, our guess is not reliable. We try to find // the "least worst" match based on parameters like the To field // check if the To header field host part // matches any of our SIP hosts for(ProtocolProviderServiceSipImpl candidate : candidates) { URI fromURI = ((FromHeader) request .getHeader(FromHeader.NAME)).getAddress().getURI(); if(fromURI.isSipURI() == false) continue; SipURI ourURI = (SipURI) candidate .getOurSipAddress((SipURI) fromURI).getURI(); String ourHost = ourURI.getHost(); URI toURI = ((ToHeader) request .getHeader(ToHeader.NAME)).getAddress().getURI(); if(toURI.isSipURI() == false) continue; String toHost = ((SipURI) toURI).getHost(); //logger.trace(toHost + "***" + ourHost); if(toHost.equals(ourHost)) { if (logger.isTraceEnabled()) logger.trace("Will dispatch to \"" + candidate.getAccountID() + "\" because " + "host in the To: is the same as in our AOR"); return candidate; } } // fallback on the first candidate ProtocolProviderServiceSipImpl target = candidates.iterator().next(); logger.info("Will randomly dispatch to \"" + target.getAccountID() + "\" because there is ambiguity on the username from" + " the Request-URI"); if (logger.isTraceEnabled()) logger.trace("\n" + request); return target; } // fallback on any account ProtocolProviderServiceSipImpl target = currentListenersCopy.iterator().next(); if (logger.isDebugEnabled()) logger.debug("Will randomly dispatch to \"" + target .getAccountID() + "\" because the username in the Request-URI " + "is unknown or empty"); if (logger.isTraceEnabled()) logger.trace("\n" + request); return target; } else { logger.error("Request-URI is not a SIP URI, dropping"); } return null; } /** * Removes from the specified list of candidates providers connected to a * registrar that does not match the IP address that we are receiving a * request from. * * @param candidates the list of providers we've like to filter. * @param request the request that we are currently dispatching */ private void filterByAddress( List candidates, Request request) { Iterator iterPP = candidates.iterator(); while (iterPP.hasNext()) { ProtocolProviderServiceSipImpl candidate = iterPP.next(); boolean forceProxyBypass = candidate.getAccountID() .getAccountPropertyBoolean( ProtocolProviderFactory.FORCE_PROXY_BYPASS, false); if(forceProxyBypass) { // Proxy check is disabled all connections are // ok (HA sipXecs, sipXcom, ...) continue; } if(candidate.getRegistrarConnection() == null) { //RegistrarLess connections are ok continue; } if ( !candidate.getRegistrarConnection().isRegistrarless() && !candidate.getRegistrarConnection() .isRequestFromSameConnection(request)) { iterPP.remove(); } } } /** * Retrieves and returns that ProtocolProviderService that this transaction * belongs to, or null if we couldn't associate it with a provider * based on neither the request nor the transaction itself. * * @param transaction the transaction that we'd like to determine a provider * for. * * @return a reference to the ProtocolProviderServiceSipImpl that * transaction was associated with or null if we couldn't * determine which one it is. */ private ProtocolProviderServiceSipImpl getServiceData(Transaction transaction) { ProtocolProviderServiceSipImpl service = (ProtocolProviderServiceSipImpl) SipApplicationData .getApplicationData(transaction.getRequest(), SipApplicationData.KEY_SERVICE); if (service != null) { if (logger.isTraceEnabled()) logger.trace("service was found in request data"); return service; } service = (ProtocolProviderServiceSipImpl) SipApplicationData .getApplicationData(transaction.getDialog(), SipApplicationData.KEY_SERVICE); if (service != null) { if (logger.isTraceEnabled()) logger.trace("service was found in dialog data"); } return service; } /** * Logs exceptions that have occurred in the application while processing * events originating from the stack. * * @param eventClass the class of the jain-sip event that we were handling * when the exception was thrown. * @param exc the exception that we need to log. */ private void logApplicationException( Class eventClass, Throwable exc) { String message = "An error occurred while processing event of type: " + eventClass.getName(); logger.error(message, exc); if (logger.isDebugEnabled()) logger.debug(message, exc); } /** * Safely returns the transaction from the event if already exists. * If not a new transaction is created. * * @param event the request event * @return the server transaction * @throws javax.sip.TransactionAlreadyExistsException if transaction exists * @throws javax.sip.TransactionUnavailableException if unavailable */ public static ServerTransaction getOrCreateServerTransaction( RequestEvent event) throws TransactionAlreadyExistsException, TransactionUnavailableException { ServerTransaction serverTransaction = event.getServerTransaction(); if(serverTransaction == null) { SipProvider jainSipProvider = (SipProvider) event.getSource(); serverTransaction = jainSipProvider .getNewServerTransaction(event.getRequest()); } return serverTransaction; } /** * Returns a local address to use with the specified TCP destination. * The method forces the JAIN-SIP stack to create * s and binds (if necessary) * and return a socket connected to the specified destination address and * port and then return its local address. * * @param dst the destination address that the socket would need to connect * to. * @param dstPort the port number that the connection would be established * with. * @param localAddress the address that we would like to bind on * (null for the "any" address). * @param transport the transport that will be used TCP ot TLS * * @return the SocketAddress that this handler would use when connecting to * the specified destination address and port. * * @throws IOException if we fail binding the local socket */ public java.net.InetSocketAddress getLocalAddressForDestination( java.net.InetAddress dst, int dstPort, java.net.InetAddress localAddress, String transport) throws IOException { if(ListeningPoint.TLS.equalsIgnoreCase(transport)) return (java.net.InetSocketAddress)(((SipStackImpl)this.stack) .getLocalAddressForTlsDst(dst, dstPort, localAddress)); else return (java.net.InetSocketAddress)(((SipStackImpl)this.stack) .getLocalAddressForTcpDst(dst, dstPort, localAddress, 0)); } /** * Place to put some hacks if needed on incoming requests. * * @param event the incoming request event. * @return status true if we don't need to process this * message, just discard it and false otherwise. */ private boolean applyNonConformanceHacks(RequestEvent event) { Request request = event.getRequest(); try { /* * Max-Forwards is required, yet there are UAs which do not * place it. SipProvider#getNewServerTransaction(Request) * will throw an exception in the case of a missing * Max-Forwards header and this method will eventually just * log it thus ignoring the whole event. */ if (request.getHeader(MaxForwardsHeader.NAME) == null) { // it appears that some buggy providers do send requests // with no Max-Forwards headers, as we are at application level // and we know there will be no endless loops // there is no problem of adding headers and process normally // this messages MaxForwardsHeader maxForwards = SipFactory .getInstance().createHeaderFactory() .createMaxForwardsHeader(70); request.setHeader(maxForwards); } } catch(Throwable ex) { logger.warn("Cannot apply incoming request modification!", ex); } try { // using asterisk voice mail initial notify for messages // is ok, but on the fly received messages their notify comes // without subscription-state, so we add it in order to be able to // process message. if(request.getMethod().equals(Request.NOTIFY) && request.getHeader(EventHeader.NAME) != null && ((EventHeader)request.getHeader(EventHeader.NAME)) .getEventType().equals( OperationSetMessageWaitingSipImpl.EVENT_PACKAGE) && request.getHeader(SubscriptionStateHeader.NAME) == null) { request.addHeader( new HeaderFactoryImpl() .createSubscriptionStateHeader( SubscriptionStateHeader.ACTIVE)); } } catch(Throwable ex) { logger.warn("Cannot apply incoming request modification!", ex); } try { // receiving notify message without subscription state // used for keep-alive pings, they have done their job // and are no more need. Skip processing them to avoid // filling logs with unneeded exceptions. if(request.getMethod().equals(Request.NOTIFY) && request.getHeader(SubscriptionStateHeader.NAME) == null) { return true; } } catch(Throwable ex) { logger.warn("Cannot apply incoming request modification!", ex); } return false; } /** * List of currently waiting timers that will monitor the protocol provider * */ Map resetListeningPointsTimers = new HashMap(); /** * Listens for network changes and if we have a down interface * and we have a tcp/tls provider which is staying for 20 seconds in * unregistering state, it cannot unregister cause its using the old * address which is currently down, and we must recreate its listening * points so it can further reconnect. * * @param event the change event. */ public void configurationChanged(ChangeEvent event) { if(event.isInitial()) return; if(event.getType() == ChangeEvent.ADDRESS_DOWN) { for(final ProtocolProviderServiceSipImpl pp : listeners) { if(pp.getRegistrarConnection().getTransport() != null && (pp.getRegistrarConnection().getTransport() .equals(ListeningPoint.TCP) || pp.getRegistrarConnection().getTransport() .equals(ListeningPoint.TLS))) { ResetListeningPoint reseter; synchronized(resetListeningPointsTimers) { // we do this only once for transport if(resetListeningPointsTimers.containsKey( pp.getRegistrarConnection().getTransport())) continue; reseter = new ResetListeningPoint(pp); resetListeningPointsTimers.put( pp.getRegistrarConnection().getTransport(), reseter); } pp.addRegistrationStateChangeListener(reseter); } } } } /** * If a tcp(tls) provider stays unregistering for a long time after * connection changed most probably it won't get registered after * unregistering fails, cause underlying listening point are conncted * to wrong interfaces. So we will replace them. */ private class ResetListeningPoint extends TimerTask implements RegistrationStateChangeListener { /** * The time we wait before checking is the provider still unregistering. */ private static final int TIME_FOR_PP_TO_UNREGISTER = 20000; /** * The protocol provider we are checking. */ private final ProtocolProviderServiceSipImpl protocolProvider; /** * Constructs this task. * @param pp */ ResetListeningPoint(ProtocolProviderServiceSipImpl pp) { this.protocolProvider = pp; } /** * Notified when registration state changed for a provider. * @param evt */ public void registrationStateChanged(RegistrationStateChangeEvent evt) { if(evt.getNewState() == RegistrationState.UNREGISTERING) { new Timer().schedule(this, TIME_FOR_PP_TO_UNREGISTER); } else { protocolProvider.removeRegistrationStateChangeListener(this); resetListeningPointsTimers.remove( protocolProvider.getRegistrarConnection().getTransport()); } } /** * The real task work, replace listening point. */ @Override public void run() { // if the provider is still unregistering it most probably won't // successes until we re-init the LP if(protocolProvider.getRegistrationState() == RegistrationState.UNREGISTERING) { String transport = protocolProvider.getRegistrarConnection() .getTransport(); ListeningPoint old = getLP(transport); try { stack.deleteListeningPoint(old); } catch(Throwable t) { logger.warn("Error replacing ListeningPoint for " + transport, t); } try { ListeningPoint tcpLP = stack.createListeningPoint( NetworkUtils.IN_ADDR_ANY , transport.equals(ListeningPoint.TCP)? getPreferredClearPort(): getPreferredSecurePort() , transport); clearJainSipProvider.addListeningPoint(tcpLP); } catch(Throwable t) { logger.warn("Error replacing ListeningPoint for " + protocolProvider.getRegistrarConnection().getTransport(), t); } } resetListeningPointsTimers.remove( protocolProvider.getRegistrarConnection().getTransport()); } } }