ONDotNet.com    
 Published on ONDotNet.com (http://www.ondotnet.com/)
 See this if you're having trouble printing code examples


Client/Server Rendezvous on the LAN

by Shawn Van Ness
04/14/2003

You're a grizzled, veteran network programmer. You've mastered RPC and DCOM, and you think .NET remoting is child's play. Web services? Web schmervices. And yet. Something is missing from your life. There is a sense of emptiness that you just can't put your finger on ... Could it be that of all of the aforementioned networking technologies, none of them provide an easy way for your clients and servers to find each other on the network? Have you now designed, developed, and shipped a dozen different client-server software products, with a dozen different clumsy dialog boxes prompting your poor, confused users to enter host/endpoint information, in order to establish a connection? I know I have. Is there not a better way?

This is known as the "rendezvous" problem--how services advertise their presence, and how clients discover them--and it's nearly as old as the notion of client-server architecture itself. So it should come as no surprise that a great plethora of technologies and protocols exists to address this problem, today. You've probably heard of most of them: LDAP, ADSI, DISCO, UDDI ... even DNS and DHCP are all examples of protocols that exist to help clients and servers rendezvous with each other.

Background

Rendezvous mechanisms tend to come in one of two flavors: directory-based or broadcast-based. Examples of directory-based mechanisms include UDDI (Universal Description, Discovery, and Integration) and LDAP (Lightweight Directory Access Protocol). Some popular examples of broadcast-based mechanisms include DHCP (Dynamic Host Configuration Protocol) and ... well, virtually all of the old head-to-head games we used to play, which somehow magically found other gamers in your office building.

Figure 1a. The client-server rendezvous pattern (directory-based)
Figure 1a. The client-server rendezvous pattern (directory-based)
Figure 1b. The client-server rendezvous pattern (broadcast-based)
Figure 1b. The client-server rendezvous pattern (broadcast-based)

Different rendezvous technologies apply to different scopes (Internet, intranet, or subnet). At the scope of the entire Internet, UDDI stands alone as humanity's ambitious attempt to provide a single directory of all of the world's public Web services. DISCO works at the level of a single web site, or family of web sites, to provide links to the various Web service endpoints available. At the intranet scale, numerous technologies exist, from LDAP and dynamic DNS services to the various RPC name-service technologies.

Related Reading

Programming .NET Web Services
By Alex Ferrara, Matthew MacDonald

Client-Server Rendezvous, on a Budget

One problem with directory-based rendezvous technologies (those targeted toward LAN-scale applications) is that they either require your customers to purchase and install special directory software, or greatly complicate the installation of your app, or both. (Not every Windows shop uses Active Directory--for example, many are heavily vested into Novell Netware's NDIS product.)

A great wealth of technical information exists for LDAP and Active Directory, and quite a bit has been written lately about web-scale directories like UDDI and DISCO--the purpose of this article, therefore, is to focus on the missing link: easy, affordable, foolproof rendezvous mechanisms for .NET apps on the LAN. And for the reasons mentioned above, "easy, affordable, and foolproof" means resorting to some kind of broadcast/response-based rendezvous architecture. Specifically, the technique I'll demonstrate in this article is based on UDP multicast groups.

Security First!

Bill Gates says we're not allowed to ignore security issues anymore when designing our software. Good call, Bill. So let's get this out of the way now: the only secure way to perform client-server rendezvous is via a centralized, secure directory, like [insert your favorite LDAP provider here].

The act of broadcasting a request to your peers (and trusting the responses) is an inherently insecure act. Just think: any host within earshot can respond to your broadcast. If your client app decides to connect to a malicious host, any number of Very Bad Things might happen. The malicious host can act as a "man-in-the-middle," snooping on sensitive information, possibly even altering it. In certain scenarios, the space of "sensitive information" might even include your callers' credentials, allowing the malicious host to masquerade as the caller, performing questionable activities on her behalf.

To make matters worse, security measures that depend on server authentication (like SSL encryption) are of no use. Those mechanisms all boil down, at some point, to comparing the name on a signed, trusted certificate to the name requested by the caller--but the caller likely never specified a hostname. In a broadcast-based rendezvous architecture, callers will simply connect to the hostname specified in the response (which is not trustworthy at all--the best you can do is display a list of responding server names to your user, and let them absent-mindedly select one).

The moral is this: only use broadcast/response-based rendezvous mechanisms when security is really, truly of no concern to your application--in environments where either the physical network is secure, or applications where no sensitive information can possibly be transmitted, no harm can be caused by missing or tampered messages, and the protocol is robustly validated (on both sides).

So, if you care about security at all (yes, you do!) please stop reading this article now, and go brush up on your Active Directory skills.

System.Net.Sockets.UdpClient

Still here? Good. It's time to get off the security soapbox and write some code. Win32 provided a nice little protocol-independent mechanism known as Mailslots, which facilitated the broadcast of messages to the LAN. That was nice, but today IP is everywhere. .NET lets us skip the middleman, by giving us System.Net.Sockets.UdpClient--a one-stop shop for sending, receiving, and responding to broadcast and/or multicast messages on the LAN.

For the sake of completeness, I will note here that the networking support provided by DirectPlay API provides a somewhat protocol-agnostic, broadcast-based rendezvous mechanism. But DirectX version 9 is the first version to provide a managed API, and it has only just been released. The motivation of this article is to help you ship code that works today, and with a minimum of baggage. So let us continue to explore .NET's UdpClient class. It's no more or less secure than DirectPlay's offering, and it's actually quite a bit more straightforward. On the other hand, if you plan on using some other aspects of DirectX in your app, you might wish to check out DirectPlay's Peer.FindHosts method.

Multicast Vs. Broadcast

So far, I've talked a lot about "broadcast"-based rendezvous architectures, but what I really want to focus on is the use of UDP multicast groups--I've been using the word broadcast to describe a family of architectures, encompassing both UDP broadcast and multicast.

UDP does offer a pure broadcast mechanism (via addressing a packet to 255.255.255.255), and I've seen this approach used for client-server rendezvous more than once--perhaps for its sheer simplicity--but UDP broadcast packets have many drawbacks. For starters, they increase network congestion, have limited message sizes, and suffer from the inability to propagate past routers and bridges. In fact, UDP broadcast is officially deprecated--it's not a part of the IPv6 UDP protocol, at all.

Multicast groups represent a more explicit subscribe/notify pattern. The "subscribers" declare their interest in a certain type of notification by sending an IGMP "group membership report" to one or more local routers. In .NET, this is accomplished simply by calling the UdpClient.JoinMulticastGroup method, specifying both an IP address for the group and a TTL (time-to-live) count that determines how far out (how many router hops) to reach. The "group address" must fall within the special range of "Class D" IP addresses, 224.0.0.0 to 239.255.255.255 (although 224.0.0.x addresses are reserved for use by low-level routing protocols and the like).


// Initialize a new UDP socket to listen on the multicast group
int port = 11000;
UdpClient listener = new UdpClient(port);

// Join the multicast group (sends an IGMP group-membership report to routers)
IPAddress groupAddr = IPAddress.Parse("226.254.82.220");
int ttl = 5;
listener.JoinMulticastGroup(groupAddr, ttl);

The "notifiers" in the multicast model simply send a UDP message to the group address--and let the routers handle the heavy lifting of forwarding the message to all of the subscribers. The size of the spanning tree of a UDP multicast system is largely determined by the TTL value specified by the subscribers. If a notification message is sent to a multicast address, a subscriber N hops away will receive the message only if it specified a TTL value greater than or equal to N, in the call to JoinMulticastGroup.


// Dynamically allocate client port
UdpClient sender = new UdpClient();

// Send message to the multicast group
IPEndPoint groupEP = new IPEndPoint(IPAddress.Parse("226.254.82.220"),11000);
sender.Send(message, message.Length, groupEP);

Clearly, this technology can be brought to bear on the problem of client-server rendezvous--effectively using the routers' IGMP membership tables as a poor man's directory, for the LAN subnet!

Client-Server Rendezvous via UDP Multicast

The code in Listing 1 wraps the somewhat-messy details of using UDP multicast groups for client-server rendezvous with two simple classes: ServicePublisher and ServiceLocator. These classes allow us to advertise services on the network, and search for those services, with just a few lines of code!

Because the space of multicast IP addresses is limited (approximately 28 bits) I've chosen to hard-code a single, arbitrary multicast IP address for use by the classes--and instead identify the specific type of service with a 128-bit GUID. Clients attempt to locate servers by specifying the desired service type's well-known GUID in the broadcast, while servers discard any packets from the multicast group whose first 128 bits do not match their service ID.

It's important that both ServicePublisher and ServiceLocator discard unrecognizable packets. Thinking in binary, constraining the IP address's first octet to the range 224 to 239 corresponds to fixing the 4 high-order bits of the octet IP address to "1110". This leaves a space of (32-4=) 28 bits to represent the actual group address. The first 256 (or so) of those are reserved, so that leaves an address-space of slightly less than 2^28 multicast group addresses. This is not as vast a space as one might like, so we must take care to write our UDP listener code very defensively (sage advice at any time) because we may end up receiving messages from a client-server system completely foreign from our own! Fortunately, this task is easy: the code in Listing 1 takes care to verify the size of incoming messages is at least 128 bits, and that those first 128 bits correspond to the GUID of our published service. In the worst-case scenario, the multicast group address can be changed in the field, by editing the application's .config file.


// Wait for broadcast... will block until data recv'd, 
// or underlying socket is closed
IPEndPoint callerEndpoint = null;
byte[] request = this.listener.Receive(ref callerEndpoint);

// Verify first 128 bits are indeed our guid
if (request.Length >= 16)
{
  byte[] temp = new byte[16];
  request.CopyTo(temp,0);
  Guid requestGuid = new Guid(temp);

  if (requestGuid == this.serviceId)
  {
    // Send response (our guid, followed by serialized endpoint info)
    this.listener.Send(this.response, this.response.Length, callerEndpoint);
  }
}

As for usage, the following sample client code shows how to discover available servers, of a particular type, with just two lines of code:


// Define the unique identifier for our service
Guid serviceId = new Guid("decafbad-baad-baad-baad-decafbaaaaad");

// Scan the network for services!  Wait 3 seconds for responses.
UdpRendezvous.ServiceLocator.HostResponse[] responses =
  UdpRendezvous.ServiceLocator.LocateService(serviceId,3000);

Publishing a service is nearly as easy--one must simply collect the endpoint properties to advertise, then decide on an appropriate TTL value (or simply leave that for the end user or system administrator to tweak in a .config file):


// Prepare our service id, and custom endpoint info
Guid serviceId = new Guid("decafbad-baad-baad-baad-decafbaaaaad");

System.Collections.Hashtable endpointInfo = new System.Collections.Hashtable();
endpointInfo.Add("HostName","somelocalserver");
endpointInfo.Add("PortNumber",321);
endpointInfo.Add("UseSSL",true);
endpointInfo.Add("Foo","whatever");

// Publish our endpoint info on the net (across as many as 5 routers)
UdpRendezvous.ServicePublisher publisher =
  new UdpRendezvous.ServicePublisher(serviceId);

int ttl = 5;
publisher.PublishServiceEndpoint(endpointInfo,ttl);

// Run the actual service, do other stuff, etc...
. . .

// Shutdown!
publisher.StopThePresses();

Conclusion

In this article, I discussed the pros and cons of a variety of client-server rendezvous architectures, and presented some useful classes that leverage UDP multicast groups for a quick-and-easy solution to the problem.

However, it's important to remember that this technique won't work in all cases. Multicast messages are capable of travelling over routers, across multiple subnets, but that doesn't mean they're guaranteed to do so. Firewalls are almost always configured to block UDP messages, in one direction or another--either of which will limit the scope of the multicast group's visibility. And on busy/congested networks, UDP messages aren't guaranteed to arrive at all! So it's still important to construct confusing, intimidating dialog boxes for your users, so that they have a fallback to provide connection information manually, in cases where multicast client-server rendezvous fails.

Now, if you'll pardon me, I need to integrate this feature into the Tic Tac Toe game I wrote, and load it onto all of the shiny new Tablet PCs in our test lab!

Shawn Van Ness is an independent consultant specializing in the development of secure, efficient, and robust distributed software systems, based on .NET, COM, and XML technologies.


Return to ONDotnet.com

Copyright © 2009 O'Reilly Media, Inc.