/**
   * 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 <tt>ListeningPoint</tt>s and <tt>SipProvider</tt>s.
   */
  @SuppressWarnings("unchecked") // jain-sip legacy code
  private void stopListening() {
    try {
      this.secureJainSipProvider.removeSipListener(this);
      this.stack.deleteSipProvider(this.secureJainSipProvider);
      this.secureJainSipProvider = null;
      this.clearJainSipProvider.removeSipListener(this);
      this.stack.deleteSipProvider(this.clearJainSipProvider);
      this.clearJainSipProvider = null;

      Iterator<ListeningPoint> it = this.stack.getListeningPoints();
      Vector<ListeningPoint> lpointsToRemove = new Vector<ListeningPoint>();
      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);
    }
  }
 /**
  * Adds this <tt>listener</tt> as a candidate recipient for the dispatching of new messages
  * received from the JAIN-SIP <tt>SipProvider</tt>s.
  *
  * @param listener a new possible target for the dispatching process.
  * @throws OperationFailedException if creating one of the underlying <tt>SipProvider</tt>s 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 <tt>listener</tt> will no longer be a candidate recipient for the dispatching of new
   * messages received from the JAIN-SIP <tt>SipProvider</tt>s.
   *
   * @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();
    }
  }
  /**
   * Retrieves and returns that ProtocolProviderService that this transaction belongs to, or
   * <tt>null</tt> 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 <tt>ProtocolProviderServiceSipImpl</tt> that <tt>transaction</tt>
   *     was associated with or <tt>null</tt> 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;
  }
  /**
   * Dispatches the event received from a JAIN-SIP <tt>SipProvider</tt> to one of our "candidate
   * recipient" listeners.
   *
   * @param event the event received for a <tt>SipProvider</tt>.
   */
  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);
    }
  }
Exemple #6
0
  /**
   * Receives options requests and replies with an OK response containing methods that we support.
   *
   * @param requestEvent the incoming options request.
   * @return <tt>true</tt> if request has been successfully processed, <tt>false</tt> otherwise
   */
  @Override
  public boolean processRequest(RequestEvent requestEvent) {
    Response optionsOK = null;
    try {
      optionsOK =
          provider.getMessageFactory().createResponse(Response.OK, requestEvent.getRequest());

      // add to the allows header all methods that we support
      for (String method : provider.getSupportedMethods()) {
        // don't support REGISTERs
        if (!method.equals(Request.REGISTER))
          optionsOK.addHeader(provider.getHeaderFactory().createAllowHeader(method));
      }

      Iterable<String> knownEventsList = provider.getKnownEventsList();

      synchronized (knownEventsList) {
        for (String event : knownEventsList)
          optionsOK.addHeader(provider.getHeaderFactory().createAllowEventsHeader(event));
      }
    } catch (ParseException ex) {
      // What else could we do apart from logging?
      logger.warn("Failed to create an incoming OPTIONS request", ex);
      return false;
    }

    try {
      SipStackSharing.getOrCreateServerTransaction(requestEvent).sendResponse(optionsOK);
    } catch (TransactionUnavailableException ex) {
      // this means that we received an OPTIONS request outside the scope
      // of a transaction which could mean that someone is simply sending
      // us b****hit to keep a NAT connection alive, so let's not get too
      // excited.
      if (logger.isInfoEnabled())
        logger.info("Failed to respond to an incoming " + "transactionless OPTIONS request");
      if (logger.isTraceEnabled()) logger.trace("Exception was:", ex);
      return false;
    } catch (InvalidArgumentException ex) {
      // What else could we do apart from logging?
      logger.warn("Failed to send an incoming OPTIONS request", ex);
      return false;
    } catch (SipException ex) {
      // What else could we do apart from logging?
      logger.warn("Failed to send an incoming OPTIONS request", ex);
      return false;
    }

    return true;
  }
  /**
   * 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 <tt>ListeningPoint</tt>s
   * and <tt>SipProvider</tt>s.
   *
   * @throws OperationFailedException if creating one of the underlying <tt>SipProvider</tt>s 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);
    }
  }
  /**
   * Dispatches the event received from a JAIN-SIP <tt>SipProvider</tt> to one of our "candidate
   * recipient" listeners.
   *
   * @param event the event received for a <tt>SipProvider</tt>.
   */
  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!");
        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 "
                + "*****@*****.**");
      }
    } 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 <tt>SipProvider</tt> to one of our "candidate
  * recipient" listeners.
  *
  * @param event the event received for a <tt>SipProvider</tt>.
  */
 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 " + "*****@*****.**");
     } 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);
   }
 }
  /**
   * 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();
      sipFactory.setPathName("org.jitsi.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);
    }
  }
  /**
   * Find the <tt>ProtocolProviderServiceSipImpl</tt> (one of our "candidate recipient" listeners)
   * which this <tt>request</tt> 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 <tt>Request</tt> as param, and not a <tt>ServerTransaction</tt>, because
   * sometimes <tt>RequestEvent</tt>s have no associated <tt>ServerTransaction</tt>.
   *
   * @param request the <tt>Request</tt> to find a recipient for.
   * @return a suitable <tt>ProtocolProviderServiceSipImpl</tt>.
   */
  private ProtocolProviderServiceSipImpl findTargetFor(Request request) {
    if (request == null) {
      logger.error("request shouldn't be null.");
      return null;
    }

    List<ProtocolProviderServiceSipImpl> currentListenersCopy =
        new ArrayList<ProtocolProviderServiceSipImpl>(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<ProtocolProviderServiceSipImpl> candidates =
          new ArrayList<ProtocolProviderServiceSipImpl>();

      // 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;
  }
  /**
   * Dispatches the event received from a JAIN-SIP <tt>SipProvider</tt> to one of our "candidate
   * recipient" listeners.
   *
   * @param event the event received for a <tt>SipProvider</tt>.
   */
  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 [email protected]",
              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;
    }
  }
  /**
   * Attach JAIN-SIP <tt>SipProvider</tt> and <tt>ListeningPoint</tt> to the stack either for clear
   * communications or TLS. Clear UDP and TCP <tt>ListeningPoint</tt>s are not handled separately as
   * the former is a fallback for the latter (depending on the size of the data transmitted). Both
   * <tt>ListeningPoint</tt>s must be bound to the same address and port in order for the related
   * <tt>SipProvider</tt> to be created. If a UDP or TCP <tt>ListeningPoint</tt> 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 <tt>SipProvider</tt> is already associated with this
   *     <tt>ListeningPoint</tt>.
   * @throws TransportAlreadySupportedException if there is already a ListeningPoint associated to
   *     this <tt>SipProvider</tt> with the same transport of the <tt>ListeningPoint</tt>.
   * @throws TooManyListenersException if we try to add a new <tt>SipListener</tt> with a
   *     <tt>SipProvider</tt> 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) {
      // makes sure we didn't leave an open listener
      // as both UDP and TCP listener have to bind to the same port
      if (tlsLP != null) this.stack.deleteListeningPoint(tlsLP);
      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;
    }
  }