Real-time Communications (RTC) 1.5 Port Manager

The updates made in RTC 1.5 includes Port Manager API extensions. This blog tries to explain how RTC 1.5 Port Manager can be used. As examples, extendable code samples are also included.

 

Port Manager Overview:

The blog assumes that the reader has a basic knowledge of Network Address Translation (NAT) and RTC APIs.

Based on SIP RFC 3261, SIP messages exchanging URI contains contact header which includes IP address (local/public) and port of the sender (RTC Client) where the sender expects further requests to be sent to. Also the IP address and port that the sender is waiting to receive media on are included in the SDP of each media offer/answer (RFC 3264).

When going through a NAT, the internal IP address that is currently assigned to the interface of the RTC Client may not be publicly routable. In this case, the RTC Client needs to learn of the external (publicly routable NAT’s) IP address and port, in order that this can go into the Contact Header/SDP. These ports will also have to be negotiated with the NAT, such that all the traffic can be forwarded from the NAT back to the client on the correct port.

The Port Manager APIs provide a way for the application layer code to implement a call-back Port Manager interface. The methods of this call-back interface are called by RTC Client to obtain the publicly routable address for unobstructed communication between clients bridged by a NAT.

 

Overview of the APIs:

The Port Manager APIs facilitate RTC Client to communicate with external (to NAT) devices for outgoing/incoming calls, registration, subscription/notification services, IM, etc.

The four main Port Manager COM interfaces that can be used to overcome NAT traversal issues are:

- IRTCClientPortManagement (same as in RTC 1.3)

- IRTCSessionPortManagement (same as in RTC 1.3)

- IRTCPortManager3 (newly extended in RTC 1.5)

o The methods in this extension includes dependency on the remote client’s port as well as the transport used, besides the existing dependency on the address pair.

- IRTCProfilePortManagement (newly added in RTC 1.5)

o This interface exposes a method similar to the one in IRTCSessionPortManagement (SetPortManager()) but for Profile.

 

IRTCPortManager3 API:

The prototypes for the newly added methods are listed below:

 

HRESULT GetMappingEx( BSTR bstrRemoteAddress, // Remote Client IP

long lRemotePort, // Remote Client Port

long lTransport, // TCP/UDP/TLS

RTC_PORT_TYPE enPortType, // Port Type

BSTR *pbstrInternalLocalAddress, // Internal IP

long *plInternalLocalPort, // Internal Port

BSTR *pbstrExternalLocalAddress, // NAT’s mapped IP

long *plExternalLocalPort ); // NAT’s mapped port

HRESULT ReleaseMappingEx( long lTransport, // TCP/UDP/TLS

BSTR bstrInternalLocalAddress, // Internal IP

long lInternalLocalPort, // Internal Port

BSTR bstrExternalLocalAddress, // NAT’s mapped IP

long lExternalLocalAddress ); // NAT’s mapped Port

HRESULT UpdateRemoteAddressEx2( BSTR bstrRemoteAddress, // Remote Client IP

long lRemotePort, // Remote Client Port

long lTransport, // TCP/UDP/TLS

BSTR bstrInternalLocalAddress, // Internal IP

long lInternalLocalPort, // Internal Port

BSTR bstrExternalLocalAddress, // NAT’s mapped IP

long lExternalLocalPort); // NAT’s mapped Port

 

Responsibility of the Application Using Port Manager API:

Depending on the type of NAT, maintaining a mapping for a particular pair of addresses and transport protocol type is the main configuration consideration.

For example some NATs provide a way to statically configure mapping. If this kind of pre-configuration is decided to be used, then the application can implement the Port Manager call-back interface to return the addresses that are pre-configured (and mapped) to RTC Client whenever the call-back interface methods are called. Another possibility would be for the application to implement STUN (RFC 3489) and dynamically provide RTC Client the required addresses.

The main application responsibilities include:

- Implementing the methods for the call-back interface IRTCPortManager3.

- Having either a pre-configured mapping or a mechanism like STUN to discover and dynamically maintain a mapping, that these methods provide as external mapping to RTC Client.

- Configuring the RTC Session with the call-back interface before establishing a session.

- Configuring the RTC Profile with the call-back interface before registering to a registrar.

 

Registration Example:

In the scenario represented by the following steps, the application registers an RTC Client with a registrar from behind a NAT.

- The application creates a profile to be registered using IRTCClientProvisioning::CreateProfile.

- The application queries Profile object for IRTCProfilePortManagement interface.

- The application calls IRTCProfilePortManagement::SetPortManager to set its IRTCPortManager3 interface implementation (say AppPortManager3) for this session.

- The RTC Client API calls the provided AppPortManager3 methods to retrieve the external IP address and port pairs on the NAT and the internal port and address on the client machine. It uses these port and address pairs for exchanging registration SIP messages with the registrar.

 

