Console Application to discover Effective Named Pipe Path of a WCF net.pipe Endpoint
As I have promised in my previous post, I am making available a C++ console application to troubleshoot named pipes endpoints in WCF. Below is a screenshot of the application:
Application logic:
- It gets the endpoint from the command line and substitute the host name by +, * depending on the wildcard mode.
- It then add a “/” to the end if not present already and transform to up case all characters of pipe and path
- If the size of the resulting string is bigger than 128 characters a hash is applied to the resulting string (not implemented)
- The final name is net.pipe:E + Base64 of string generated in item 2 or net.pipe:H + Base64 of string generated in item 3 if uri is bigger than 128 characters
Post detailing the problem:
Source Code is as shown below (subject to this license: http://rodneyviana.codeplex.com/license).
// ReadMemory.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
void NormalizeEndPoint(const std::wstring &source, std::wstring& normal)
{
normal.assign(source);
if(!normal.compare(0, 10, L"net.pipe://"))
{
normal.clear();
return;
}
int hoststart = normal.find_first_of(L"//");
int hostend = normal.find(L"/", hoststart+2);
std::wstring ending(normal.substr(hostend));
std::wstring starting(normal.substr(0, hoststart).append(L"//+"));
std::transform(ending.begin(), ending.end(), ending.begin(), (int(*)(int))std::toupper);
std::wstring normalized(starting.append(ending));
if(normalized.substr(normalized.length()-1).compare(L"/"))
{
normalized.append(L"/");
}
normal.assign(normalized);
return;
}
void ShowSyntax(bool SyntaxError)
{
std::wprintf(L"ReadMemory version 1.0\n");
std::wprintf(L"Written by Rodney Viana – http://blogs.msdn.com/rodneyviana\n");
std::wprintf(L"\n");
if(SyntaxError)
std::wprintf(L"Syntax Error\n\n");
std::wprintf(L"Syntax:\n");
std::wprintf(L"ReadMemory <PipeNameEndPoint> | -file <MappedMemoryFile>\n");
std::wprintf(L"Where:\t<PipeNameEndPoint> is a endpoint for a net.pipe in WCF in\n\t the format net.pipe://host/path\n");
std::wprintf(L"\t<MappedMemoryFile> is a memory mapped file\n");
std::wprintf(L"\n");
std::wprintf(L"Examples\n");
std::wprintf(L"\tReadMemory net.pipe://localhost/Service/Service1\n");
std::wprintf(L"\tReadMemory -file \"net.pipe:EbmV0LnBpcGU6Ly8rLzhFNjFFRUM5LUYxOUEtNEIxNy04REE4LTM5NTc1QzhGMTU4QS8=\"\n");
}
int _tmain(int argc, _TCHAR* argv[])
{
if(argc < 2)
{
ShowSyntax(false);
return 0;
}
if(argc > 3)
{
ShowSyntax(true);
return 1;
}
std::wstring original;
std::wstring normalized;
std::wstring mapFile;
if(argc == 2)
{
original.append(argv[1]);
NormalizeEndPoint(original, normalized);
std::wprintf(L"\nOriginal Endpoint: %s", original.c_str());
std::wprintf(L"\nNominal Endpoint: %s", normalized.c_str());
char base64A[1000];
CW2A ansiNormal(normalized.c_str());
int size = 1000;
Base64Encode((BYTE*)ansiNormal.m_psz, normalized.length(), base64A, &size, ATL_BASE64_FLAG_NOCRLF | ATL_BASE64_FLAG_NOPAD );
base64A[size]=’=’;
base64A[size+1]=”;
CA2W base64W(base64A);
mapFile.append(L"net.pipe:E");
mapFile.append(base64W.m_psz);
}
if(argc == 3)
{
std::wstring force(argv[1]);
std::transform(force.begin(), force.end(), force.begin(), (int(*)(int))std::toupper);
if(!force.compare(0, 4, L"-FILE"))
{
ShowSyntax(true);
return 2;
}
mapFile.append(argv[2]);
}
//original.append(L"net.pipe://localhost/TradeService/Service1");
std::wprintf(L"\nMapped Memory Object Name: %s", mapFile.c_str());
HANDLE map = OpenFileMapping(FILE_MAP_READ, FALSE, mapFile.c_str());
MEMORY_BASIC_INFORMATION mi;
if(map)
{
std::wprintf(L"\nGood News: Mapped memory Object was found. It shows that Host is enabled.");
PVOID contents = MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0);
SIZE_T s = VirtualQuery(contents, &mi, sizeof(mi));
if(s<sizeof(GUID)+4)
{
std::wprintf(L"\nMapped memory Object seems to be corrupted. Restart your WCF Host.\n");
}
PBYTE bytes = ((PBYTE)contents)+4;
std::wprintf(L"\nRaw Bytes:\n");
if(contents)
{
for(SIZE_T i=0;i<sizeof(GUID);i++)
{
printf("%02x ", *(bytes+i));
}
std::wprintf(L"\n");
for(SIZE_T i=sizeof(GUID);i<s-4;i++)
{
printf("%02x ", *(bytes+i));
}
GUID *guid = (GUID*)bytes;
RPC_WSTR guidStr;
UuidToString(guid, &guidStr);
std::wprintf(L"\nActual Named Pipe Name: %s", guidStr);
std::wprintf(L"\nFull Named Pipe Name: \\Device\\NamedPipe\\%s", guidStr);
std::wstring localPipe(_T("\\\\.\\pipe\\"));
localPipe.append((LPWSTR)guidStr);
std::wprintf(L"\nAttempting to connect to Named Pipe for 20 seconds …\n");
if(!WaitNamedPipe(localPipe.c_str(), 20000))
{
std::wprintf(L"\nBad News: Unable to connect to local pipe: %s.\nReason: Time out.", localPipe.c_str());
} else
{
std::wprintf(L"\nGood News: Local pipe \"%s\" is alive.", localPipe.c_str());
HANDLE pipe;
std::wprintf(L"\nAttempting to open Named Pipe…\n");
pipe = CreateFile(localPipe.c_str(), GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, NULL);
if(pipe == INVALID_HANDLE_VALUE)
{
std::wprintf(L"\nBad News: Unable to open local pipe: %s.\nLast Error: %i.", localPipe.c_str(), GetLastError());
} else
{
std::wprintf(L"\nGood News: Named Pipe opened successfully.\n");
TCHAR* send = L"<bad><\\bad>\n";
DWORD bytesUsed;
if(!WriteFile(pipe, (void*)send, wcslen(send)*sizeof(TCHAR), &bytesUsed, NULL))
{
std::wprintf(L"\nBad News: Unable to send bytes.\n");
} else
{
std::wprintf(L"\nGood News: Pipe accepted bytes\n");
std::wprintf(L"\nTest completed successfully!\n");
}
CloseHandle(pipe);
}
}
} else
{
std::wprintf(L"\nBad News: Host is not informing pipe id.");
}
UnmapViewOfFile(map);
} else
{
std::wprintf(L"\nBad News: Mapped File %s was not found. LastError: %x", mapFile.c_str(), GetLastError());
}
CloseHandle(map);
std::wprintf(L"\n");
return 0;
}
Download the project/executable here:
Hi Rodney,
Great article! It has helped me a lot.
Can you shed any light on the hash for longer URIs?
Thanks,
Nick
Hi Nick,
I explain the logic here:
blogs.msdn.com/…/named-pipes-in-wcf-are-named-but-not-by-you-and-how-to-find-the-actual-windows-object-name.aspx
Excerpt:
The shared memory object (or memory mapped file) name is generated this way:
1.It gets the endpoint and substitute the host name by + or * depending on the wildcard mode.
2.It then add a “/” to the end if not present already and transform to up case all characters of pipe and path
3.If the size of the resulting string is bigger than 128 characters a hash is applied to the resulting string
4.The final name is net.pipe:E + Base64 of string generated in item 2 or net.pipe:H + Base64 of string generated in item 3 if uri is bigger than 128 characters
Hi Rodney,
Thanks for the quick response.
Yes, I read that article too. But when you say "a hash is applied", what exactly do you mean? Is it md5, SHA1, or something else? Also, is it definitely 128 characters? The URI I have is less than that (probably around 80-90 chars) but doesn't seem to follow the rest of the logic. Or are there other characters that are treated differently at all?
I've searched around a bit in the .Net code to see if I can find the logic myself, but haven't had much luck.
Thanks,
Nick
Hi Nick,
If number of bytes in UTF-8 of the canonical URI* >= 128
{
start = "net.pipe:H"
Name = Base64(SHA1(canonical URI*))
}
else
{
start = "net.pipe:E"
Name = Base64(canonical URI*)
}
* Depends on the wildcard mode. See step 1.
Please note that the hashing algorithm type is not part of the high level specification. It is currently SHA1 but it is not guaranteed to be this way forever, though it will most likely remain SHA1 in next versions.
If you want to add Hash in this C++ Application, see this C++ sample:
msdn.microsoft.com/…/aa382051(v=VS.85).aspx
Hope I have now answered your question,
This may not be possible but can you take the Mapped Memory Object Name and find the application that created it? I am trying to resolve a problem with the situation described here:
stackoverflow.com/…/3rd-party-app-breaks-our-wcf-application
In my particular situation both pipes are being created by two 3rd part applications. One of my applications fails to run correctly. One of the third party apps is aware of issues with the Garmin software but I don't have that software installed on my machine so I am left with trying to find the offending app.
If you can't help with this, I do appreciate your time.
Your articles on the name pipes has helped me to better understand the problems I am experiencing,
Steve,
Try to use SysInternals ProcMon and set a filter by path with some part containing the mapped memory file path. It should show you the culprit.