Retired Microsoft Blog disclaimer

This directory is a mirror of retired "Decrypt My World" MSDN blog and is provided as is. All posting authorship and copyrights belong to respective authors.
Original URL: https://blogs.msdn.microsoft.com/alejacma/2009/05/27/ndrclientcall2-fails-with-rpc_s_already_listening-when-using-pipes-over-ncalrpc/
Post name: NdrClientCall2 fails with RPC_S_ALREADY_LISTENING when using pipes over ncalrpc
Original author: Alejandro Campos Magencio
Posting date: 2009-05-27T05:29:00+00:00


Hi all,


I've been working on a Microsoft Remote Procedure Call (RPC)issue recently, where the first call to aspecificremote method fails because the call to NdrClientCall2 function in the client stub returns RPC_S_ALREADY_LISTENING("The server is already listening") error. But subsequent calls to the very same method succeed.


The following CONDITIONS are required to reproduce the issue:


1. Process has started its own RPC server (called RpcServerListen) and
2. The same process starts an outgoing RPC call of a method from an interface for which it has no own RPC server started and this method has an "[in] pipe byte" parameter.



In order to better understand the issue, let's consider the following SCENARIO:


- We have 2 processes which useRPCfor interprocess communication. We'll call them A and B.


- Process A and B both run on the same machine. Protocol being used is ncalrpc (Local procedure call). Machine can be either Windows XP or Server 2003.


- We have a MIDL-defined interface that declares a method "rpcMethod" with "[in] pipe byte" parameter like the following:


[
uuid(ActualGuidHere),
version(1.0),
pointer_default(unique)
]
interface IMyInterface
{
//other irrelevant methods here
error_status_t rpcMethod( [in] pipe byte params );
//more irrelevant methods here
}


- An RPC server that implements this interface is started in process B.


- Process A starts its own RPC server for another interface (not the same as process B). While doing this it certainly calls RpcServerListenAPI and that call succeeds.


- Later on, process A tries to invoke IMyInterface::rpcMethod in process B, but call to NdrClientCall2 function fails with RPC_S_ALREADY_LISTENING error and it never reaches the server side.


This error should only be returned when we call RpcServerListentwice on the same process, but we are 100% sure thatwe are only calling it once directly. What is going on then? Why is RPC runtime returning that error? Is it calling RpcServerListen under the hood? Why are the rest of the calls to IMyInterface::rpcMethod succeeding?



The EXPLANATION is the following:


When weuse pipes over ncalrpc, NdrClientCall2 ends upinitializing an ncalrpc endpoint (don't know what an endpoint is? Please check Essential RPC Binding Terminology) for internal use with RpcServerUseProtseqEpAPI andcalling RpcServerListento listen for remote procedure calls, but only if that endpoint has notbeen initialized yet.


The first time we call a method with a pipe parameter, that endpoint for internal use is not initialized yet, so NdrClientCall2 initializes it and calls RpcServerListenwhich fails because we already called it in the same process once before.


The next time we call a method with a pipe parameter, that endpoint is already initialized, so NdrClientCall2 doesn't call RpcServerUseProtseqEp or RpcServerListen. Additionally, we already called RpcServerListen successfully once in that process, so this side is already listening for RPC calls. With the endpoint created and the process listening for RPC calls, everything works just fine from that moment on.



It's very easy to WORKAROUND this limitation: we can safely ignore the RPC_S_ALREADY_LISTENING error of the first call toa method with a pipe parameter and just repeat the call immediately. The failed call won't have any effect andit won't change the pipe state, so there is no need to reinitialize the pipe. There should be no performance impact either.


Note that this issue is related to the usage of pipes over ncalrpc, and it doesn't matter if we use them synchronously or asynchronously (Asynchronous Pipes), or the parameter is [in] or [out]. And of course, the same workaround applies.



Also note that using synchronous pipes over ncalrpc protocol is deprecated on Vista. If you start an application which uses them on Vista, you will see a message like the following on the Event Log:


"
Application ("my program exe file name here" \service) (PID: 344) is using Windows functionality that is not present in this release of Windows. For obtaining an updated version of the application, please, contact the application vendor. The technical information that needs to be conveyed to the application vendor is this: "An RPC method using synchronous pipes has been called on on protocol sequence ncalrpc interface with unique identifier (actual UUID here). Usage and support of synchronous pipes on this protocol sequence has been deprecated for this release of Windows. For information on the deprecation process, please, see <http://go.microsoft.com/fwlink/?LinkId=36415>." User Action Contact the application vendor for updated version of the application.
"


We can still use asynchronous pipes over ncalrpc on Vista,and the RPC_S_ALREADY_LISTENINGissue won't happen.



AnALTERNATE SOLUTIONwhich we recommend and works on all supported versions of Windows is to switch protocols from ncalrpc toncacn_np (Connection-oriented named pipes). Windows components like EFS among others use synchronous pipes over ncacn_np.


Note thatncacn_np transport is remotely accessible, but ncalrpc is not. To keep parity with our existing thread model, we'll need to write our own security callback function and register it with the server (RpcServerRegisterIfEx). The security callback can simply check if the call is coming from local system or remote (RpcServerInqCallAttributesand its IsClientLocal value), and rejects it if it'sa remote client. Or we can update our thread model to deal with remote clients instead.


Sample code:

// Register the security callback with interface
status = RpcServerRegisterIfEx(
hRpcInterface,
NULL, // MgrTypeUuid
NULL, // MgrEpv; null means use default
RPC_IF_ALLOW_LOCAL_ONLY,
RPC_C_LISTEN_MAX_CALLS_DEFAULT,
(RPC_IF_CALLBACK_FN*)RpcSecurityCallback
);

RPC_STATUS RPC_ENTRY
RpcSecurityCallback(
IN RPC_IF_HANDLE* handle,
IN void* pCtx)
{
// You can use RpcServerInqCallAttributes API here to find if the client is local or remote.
}



I hope this helps.
Regards,


Alex (Alejandro Campos Magencio)


Share this article:

Comments:

Comments are closed.