in src/Shared/NodeEndpointOutOfProcBase.cs [352:539]
private void PacketPumpProc()
{
#if FEATURE_NAMED_PIPES_FULL_DUPLEX
NamedPipeServerStream localPipeServer = _pipeServer;
PipeStream localWritePipe = _pipeServer;
PipeStream localReadPipe = _pipeServer;
#else
PipeStream localWritePipe = _pipeClientToServer;
PipeStream localReadPipe = _pipeServerToClient;
#endif
AutoResetEvent localPacketAvailable = _packetAvailable;
AutoResetEvent localTerminatePacketPump = _terminatePacketPump;
Queue<INodePacket> localPacketQueue = _packetQueue;
DateTime originalWaitStartTime = DateTime.UtcNow;
bool gotValidConnection = false;
while (!gotValidConnection)
{
DateTime restartWaitTime = DateTime.UtcNow;
// We only wait to wait the difference between now and the last original start time, in case we have multiple hosts attempting
// to attach. This prevents each attempt from resetting the timer.
TimeSpan usedWaitTime = restartWaitTime - originalWaitStartTime;
int waitTimeRemaining = Math.Max(0, CommunicationsUtilities.NodeConnectionTimeout - (int)usedWaitTime.TotalMilliseconds);
try
{
#if FEATURE_NAMED_PIPES_FULL_DUPLEX
// Wait for a connection
#if FEATURE_APM
IAsyncResult resultForConnection = localPipeServer.BeginWaitForConnection(null, null);
#else
Task connectionTask = localPipeServer.WaitForConnectionAsync();
#endif
CommunicationsUtilities.Trace("Waiting for connection {0} ms...", waitTimeRemaining);
#if FEATURE_APM
bool connected = resultForConnection.AsyncWaitHandle.WaitOne(waitTimeRemaining, false);
#else
bool connected = connectionTask.Wait(waitTimeRemaining);
#endif
if (!connected)
{
CommunicationsUtilities.Trace("Connection timed out waiting a host to contact us. Exiting comm thread.");
ChangeLinkStatus(LinkStatus.ConnectionFailed);
return;
}
CommunicationsUtilities.Trace("Parent started connecting. Reading handshake from parent");
#if FEATURE_APM
localPipeServer.EndWaitForConnection(resultForConnection);
#endif
#endif
// The handshake protocol is a simple long exchange. The host sends us a long, and we
// respond with another long. Once the handshake is complete, both sides can be assured the
// other is ready to accept data.
// To avoid mixing client and server builds, the long is the MSBuild binary timestamp.
// Compatibility issue here.
// Previous builds of MSBuild 4.0 would exchange just a byte.
// Host would send either 0x5F or 0x60 depending on whether it was the toolset or not respectively.
// Client would return either 0xF5 or 0x06 respectively.
// Therefore an old host on a machine with new clients running will hang,
// sending a byte and waiting for a byte until it eventually times out;
// because the new client will want 7 more bytes before it returns anything.
// The other way around is not a problem, because the old client would immediately return the (wrong)
// byte on receiving the first byte of the long sent by the new host, and the new host would disconnect.
// To avoid the hang, special case here:
// Make sure our handshakes always start with 00.
// If we received ONLY one byte AND it's 0x5F or 0x60, return 0xFF (it doesn't matter what as long as
// it will cause the host to reject us; new hosts expect 00 and old hosts expect F5 or 06).
try
{
long handshake = localReadPipe.ReadLongForHandshake(/* reject these leads */ new byte[] { 0x5F, 0x60 }, 0xFF /* this will disconnect the host; it expects leading 00 or F5 or 06 */);
#if FEATURE_SECURITY_PERMISSIONS
WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent();
string remoteUserName = localPipeServer.GetImpersonationUserName();
#endif
if (handshake != GetHostHandshake())
{
CommunicationsUtilities.Trace("Handshake failed. Received {0} from host not {1}. Probably the host is a different MSBuild build.", handshake, GetHostHandshake());
#if FEATURE_NAMED_PIPES_FULL_DUPLEX
localPipeServer.Disconnect();
#else
localWritePipe.Dispose();
localReadPipe.Dispose();
#endif
continue;
}
#if FEATURE_SECURITY_PERMISSIONS
// We will only talk to a host that was started by the same user as us. Even though the pipe access is set to only allow this user, we want to ensure they
// haven't attempted to change those permissions out from under us. This ensures that the only way they can truly gain access is to be impersonating the
// user we were started by.
WindowsIdentity clientIdentity = null;
localPipeServer.RunAsClient(delegate () { clientIdentity = WindowsIdentity.GetCurrent(true); });
if (clientIdentity == null || !String.Equals(clientIdentity.Name, currentIdentity.Name, StringComparison.OrdinalIgnoreCase))
{
CommunicationsUtilities.Trace("Handshake failed. Host user is {0} but we were created by {1}.", (clientIdentity == null) ? "<unknown>" : clientIdentity.Name, currentIdentity.Name);
localPipeServer.Disconnect();
continue;
}
#endif
}
catch (IOException
#if FEATURE_NAMED_PIPES_FULL_DUPLEX
e
#endif
)
{
// We will get here when:
// 1. The host (OOP main node) connects to us, it immediately checks for user privileges
// and if they don't match it disconnects immediately leaving us still trying to read the blank handshake
// 2. The host is too old sending us bits we automatically reject in the handshake
#if FEATURE_NAMED_PIPES_FULL_DUPLEX
CommunicationsUtilities.Trace("Client connection failed but we will wait for another connection. Exception: {0}", e.Message);
if (localPipeServer.IsConnected)
{
localPipeServer.Disconnect();
}
continue;
#else
throw;
#endif
}
gotValidConnection = true;
}
catch (Exception e)
{
if (ExceptionHandling.IsCriticalException(e))
{
throw;
}
CommunicationsUtilities.Trace("Client connection failed. Exiting comm thread. {0}", e);
#if FEATURE_NAMED_PIPES_FULL_DUPLEX
if (localPipeServer.IsConnected)
{
localPipeServer.Disconnect();
}
#else
localWritePipe.Dispose();
localReadPipe.Dispose();
#endif
ExceptionHandling.DumpExceptionToFile(e);
ChangeLinkStatus(LinkStatus.Failed);
return;
}
}
CommunicationsUtilities.Trace("Writing handshake to parent");
localWritePipe.WriteLongForHandshake(GetClientHandshake());
ChangeLinkStatus(LinkStatus.Active);
RunReadLoop(
new BufferedReadStream(localReadPipe),
localWritePipe,
localPacketQueue, localPacketAvailable, localTerminatePacketPump);
CommunicationsUtilities.Trace("Ending read loop");
try
{
#if FEATURE_NAMED_PIPES_FULL_DUPLEX
if (localPipeServer.IsConnected)
{
localPipeServer.WaitForPipeDrain();
localPipeServer.Disconnect();
}
#else
localReadPipe.Dispose();
localWritePipe.WaitForPipeDrain();
localWritePipe.Dispose();
#endif
}
catch (Exception)
{
// We don't really care if Disconnect somehow fails, but it gives us a chance to do the right thing.
}
}