Modifying SIP headers using the Managed SIP Application API

The Lync Server SDK includes an API, the Managed SIP Application API, which is essentially a more powerful and complex managed-code cousin of MSPL (Microsoft SIP Processing Language). It allows you to create applications that, much like MSPL scripts, reside on a Front End Server or one of the other Lync servers (Edge, Mediation, etc.). Unlike MSPL, however, these are created using managed code. The classes that make up the API, which are contained in the Microsoft.Rtc.Sip namespace, allow you to modify SIP messages and control message routing in a number of ways that are not possible with MSPL. This post assumes you have a basic understanding of MSPL, so if you haven''t worked with MSPL at all you may want to start by reading my previous posts on it,which you can find here.

How Managed SIP Applications Work

These applications start up in a slightly different way from the script-only applications that you can create with MSPL. For MSPL applications,there is a service that loads in all of the MSPL applications that have been registered, compiles them, and runs them. This is RtcSpl.exe, or the Lync Server Script-Only Applications Service. With an application built with the Managed SIP Application API (should we call it MSAA?) you need to perform these steps yourself.

The managed SIP application consists of a few things. There is an executable of some kind, which can be a console app, a Windows service, a WinForms app, whatever. Within the executable there must be a class with methods that handle SIP messages using the classes from Microsoft.Rtc.Sip. There must also be an application manifest file, which, as with script-only MSPL applications, identifies to Lync Server which messages the application will take. This manifest also must contain an MSPL script which acts as a sort of "first line of defense," deciding which messages are important enough to be sent over to your managed code.

When the executable starts up, it loads in and compiles the application manifest file, creates an instance of your handler class, and establishes a connection with Lync Server. It then waits for messages to come through.

The Application Manifest

I''ll start by showing a very simple application manifest that you might use with a SIP application. By the way, there is a collection of sample applications that come along with the Lync Server 2010 SDK, and I highly recommend looking at these as well; they don''t come with a whole lot of documentation, but they''ll give you some ideas of what you can do with the API. If you used the default install path for the SDK, you can find them at C:Program FilesMicrosoft Lync Server 2010SDKSamples.

Here''s the content of my application manifest, which I''ve called

[xml] <?xml version="1.0"?> <r:applicationManifest r:appUri="" xmlns:r=""> <r:requestFilter methodNames="INVITE" strictRoute="true" domainSupported="false"/> <r:responseFilter reasonCodes="ALL"/>

<r:splScript><![CDATA[ if (sipRequest) { Dispatch("OnRequest"); } else { Dispatch("OnResponse"); } ]]></r:splScript> </r:applicationManifest> [/xml]

As you can see, this application manifest indicates that the application will handle SIP INVITE requests, and all SIP responses. The script itself is extremely simple, and calls the Dispatch method with a different parameter depending on whether the application is handling a request or a response.

The Dispatch method in MSPL is a gateway of sorts to the managed code portion of the application, which we''ll look at in a moment. It passes the SIP message to the corresponding method in your handler class. The script above is about as basic as you can get, simply passing requests and responses to different managed code methods. For this example I''m keeping things simple, but I''ll quickly call out a few modifications you could make here to make the script more useful.

For one thing, dispatching a message to managed code is a relatively expensive operation from a performance standpoint, so you want to minimize the frequency with which you have to do it. You definitely want to avoid dispatching any messages which your application isn''t going to do anything with. Let''s say, for instance, that your application modifies a particular SIP header on the message if it''s there. Before dispatching the message, you should check that the header is present. This way you can only dispatch messages that have the SIP header, and avoid unnecessary performance degradation. You might also be looking for messages from or to a particular SIP URI, which again you can check in MSPL. There are also some things, such as adding SIP headers, that you may be able to do entirely in MSPL, so you can avoid involving the managed code.

You can also dispatch messages to different managed code methods depending on conditions you check in the MSPL script. For example, you might have separate methods for messages from internal users vs. external or PSTN users.

