Implications of using IPCServerChannel when connection from remote clients

 

In .Net 2.0 (Whidbey), Remoting introduced support for IPC channels. This was a quick way for local machine processes to communicate with objects hosted locally. Now there is a small behavior that  users might not be aware of when they are unmarshalling an ObjRef from an remote appdomain that has an IpcChannelServer registered. As you might know when an object that implements MarshalbyRefObject is passed across appdomains (via an argument to a function call or as a return value) Remoting sends a ObjRef instead of the actual object. This ObjRef contains information on the unique Uri that the object is identified in the remote server and the various channels via which the remote object can be reached (in addition to other information). Now imagine the scenario where the Server has registered  IpcServerChannel in addition to other channels (http and tcp) to enable local clients to communicate with the server. When a remote client gets any ObjRef from this Server it will unmarshal the ObjRef and as part of the process will create a Sink that the TransparentProxy for the ObjRef will use. The ObjRef contains a field called ChannelInfo that contains information on all server channels registered on the Server. The client iterates over the channel info list it received from the server over the list of channels registered in its local appdomain to see which client channel it can connect.

This ChannelInfo field always stores the list of available channels in descending order based on their priority. The priority is of a channel denotes how "preferred" is that channel as compared to the other registered channels. If two channels have the same priority then they are sorted based on the order they were registered. In Remoting, HttpServerChannel and TcpServerChannel both have a default priority of 1 while IpcServerChannel has a priority of 20. I am going to exclude the CrossAppDomainChannel, that is registered by default and has the highest priority of 64, from this discussion as that channel is automatically not considered when an Objref is located on a remote machine. So if the server has registered the following channels TcpServerChannel, HttpServerChannel and IpcServerChannel (in that specific order), the ChannelInfo data on that ObjRef will contain the following information

ChannelInfo[]

IpcChannelData

TcpChannelData

HttpChannelData

The sink creation process for an ObjRef works in the following way.

  1. Iterate over list of received ChannelInfo data over list of registered client channels (sorted in descending order of their prioirty just like server channels) to see if any one of the channel can connect to the Uri pointed by the RemoteChannelData object.
  2. If none of the registered channels can create a sink then Remoting will iterate over the list again to see if it can use one of the default client channels (by default Remoting will always register TcpClient, HttpClient and IpcClient if the user has not explicitly registered one).

 

Lets see how the sink creation process works on the client side for couple of scenarios.

Scenario 1:

Client has explicitly registered a TcpClientChannel and HttpServerChannel. It first passes Ipc channel Uri to TcpClientChannel/HttpClientChannel and will see that both cant connect to Ipc Uri. Then it will pick the next ChannelInfo data (Tcp in this case) and pass it to TcpClientChannel. TcpClientChannel will see that it can indeed connect to the Tcp Uri and create the sink and things works fine. Once Remoting finds a Sink then it will not continue to see if other channels can also be used. It will *always* use the first channel that recognizes the Uri.

Scenario 2:

Client has explicitly registered TcpClientChannel and IpcClientChannel (order doesn't matter as Ipc will always be ahead of Tcp because of its default priority) . It first passes the Ipc channel Uri to see if any client can connect to the Uri and it will return a Ipc sink as the IpcClientChannel will successfully create the sink. Remoting will quit looking for other Sinks and use the Ipc channel sink for all future communication. When you try to make any call on the TransparentProxy you will get the following error message.

Unhandled Exception: System.Runtime.Remoting.RemotingException: Failed to connect to an IPC Port: The system cannot find the file specified.

Server stack trace:
at System.Runtime.Remoting.Channels.Ipc.IpcPort.Connect(String portName, Boolean secure, TokenImpersonationLevel impersonationLevel, Int32 timeout)
at System.Runtime.Remoting.Channels.Ipc.ConnectionCache.GetConnection(String portName, Boolean secure, TokenImpersonationLevel level, Int32 timeout)
at System.Runtime.Remoting.Channels.Ipc.IpcClientTransportSink.ProcessMessage(IMessage msg, ITransportHeaders requestHeaders, Stream requestStream, ITransportHeaders& responseHeaders, Stream& responseStream)
at System.Runtime.Remoting.Channels.BinaryClientFormatterSink.SyncProcessMessage(IMessage msg)

Exception rethrown at [0]:

Reason being that Remoting Ipc channels cannot communicate with names pipes on remote machines. So you see that having an IpcClientChannel registered and unmarshalling an ObjRef containing an IpcServer Channel info is almost bound to fail if the ObjRef is from a remote machine.

One more associated problem with the server sending list of registered channels to the client will show up when you have a custom ServerChannel registered that is not known to the clients. In cases when a client tries to unmarshall the ObjRef from such a server it will fail to deserialize the ChannelIno as the custom ServerChannels ChannelDataStore will not be known to the client and you will get a serialization error such as

 

Unhandled Exception: System.Runtime.Serialization.SerializationException: Type 'Server.MyCustomData' in Assembly 'Server, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.

Server stack trace:
at System.Runtime.Serialization.FormatterServices.InternalGetSerializableMembers(RuntimeType type)
at System.Runtime.Serialization.FormatterServices.GetSerializableMembers(Type type, StreamingContext context)
at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitMemberInfo()
at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitSerialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter)
at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.Serialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter)
at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Serialize(Object graph, Header[] inHeaders, __BinaryWriter serWriter, Boolean fCheck)
at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph, Header[] headers, Boolean fCheck)
at System.Runtime.Remoting.Channels.BinaryServerFormatterSink.SerializeResponse(IServerResponseChannelSinkStack sinkStack, IMessage msg, ITransportHeaders& headers, Stream& stream)
at System.Runtime.Remoting.Channels.BinaryServerFormatterSink.ProcessMessage(IServerChannelSinkStack sinkStack, IMessage requestMsg, ITransportHeaders requestHeaders, Stream requestStream, IMessage& responseMsg, ITransportHeaders& responseHeaders, Stream& responseStream)

 

Solutions:

There are no straight forward way for solving the above mentioned problem. Some possible solutions that I can suggest are

  • To avoid your client from picking the Ipc channel you can increase the priority of your TcpClientChannels and HttpServerChannel to something more than 20 (so they are placed above the IpcChannel).
  • Or you can try the reverse of earlier suggestion and lower the priority of IpcClientchannel to 0.
  • Implement ITrackingHandler on the server and in your implementation of public void MarshaledObject(object obj, ObjRef or) {} you can loop over the ChannelInfo array on ObjRef and remove any ChannelInfo you dont want to send across. One draw back to this process is the fact that Remoting does not give you any way of determining the reason for marshalling the object. What I mean is there is no way of finding out if the marshalling is happening because of a call made by a remote client or a client residing on the local machine.