Share via


NdrClientCall2 fails with RPC_S_ALREADY_LISTENING when using pipes over ncalrpc

Hi all,

I've been working on a Microsoft Remote Procedure Call (RPC) issue recently, where the first call to a specific remote 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 use RPC for 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 RpcServerListen API 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 RpcServerListen twice on the same process, but we are 100% sure that we 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 we use pipes over ncalrpc, NdrClientCall2 ends up initializing an ncalrpc endpoint (don't know what an endpoint is? Please check Essential RPC Binding Terminology) for internal use with RpcServerUseProtseqEp API and calling RpcServerListen to listen for remote procedure calls, but only if that endpoint has not been 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 RpcServerListen which 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 to a method with a pipe parameter and just repeat the call immediately. The failed call won't have any effect and it 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 < https://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_LISTENING issue won't happen.

 

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

Note that ncacn_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 (RpcServerInqCallAttributes and its IsClientLocal value), and rejects it if it's a 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)