Finally, you can pass an unlimited number of parameters to the Dispatch method. These go after the method name, and can be nearly anything that converts into a string. So you can do something like this (assuming you''ve stored something in variables called data1, data2, and data3):

[csharp] Dispatch("HandleMessage", data1, data2, data3); [/csharp]

The Executable

The other piece to the application, as I mentioned earlier, is an executable. The easiest way to start out is just to create a console app project in Visual Studio, but you could use a Windows Service as well. You''ll need to add a reference to ServerAgent.dll, which is installed by default at C:Program FilesMicrosoft Lync Server 2010SDKBinServerAgent.dll.

Next, let''s take a look at a handler class in managed code. This one doesn''t do anything very exciting, but it''s easy to understand. For requests, it first looks for the Ms-Sensitivity header and removes it if it''s there. For both requests and responses, it adds a ModifyHeadersSample header with the host name of the server. Note that we can end up with multiple instances of this header if the script runs on more than one server.

Here''s the class:

[csharp] using System.Net; using Microsoft.Rtc.Sip;

namespace ModifyHeadersSample { public class ModifyHeaders { public void OnRequest(object sender, RequestReceivedEventArgs e) { // Enable simple proxy mode and disable forking. e.Request.SimpleProxy = true; e.ServerTransaction.EnableForking = false;

// Get a collection of all headers on the request. HeaderCollection headers = e.Request.AllHeaders;

// Find and remove an Ms-Sensitivity header if present. Header sensitivity = headers.FindFirst("Ms-Sensitivity"); if (sensitivity != null) { headers.Remove(sensitivity); }

// Add a ModifyHeadersSample header. Header newHeader = new Header("ModifyHeadersSample", Dns.GetHostEntry("localhost").HostName); headers.Add(newHeader);

// Send the request along. e.ServerTransaction.CreateBranch().SendRequest( e.Request); }

public void OnResponse(object sender, ResponseReceivedEventArgs e) { // Get a collection of all headers on the response. HeaderCollection headers = e.Response.AllHeaders;

// Add a ModifyHeadersSample header. Header newHeader = new Header("ModifyHeadersSample", Dns.GetHostEntry("localhost").HostName); headers.Add(newHeader);

// Send the response along. e.ClientTransaction.ServerTransaction.SendResponse( e.Response); } } } [/csharp]

I won''t explain every bit of this script, since some of it is fairly self-explanatory, although I will go into more detail on the classes and methods available in a future post. But I do want to draw attention to a few parts that may not be so clear. First, let''s look at this bit here:

[csharp] // Enable simple proxy mode and disable forking. e.Request.SimpleProxy = true; e.ServerTransaction.EnableForking = false; [/csharp]

The first part deals with the Request class, which represents the SIP request itself. It sets a property called SimpleProxy to true. Unfortunately, I haven''t been able to find any documentation on this property, so I can''t give you the full details on what it does, but I know from looking through sample code that it helps improve performance when turned on. My guess would be that you can only use it if you''re not really modifying the routing of the message, and you''re simply changing or inspecting the message itself. I''ve turned it on here since we''re not doing anything with routing in this application.

The second part is to say that forking (sending the message to two possible destinations) is disabled for this transaction. We''ll simply be passing the message along to wherever it was already going.

After that we do some SIP header manipulation, and then there is this bit of code:

[csharp] // Send the request along. e.ServerTransaction.CreateBranch().SendRequest( e.Request); [/csharp]

Essentially what we''re doing here is taking the server transaction (the transaction where we are acting as the server, receiving the request) and using it to create a client transaction (one where we''re acting as the client, sending the request along somewhere else). Then we''re calling the SendRequest method on that ClientTransaction object to send the request along to its destination.

The OnResponse method is a simpler version of the same -- in this case, the client and server parts are reversed because we''re receiving a response (as the client) and sending the same response back to the origin (as the server).

Let me say that another way to make sure I''m being clear. For each request-response pair, the application gets to wear two hats: the server hat and the client hat.

When a request first comes in, the app is wearing its server hat. It takes the message and does something with it. Then it puts on its client hat and sends it along to another user. That user sends back a response, which the app receives, still wearing its client hat. Then it switches back to its server hat and sends the response along to the original sender.


Sorry for the awful hat; I couldn''t find any clip art.

We need one last thing to run this application: a Program class to be the entry point for our executable. Here''s an example:

[csharp] using System; using System.Threading; using Microsoft.Rtc.Sip;

namespace ModifyHeadersSample { public class Program { static void Main(string[] args) { ModifyHeaders serverApplication = new ModifyHeaders();

try { // Try to connect to the server 5 times. ServerAgent.WaitForServerAvailable(5); } catch (Exception ex) { Console.WriteLine(ex); }

Environment.CurrentDirectory = System.AppDomain.CurrentDomain.BaseDirectory;

// Load the app manifest from a file. ApplicationManifest manifest = ApplicationManifest.CreateFromFile(""); try { // Try to compile the manifest. manifest.Compile(); } catch (CompilerErrorException ex) { Console.WriteLine(ex); }

ServerAgent agent = null; try { // Create the new server agent object, setting // the ModifyHeaders object as the handler for messages. agent = new ServerAgent(serverApplication, manifest); } catch (Exception ex) { Console.WriteLine(ex); }

if (agent != null) { Console.WriteLine("Server application started."); while (true) { // Wait for a message to arrive and then handle it. agent.WaitHandle.WaitOne(); ThreadPool.QueueUserWorkItem( new WaitCallback(agent.ProcessEvent)); } } else { Console.WriteLine("Server application failed to start."); } } } } [/csharp]

Nothing shocking here -- it just waits for the server to be available, compiles the app manifest, and then connects to the server using a new instance of our handler class and the manifest. If you look at the samples that come with the SDK, there is a utility class in there that does some of this for you, which you may want to use.

Building, Installing, and Testing the Application

At this point, the application is ready to be built. Make sure you target x64 or Any CPU as the platform, not x86, or your application won''t work.

Move the compiled application, along with the manifest file, to a Lync Front End Server. (I probably don''t need to say this, but please don''t try this on a production Lync server -- you can seriously screw things up. Use an isolated test environment with no real users.) You can put it pretty much anywhere on the server; it doesn''t matter where the executable is located.

The next very important step is to add the user under whose identity the application will run to the RTC Server Application local group on the Front End Server. If you don''t do this, your application will crash with an UnauthorizedException.

Last but not least, we need to register the application with Lync Server. You can do this with the New-CsServerApplication PowerShell command. You would enter something like this:

[ps] New-CsServerApplication -Identity Service:Registrar:lync-se.domain.local/ModifyHeaders -Uri -Enabled $true -Critical $false [/ps]

The idea is the same as with MSPL script-only apps, so you can refer back to those instructions if you''re not sure how to do this. The only difference is that you don''t specify any value for the ScriptName parameter. That parameter is specifically for script-only applications and if you put a value in here, Lync will treat your application as script-only and get confused.

Once all of this is done, cross your fingers and run the .exe. (It''s usually easier to run from an existing console window so you can see any exceptions that get spit out if the app fails.) If all goes well, it will pause for a moment and then print a message to the console saying it has started.

You can double-check that the application has connected to Lync Server by going into Event Viewer and looking for an event like the one I''ve selected here:

If all is well at this point, you''re pretty much done. Start a logging session on your Front End Server, then open up the Lync client and send an IM to another user. Stop the log and look at the messages. You should see the ModifyHeadersSample header tacked on to the INVITE when it is going outbound from the Front End Server.

This has been a very quick overview of the Managed SIP Application API, but should at least start you out if you are curious about using it for your own development. Stay tuned for future posts that delve into more specific topics on how to use the API.