This is the first in a series of posts in which I will aim to explain some details of the named pipe binding provided by Windows Communication Foundation (WCF), discovered during the course of some exploring I have been doing. My motivations for looking into this were:
- The standard binding (NetNamedPipeBinding) exposes very few properties relating to configuration of the underlying transport mechanism. Having some awareness of the Windows named pipe APIs from previous work, I was interested to understand how the WCF binding mapped to the underlying transport protocol; which named pipe configuration options were "baked into" the WCF implementation and which might be controlled/tweaked with a bit of work in the channel stack.
- I wanted to understand in more detail the security characteristics of the binding.
- I was just nosey :-)
In this post I will start by looking at the how the named pipe used by a service endpoint with the NetNamedPipe binding is created, and how clients locate it in order to connect.
I had expected that if I looked into the service process using a tool like Process Explorer, I would see it holding a handle to a named pipe with a name closely related to the URI of the endpoint. What I see instead is a handle to a pipe named something like...
... clearly the pipe name has been created using a GUID. I also note that the name of the pipe changes each time I stop and restart my service host, so the GUID is being regenerated each time the endpoint runtime is built by WCF.
How then does a client of the service know how to communicate with the endpoint? Somehow it must be able to resolve the well-known URI for the endpoint into whatever is the current name of the pipe it must use to send messages to the service. It turns out that this is accomplished using what amounts to a mini metadata publishing mechanism which is exclusive to the NetNamedPipe binding. This mechanism is based on a named Windows file mapping object backed by the system paging file. It is the name of this object which is invariant, and directly derived from the endpoint URI... though in a far from obvious way.
So in order to locate the correct pipe, a client of a WCF NetNamedPipe service endpoint has to:
- know that the special metadata mechanism exists
- know how to derive from the endpoint URI the name of the file mapping object through which the metadata is published
- located the file mapping object and use it to open a view on the shared memory
- know how to interpret the metadata stored in the shared memory, and translate it into the name of the pipe currently being used by the endpoint
Some more details for those who are interested:
Deriving the file mapping object name from the URI
The shared memory file mapping object created by the service endpoint listener (System.ServiceModel.Channels.PipeConnectionListener) has a name which looks something like this:
This is derived from the following components:
["net.pipe"] [:E|:H] [base-64 encoded byte X]
Where the X is constructed as:
- when the second component is :E : UTF8 encoding of ["net.pipe://"] [URI hostname-or-wildcard*] [URI path or parent path]
- when the second component is :H : the SHA-1 hash of the above (used when the UTF8 encoding of the above exceeds 127 bytes)
*The URI hostname-or-wildcard depends on the HostNameComparisonMode setting for the endpoint's transport binding - this property is set to HostNameComparisonMode.StrongWildcard in the standard NetNamedPipeBinding, and is not exposed as a property of the binding itself. This means that this component of the name will be "+" (the strong wildcard symbol) unless a custom binding has been used to tweak the HostNameComparisonMode property of the transport binding element.
Data stored by the service in the shared memory object
The service stores 20 bytes of data in the shared memory, representing an instance of the structure System.ServiceModel.Channels.PipeSharedMemory+SharedMemoryContents, which looks like this...
public bool isInitialized;
public Guid pipeGuid;
The client uses the GUID stored in this object to construct the pipe name through which to connect to the service endpoint.
Of course, the WCF client stack knows how to jump through these hoops, as it uses the same set of System.ServiceModel types as the service used to set up the mechanism. So you don't really need to know anything about all this if your service client is also a WCF application using the standard binding... which it will be if you are doing things as the WCF designers intended: the named pipe binding was designed solely for WCF-to-WCF scenarios.
That's not to say that, in principle, there is any fundamental reason why a named pipe binding to a WCF service should not be able to support any arbitrary client implementation which knows how to write messages to and read messages from a named pipe. Perhaps there are integration scenarios involving legacy unmanaged code or mixed technologies on a single box, where a more open named pipe binding might be useful, not least because the underlying transport mechanism is very fast. But the standard NetNamedPipe binding won't help with this. In practice, it is going to be much easier to use one of the bindings based on standard interoperable protocols, or by providing a COM wrapper around a WCF client implementation.