WCF: Net.Pipe - Endpoint not found exception - Admin/Non Admin mode
Issue Definition:
Following error reported from client app calling the WCF service over net.pipe.
There was no endpoint listening at net.pipe://localhost/XYZ/MyService that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details.
Observation:
Client fails to locate the MyService - although server creation doesn't have any issue.
It happens because some other process has created a netpipe server on "net.pipe://localhost" in admin mode. While our client tries to locate MyService it actually finds first in this "net.pipe://localhost" and tries to locate "XYZ/MyService". This happens because this initial service is created in admin mode so it gets priority over the other netpipe "net.pipe://localhost/XYZ" which got created in non-admin mode
When we create MyService also in admin mode - this problem doesn't come. But we need to support our App in non-admin mode as well.
The Client and Server processes always resides on the same machine so ideally WCF Net-pipes is the ideal choice
Working for Net.Pipe client
The first attempt will be for the name derived from "net.pipe://+/MyWCFConnection" in the Global namespace.
The second attempt will be based on the variant "net.pipe://+/", and this will match the name of the unwanted service's shared memory mapping published in the Global namespace.
Because of the search order, it will never get to our service's metadata published in the Local session namespace (if main WCF service is running under the non admin account).
Workaround:
- Run the required WCF service with Admin privilege
- We can try to use the “Absolute Address” and avoid using base address, that should register the pipe correctly and you should be able to use Non Admin mode to run the service.
Works:
<services>
<service behaviorConfiguration="servBeh" name="Component.Blah">
<!--<host>
<baseAddresses>
<add baseAddress="net.pipe://localhost"/>
</baseAddresses>
</host>-->
<endpoint address="net.pipe://localhost/MyEndpoint" binding="netNamedPipeBinding"
bindingConfiguration="noSec" name="MyEndpoint" contract="Component.IBlah" />
</service>
</services>
3. Allow the selected non admin user to publish the pipe in Global session. we need to add the user to policy, as described below
References:
technet.microsoft.com/en-us/library/dn221972.aspx
This policy setting determines which users can create global objects that are available to all sessions.
Possible values
• User-defined list of accounts
• Default accounts listed below
To edit this policy:
technet.microsoft.com/en-us/library/cc758168(v=WS.10).aspx
Once done, restart the box and see if the setting helps.
4. Try setting - hostNameComparisonMode="Exact" at the Net.Pipe binding level and see if it helps.
Little more theory:
Running the handle.exe tool we can see the shared memory name generated by WCF:
\BaseNamedObjects\net.pipe:EbmV0LnBpcGU6Ly8rLw==
in this case, the base64 is: net.pipe://+/
Which is pretty general.
Also, we know that we can't create this base address using two different services if they're running in the same session.
If you run as admin (right click, run as admin) it will run in session 0. Or if you run as a Windows service it will run in session 0.
You can run two different services in different sessions, but if you do, the client will try to use the one with the more generic name.
i.e, if you create the shared memory net.pipe://+/ in session 1 and net.pipe://BLAH/ in session 0, the client message will try to send to the net.pipe://+/ service.
Setting an absolute address will bypass this and will create a pipe specific to the service.
You can get the baseaddress to work as expected by setting the hostNameComparisonMode of the binding to Exact.
<netNamedPipeBinding>
<binding name="blah" hostNameComparisonMode="Exact">
<security mode="None"/>
</binding>
</netNamedPipeBinding>
Doing this will allow you actually set a unique base address host name. The default is StrongWildcard which just puts a '+' as the host name. Which means that it doesn't matter what the hostname of the baseaddress is, it will just use net.pipe://+/.
public static string BuildSharedMemoryName(Uri uri, HostNameComparisonMode hostNameComparisonMode, bool global)
{
string path = PipeUri.GetPath(uri);
string host = null;
switch (hostNameComparisonMode)
{
case HostNameComparisonMode.StrongWildcard:
host = "+";
break;
case HostNameComparisonMode.Exact:
host = uri.Host;
break;
case HostNameComparisonMode.WeakWildcard:
host = "*";
break;
}
return PipeUri.BuildSharedMemoryName(host, path, global);
Please understand:
This is a known issue and it’s by design (for security reasons).
I hope this helps in understanding the way we need to use Net.Pipe binding in WCF.
Thanks
Saurabh Somani and Scott Mason (blogs.msdn.com/b/distributedservices/)