예제 #1
0
bool CallTracker::notifyIncomingDialogFormingInvite( SipMessage& request, RouteState& routeState, const EndpointDescriptor*& prCaller, const EndpointDescriptor*& prCallee )
{
   bool                bResult = false;
   UtlString           sessionContextHandle;
   SessionContext*     pSessionContext;

   // We need to process and incoming dialog-forming request.  In some scenarios, it is possible
   // for such a request to visit us twice.  If there is no session-id encoded in the RouteState,
   // it indicates that this is the first time we see it.
   if( !getSessionContextHandle( routeState, sessionContextHandle ) )
   {
      // we are not tracking this session yet.  Allocate a new session context to track
      // this particular fork and save a copy of the message's SDP body if not already done - this
      // may be useful if the request spirals back to us and we need to restore a patched
      // SDP to its original form.
      pSessionContext = createSessionContextAndSetHandle( request, routeState, true, sessionContextHandle );
      if( pSessionContext )
      {
         prCaller = &pSessionContext->getEndpointDescriptor( CALLER );
         prCallee = &pSessionContext->getEndpointDescriptor( CALLEE );
         if( !mpSavedOriginalSdpOfferCopy )
         {
            mpSavedOriginalSdpOfferCopy = const_cast<SdpBody*>( request.getSdpBody() );
         }
         bResult = true;
      }
      else
      {
         Os::Logger::instance().log( FAC_NAT, PRI_ERR, "CallTracker[%zd]::notifyIncomingDialogFormingInvite[1] failed to create session context ",
                        mHandle );
      }
   }
   else
   {
      // the RouteState already has a session-id which means that we are already tracking this
      // session.  There are several scenarios that can lead up to that situation.  Consider this:
      //
      // User A--------sipXecs1-----------sipXecs2-------User C(call forwarded to User B)
      // User B---+
      // User B---+
      //
      // Users A & B are registered against sipXecs1 and User C is registered againt sipXecs2.
      // User B is registered from two endpoints.
      // User C has its sipXecs-based call forward all calls set to SetB@sipXecs1.
      // When User A calls User C, the INVITE takes the following path:
      //           UserA-->sipXecs1-->sipXecs2-->sipXecs1-->UserB
      //                                                 +->UserB
      // A SessionContext is created when sipXecs1 is visited the first time and its session-id
      // is encoded in the RouteState.  When sipXecs1 is revisited by the INVITE it sees that
      // the RouteState already carries a session-id which tells it that this is a revisiting
      // INVITE.

      // If no special handling is performed here, several problems can appear.
      //
      // Problem #1- callee's real location wasn't known when SessionContext was created on first visit
      // ==============================================================================================
      //     When the dialog-forming INVITE was first seen by sipXecs1, the target was UserC@sipxecs2.
      //     Given that the call was routed via a SIP trunk, sipXecs1 didn't know the real location of the
      //     callee and therefore pegged the callee a being at an UNKNOWN location and imposed the use
      //     of a media relay using the media relay's public IP adddress as the media connection address
      //     in the SDP presented to the callee.
      //     When the dialog-forming INVITE comes back to sipxecs1,  the request target of the INVITE, namely
      //     UserB@sipxecs1, is known to sipXecs1 and can therefore promote its location information
      //     from UNKNOWN to its precise location based on the information it collected from the set
      //     at registration time.  The SessionContext that was initially created by the CallTracker to
      //     handle the NAT traversal was created at the time when the location of the callee was unknown.
      //     Now that the location is known, the old SessionContext can be abandonned and a new one created
      //     that will choose whether or not to involve a media relay based on an accurate representation
      //     of the caller and callee's locations.
      //
      // Solution to Problem #1
      // ======================
      // - Restore the SDP to its original state (i.e. before any transformation) if it got changed by us.
      // - Allocate a new SessionContext that will be responsible handling all the dialogs forked off
      //   of this INVITE using the updated callee location information.
      // - Remove any 'id' param containing the handle of the original SessionContext in the Vias
      //   of the request.  This procedure will prevent the original SessionContext from handling
      //   responses pertaining to the newly created SessionContext.
      pSessionContext = getSessionContextFromHandle( sessionContextHandle );
      if( pSessionContext )
      {
         // First, allocate a new SessionContext that will take care of this new fork.
         UtlString handleOfnewSessionHandle;
         pSessionContext = createSessionContextAndSetHandle( request, routeState, true, handleOfnewSessionHandle );
         if( pSessionContext )
         {
            prCaller = &pSessionContext->getEndpointDescriptor( CALLER );
            prCallee = &pSessionContext->getEndpointDescriptor( CALLEE );
            bResult = true;

            // Second, restore the SDP to its original form as saved by the original SessionContext
            if( mpSavedOriginalSdpOfferCopy )
            {
               request.setBody( mpSavedOriginalSdpOfferCopy->copy() );
            }

            // third, remove the handle of the SessionContext being replaced in the Vias
            removeSessionHandleFromVias( request, sessionContextHandle );
         }
         else
         {
            Os::Logger::instance().log( FAC_NAT, PRI_ERR, "CallTracker[%zd]::notifyIncomingDialogFormingInvite[2] failed to create session context ",
                           mHandle );
         }
      }

      // Problem #2- sipXecs2 may not know how to reach UserB directly
      // =============================================================
      //     As we can see in the example above, the sipXecs1 is visited twice but the SipRouter
      //     logic is such that a Record-Route is added the first time it sees the request and
      //     not the others.  This means that when the request arrives at UserB's sets it will
      //     have Record-route: <sip:sipXecs2>,<sip:sipXecs1>. That particular arrangement
      //     means that although sipXecs1 was the proxy that routed the dialog-forming
      //     request to UserB's sets, sipXecs2 will be responsible for routing all subsequent in-dialog
      //     requests.  Since UserB's sets are registered against sipXecs1 only, it is the only
      //     proxy that truly knows the public and private IP address information of UserB's sets
      //     and therefore the only one that can successfully deliver requests to them accross NATs.
      //     Even if sipXecs2 could somehow "learn" the sets public IP addresses, if UserB's set happened to
      //     be behind a non-full cone NAT  sipXecs2 will not have the ability to send requests to
      //     that set as they will be rejected by the NAT because pinholes exist between the set and sipXecs2
      //
      // Solution to Problem #2
      // ======================
      // To solve this problem, the following piece of logic will ask the RouteState to add a copy of the
      // Record-Route header when it is updated in cases where sipXproxy is not already at the top of the
      // route set.
      UtlString topRecordRoute;
      if( routeState.isFound() )
      {
         if( request.getRecordRouteUri( 0, &topRecordRoute ) )
         {
            Url topRecordRouteUrl( topRecordRoute );
            UtlString topRecordRouteHost;
            topRecordRouteUrl.getHostAddress( topRecordRouteHost );
            int topRecordRoutePort = topRecordRouteUrl.getHostPort();

            if( ( topRecordRouteHost != mpNatTraversalRules->getProxyTransportInfo().getAddress() ||
                  topRecordRoutePort != mpNatTraversalRules->getProxyTransportInfo().getPort() ) &&
                ( topRecordRouteHost != mpNatTraversalRules->getPublicTransportInfo().getAddress() ||
                  topRecordRoutePort != mpNatTraversalRules->getPublicTransportInfo().getPort() ) )
            {
               routeState.addCopy();
            }
         }
      }
   }

   // If the method successfully completed and if the caller is a remote worker then add it to the
   // list of endpoints whose NAT pinholes need to be kept alive for the duration of the call.
   // NOTE: the called party is already handled the NatMaintainer's RegDB lookups.
   if( bResult == true )
   {
      if( prCaller->getLocationCode() == REMOTE_NATED &&
          prCaller->getPublicTransportAddress().getTransportProtocol().compareTo( "udp", UtlString::ignoreCase ) == 0 &&
          !mpCallerPinholeInformation &&
          mpNatMaintainer )
      {
         mpCallerPinholeInformation = new TransportData( prCaller->getPublicTransportAddress().getAddress(),
                                                         prCaller->getPublicTransportAddress().getPort() );

         mpNatMaintainer->addEndpointToKeepAlive( *mpCallerPinholeInformation );
      }
   }
   return bResult;
}