.NET remoting enables application communication. It is a generic system that different applications can use to communicate with one another. .NET objects are exposed to remote processes, thus allowing interprocess communication. The applications can be located on the same computer, different computers on the same network, or even computers across separate networks.
Remoting communication is not secure by default, however. The 1.0 and 1.1 versions of the Microsoft Framework offered two options for making it secure:
- Use the HttpChannel, host the remoting server inside of IIS, and use SSL to secure communication between the client and server.
- Do something similar to this example from Microsoft, which is complicated, not supported, and requires you to create custom code.
The 2.0 version of the Microsoft Framework provides additional security-related functionality within the System.Runtime.Remoting.Channels.Tcp namespace. The TCP communication now can be secured either through code or the application configuration files. The classes support encryption and authentication using the Security Support Provider Interface (SSPI). It relies internally on the NegotiateStream class to secure the communication.
This article shows how to create and consume .NET remoting clients and servers using Visual Studio 2005 and the Microsoft .NET Framework 2.0, focusing on the security enhancements to the TCP channel and how to use them within your applications. It includes the following examples for communicating over secure TCP and securing the communication both in code and through the configuration file:
- Create a remotable object.
- Create a server to expose the remotable object.
- Create a client to connect to the server and consume the object.
- Show an example using the configuration file rather than code to control the security.
All you need to follow along is a familiarity with remoting, which you can acquire by referring to a prior article on the topic.
Create a Remotable Object
A remotable object is nothing more than an object that inherits from MarshalByRefObject. The following sample demonstrates a simple class to expose the omnipresent hello world message as an example. This object exposes a single method HelloWorld that will return a string. Anything that is serializable can be returned from the method call.
The first step is to create a new C# class library project. Add a class called SampleObject and put in the following code. Compile the class to make sure you have everything correct.
using System; namespace CodeGuru.RemotingSample { /// <remarks> /// Sample object to demonstrate the use of .NET remoting and IPC. /// </remarks> public class SampleObject : MarshalByRefObject { /// <summary> /// Constructor /// </summary> public SampleObject() { } /// <summary> /// Return a hello message /// </summary> /// <returns>Hello world message</returns> public string HelloWorld() { return "Hello World!"; } } }
Create a Server to Expose the Remotable Object via TCP
To create a server object that will act as a listener to accepts remote object requests, you create an instance of the channel and then register it for use by clients on a specific port. You can register the service as WellKnownObjectMode.SingleCall, which results in a new instance of the object for each client, or as WellKnownObjectMode.Singleton, which results in one instance of the object used for all clients.
For this example, create a server listener. Because the service needs to be bound to an available port, choose a port that you know to be unused on your computer. (The example code uses 8080.) To see a list of the used ports on your computer, open a command prompt and issue the command “netstat -a”. It may produce a long list, so make sure the command prompt buffer sizes are set to allow scrolling. Compile the class to make sure you have everything correct.
Create a new C# console application project. Add a class called SampleRemotingServer and paste in the following code:
using System; using System.Collections; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; namespace CodeGuru.RemotingSample { /// <remarks> /// Sample server to demonstrate the use of secure .NET Remoting. /// </remarks> public class SampleRemotingServer { public static void Main() { // Set up the configuration parameters through a dictionary IDictionary properties = new Hashtable(); properties.Add("port", 8080); properties.Add("secure", true); properties.Add("impersonate", true); // Create an instance of a channel TcpServerChannel serverChannel = new TcpServerChannel(properties, null); ChannelServices.RegisterChannel(serverChannel); // Register as an available service with the name HelloWorld RemotingConfiguration.RegisterWellKnownServiceType( typeof(SampleObject), "HelloWorld.rem", WellKnownObjectMode.Singleton ); Console.WriteLine("Listening on {0}", serverChannel.GetChannelUri()); Console.WriteLine("Press the enter key to exit..."); Console.ReadLine(); } } }
Add a reference in your project to System.Runtime.Remoting; otherwise, the TcpChannel and other related classes will not be found. In addition, add a reference to the project containing the SampleObject. Otherwise, the code will not compile because it won’t know how to find a reference to SampleObject.
To secure the communication programmatically, you need to configure the server connection. Notice how the example code passes parameter information in through a Dictionary. You can configure a number of additional parameters (Find more parameters in the VS 2005 Beta Docs on MSDN.) The parameters you will use on the server side for this example are as follows:
- port: Specifies the port number on which the channel will listen.
- secure: Indicates whether or not the channel is to be secured. (When secured, the channel can require user credentials, as well as encrypt and sign the communication. If the TcpServerChannel is secure, the TcpClientChannel must also be secure. Otherwise, no connection is made.)
- impersonate: Indicates whether or not the server should impersonate the client.
Create a TCP Client to Use the Remotable Object
Now that you have your remotable object and a server object to listen for requests, create a client to consume it. The client will be very simple. It will connect to the server, create an instance of the object from the server, and then execute the HelloWorld method.
Create a new C# console application project. Add a class called SampleRemotingClient and paste in the following code:[>
using System; using System.Collections; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Ipc; namespace CodeGuru.RemotingSample { /// <remarks> /// Sample client to demonstrate the use of secure .NET Remoting. /// </remarks> public class SampleRemotingClient { public static void Main() { // Set up the configuration parameters through a dictionary IDictionary properties = new Hashtable(); properties.Add("secure", true); properties.Add("connectionTimeout", 5000); properties.Add("tokenImpersonationLevel", "Impersonation"); // Create a channel for communicating w/ the remote object IpcClientChannel clientChannel = new IcpClientChannel(properties, null); ChannelServices.RegisterChannel(clientChannel); // Create an instance of the remote object RemotingConfiguration.RegisterWellKnownClientType( typeof(SampleObject), "ipc://localhost:8080/HelloWorld.rem"); SampleObject sample = new SampleObject(); Console.WriteLine("{0}", sample.HelloWorld()); Console.WriteLine("Press the Enter key to exit..."); Console.ReadLine(); } } }
Add a reference to System.Runtime.Remoting in the project; otherwise, the TcpChannel and other related classes will not be found. In addition, add a reference to the project containing the SampleObject. Otherwise, the code will not compile because it won’t know how to find a reference to SampleObject. You could use the Activator object to create a remote instance, but this example simply uses the RemotingConfiguration object to register the SampleObject to be created from the remote location.
You also need to pass parameters to the client channel, which you’ll do the same way as the server parameters in the previous section. The parameters you’ll use on the client side are as follows:
- secure: Indicates whether or not the channel is to be secured. (When secured, the channel can require user credentials, as well as encrypt and sign the communication. If the TcpServerChannel is secure, the TcpClientChannel must also be secure. Otherwise, no connection is made.)
- connectionTimeout: Dictates number of milliseconds to wait for a successful connection. (The default value is to wait indefinitely, which you’ll change for this example.)
- tokenImpersonationLevel: Indicates how the client is to be authenticated with the server.