//-------------------------------

// Registration from behind a NAT

//-------------------------------

IRTCClientProvisioning *pIRTCClientProvisioning = NULL;

IRTCProfile *pIRTCProfile = NULL;

BSTR bstrXMLProfile = // XML Blob referenced in different section

IRTCProfilePortManagement *pProfilePortManagement;

CComObject<AppPortManager3>* pIRTCPortManager3 = NULL; // App’s impl. of IRTCPortManager3

IRTCClient *pIRTCClient;

// Create a COM Object of AppPortManager3.

hr = CComObject<AppPortManager3>::CreateInstance(&pIRTCPortManager3);

// If (hr != S_OK), process the error here.

// CoCreate and initialize IRTCClient to pIRTCClient. See RTC Initialize.

// Perform QI for the Provisioning interface.

hr = pIRTCClient->QueryInterface( IID_IRTCClientProvisioning,

reinterpret_cast<void **>(&pIRTCClientProvisioning) );

// If (hr != S_OK), process the error here.

// Create the Profile object.

hr = pIRTCClientProvisioning->CreateProfile( bstrXMLProfile,

       &pIRTCProfile );

// If (hr != S_OK), process the error here.

// Query for the IRTCProfilePortManagement interface.

hr = pIRTCProfile->QueryInterface( IID_IRTCProfilePortManagement),

(void **) &pProfilePortManagement );

// If (hr != S_OK), process the error here.

//Set the port manager call-back interface on the profile.

hr = pProfilePortManagement->SetPortManager(pIRTCPortManager3);

// Release the pointer to IRTCProfilePortManagement

// because we no longer need it.

...

// Enable the Profile and Register.

hr = pIRTCClientProvisioning->EnableProfile( pIRTCProfile, RTCRF_REGISTER_ALL);

// If (hr != S_OK), process the error here.

// RTC calls into the AppPortManager3::GetMappingEx()

// method to get the external SIP address and port.

// Here the application could answer back either with

// pre-configured external mapping details or could

// discover the external address and mapping using STUN.

...

Outgoing Call Example:

The steps in this scenario are same as explained in Client Application Behind NAT. The only changes are the usage of the extended call-back interface.

 

//--------------------------------

// Outgoing call from behind a NAT

//--------------------------------

HRESULT hr = S_OK;

BSTR bstrLocalURI = NULL;

BSTR bstrDestURI = NULL;

RTC_SESSION_TYPE enSessionType;

IRTCSession *pIRTCSession = NULL;

IRTCSessionPortManagement *pSessionPortManagement;

CComObject<AppPortManager3>* pIRTCPortManager3 = NULL; // App’s impl. of IRTCPortManager3

IRTCProfile *pIRTCProfile = NULL;

IRTCClient *pIRTCClient;

enSessionType = // Specify session type

// Get the URI to call; this can be a sip: or a tel: URI.

bstrDestURI = ::SysAllocString(L"sip:someone@microsoft.com");

// Create a COM Object of AppPortManager3.

hr = CComObject<AppPortManager3>::CreateInstance(&pIRTCPortManager3);

// If (hr != S_OK), process the error here.

// The profile for pIRTCProfile is setup as explained in the

// example for registration!

// CoCreate and initialize IRTCClient to pIRTCClient. See RTC Initialize.

// Create the session with the specified session type

// and local URI.

hr = pIRTCClient->CreateSession( enSessionType, bstrLocalURI, pIRTCProfile,

RTCCS_FORCE_PROFILE, &pIRTCSession );

// If (hr != S_OK), process the error here.

// Query for the IRTCSessionPortManagement interface.

hr = pIRTCSession->QueryInterface( IID_IRTCSessionPortManagement,

(void **)&pSessionPortManagement );

// If (hr != S_OK), process the error here.

//Set the port manager on the session.

// Note that the same Port Manager Call-Back Interface can be used for

// both registration and Session establishment.

hr = pSessionPortManagement->SetPortManager(pIRTCPortManager3);

// Release the pointer to IRTCSessionPortManagement

// because we no longer need it.

...

IRTCParticipant *pIRTCParticipant = NULL;

hr = pIRTCSession->AddParticipant(bstrDestURI, NULL, &pIRTCParticipant);

// If (hr != S_OK), process the error here.

// RTC calls into the AppPortManager3::GetMappingEx() method multiple

// times to get the SIP and Media mapped addresses and ports.

// Note that RTC Client calls this method for each Port Type (SIP, RTP, RTCP)

// so the call-back interface should be ready to provide a mapped external

// address/port pair for each call.

// The application could answer back either with pre-configured external

// mapping details or could discover the external address and mapping using STUN.

...

// Free all the strings allocated with SysFreeString().

...