Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Hosted-services / serverless

Peer Name Resolution (PNRP)

4.85/5 (25 votes)
27 Oct 20058 min read 3   1.2K  
Introduction to Peer Name Resolution and Microsoft's Peer-to-Peer technology.

Sample Image

Background

This is the first article of many, where I will talk about the practical uses of Microsoft's Peer-to-Peer technology. Peer-to-Peer technology has been around since the late 90's. There have been several articles written for CodeProject that discuss the various aspects and implementations of a peer-to-peer infrastructure. There are also several open-source implementations including Gnucleus and Shareaza.

Microsoft recognized the value of this technology and spun up various research projects to investigate and document implementation approaches. Pastry was the earliest project that I recall that tackled the issues involved in building a peer-to-peer network. By late 2003, Microsoft released the Advanced Networking Pack for Windows XP SP1 which added basic Peer-to-Peer technology plumbing into Windows. While characteristically late to the game, their implementation recognized the value of using IPv6 as the foundation for building peer-to-peer networks. Microsoft has subsequently rolled this technology into Windows XP Service Pack 2 and continues to expand the infrastructure's capabilities into Windows Vista. Finally, given the nature of the technology, it's interesting to note that Microsoft has not included this support on any of its server platforms.

Introduction

Microsoft's entire Peer-to-Peer technology is exposed through the latest Platform SDK as C/C++ API calls. That's great for anyone who still develops applications in unmanaged code or managed C++. However, I'm a big fan of .NET managed code, and have been busy importing each API call into C# and creating a managed framework to simplify, as much as possible, peer-to-peer application development in .NET. So, without further delay, let's roll our sleeves up and dig in.

Clouds

As previously mentioned, IPv6 is at the heart of Microsoft's Peer-to-Peer technology. The beauty of IPv6 is that besides an address and port, a registered resource also has a scope. Scope was added to further identify the services/resources listening on a port. A group of resources connected in a peer-to-peer network using the same scope is also known as a Cloud. Clouds are closely related to IPv6 scopes but there are some minor differences.

A Cloud is like a membership at the YMCA, where only members (registered peers) can use the facilities (in this case, communicate). All members can use the facilities at any YMCA and so belong to the organization. In Peer-to-Peer terms, this organization is called the Global Cloud. The equivalent to individual YMCA facilities are called Link Clouds. That is, each LAN the computer is connected to has a Link Cloud.

The Global Cloud has the greatest range and allows applications to communicate over the Internet. Link clouds allow applications behind a firewall or connected through a common subnet to communicate.

To enumerate the available clouds, you must indicate the scope of the search:

Any Return any cloud the computer is connected to
Global Return the global cloud
LinkLocal Return any link local clouds

Next comes a series of of Windows Socket calls to synchronously begin a lookup and iterate over the results:

  • Call WSALookupServiceBegin to begin the enumeration and return a handle.
  • Call WSALookupServiceNext to retrieve a set of clouds matching the scope. Call this function until the application has retrieved all the clouds.
  • Call WSALookupServiceEnd to finish the enumeration.

To encapsulate this functionality, I created a Collection class that implements IEnumerable with an embedded class that implements IEnumerator. This allows a simple foreach loop to enumerate the clouds.

C#
public class PeerCloudCollection : IEnumerable
{
    private static WSAService service = WSAService.Instance;
    private IEnumerator clouds;

    public PeerCloudCollection(PNRP_SCOPE Scope)
    {
       clouds = new PeerCloudEnumerator(Scope);
    }

    public PeerCloudCollection()
    {
      clouds = new PeerCloudEnumerator(PNRP_SCOPE.Any);
    }

    private class PeerCloudEnumerator : IEnumerator
    {
      private static Guid SVCID_PNRPCLOUD = new Guid(0xc2239ce6, 
              0xc0, 0x4fbf, 0xba, 0xd6, 0x18, 0x13, 0x93, 0x85, 0xa4, 0x9a);
      private IntPtr hLookup;
      private IntPtr pResults;
      private PNRP_SCOPE scope;

      public PeerCloudEnumerator(PNRP_SCOPE Scope)
      {
         scope = Scope;
         Reset();
      }

     ~PeerCloudEnumerator()
     {
        if (pResults != IntPtr.Zero) Marshal.FreeHGlobal(pResults);
        if (hLookup != IntPtr.Zero) PnrpNative.WSALookupServiceEnd(hLookup);
     }

#region IEnumerator Members

    public void Reset()
    {
        int err;
        if (hLookup != IntPtr.Zero)
        {
            err = PnrpNative.WSALookupServiceEnd(hLookup);
            if (err != 0)
             throw new 
               System.Net.Sockets.SocketException(PnrpNative.WSAGetLastError());
            hLookup = IntPtr.Zero;
            pResults = IntPtr.Zero;
        }
        
        CSADDR_INFO csaAddr = new CSADDR_INFO();
        PNRPCLOUDINFO pnrpCloudInfo = new PNRPCLOUDINFO();
        BLOB blPnrpData = new BLOB();
        WSAQUERYSET querySet = new WSAQUERYSET();
        
        pnrpCloudInfo.dwSize = Marshal.SizeOf(typeof(PNRPCLOUDINFO));
        pnrpCloudInfo.Cloud.Scope = scope;
        
        blPnrpData.cbSize = Marshal.SizeOf(typeof(PNRPCLOUDINFO));
        blPnrpData.pBlobData = Marshal.AllocHGlobal(blPnrpData.cbSize);
        Marshal.StructureToPtr(pnrpCloudInfo, blPnrpData.pBlobData, false);
        
        querySet.dwSize = Marshal.SizeOf(typeof(WSAQUERYSET));
        querySet.dwNameSpace = 39; // NS_PNRPCLOUD
        querySet.lpServiceClassId = 
           Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Guid)));
        Marshal.StructureToPtr(SVCID_PNRPCLOUD, 
                querySet.lpServiceClassId, false);
        
        querySet.lpBlob = 
          Marshal.AllocHGlobal(Marshal.SizeOf(typeof(BLOB)));
        Marshal.StructureToPtr(blPnrpData, querySet.lpBlob, false);
        
        IntPtr qryptr = 
          Marshal.AllocHGlobal(Marshal.SizeOf(typeof(WSAQUERYSET)));
        Marshal.StructureToPtr(querySet, qryptr, false);
    
        err = PnrpNative.WSALookupServiceBegin(qryptr, 
              WSALookup.LUP_RETURN_ALL, out hLookup);
        if (err != 0)
         throw new 
           System.Net.Sockets.SocketException(PnrpNative.WSAGetLastError());
    }
    
    public object Current
    {
        get
        {
            WSAQUERYSET querySet = (WSAQUERYSET)
              Marshal.PtrToStructure(pResults, typeof(WSAQUERYSET));
            BLOB blPnrpData = (BLOB)
              Marshal.PtrToStructure(querySet.lpBlob, typeof(BLOB));
            PNRPCLOUDINFO pnrpCloudInfo = (PNRPCLOUDINFO)
              Marshal.PtrToStructure(blPnrpData.pBlobData, 
              typeof(PNRPCLOUDINFO));
            
            PeerCloud cloud = new PeerCloud(querySet.lpszServiceInstanceName, 
                      pnrpCloudInfo.Cloud.Scope, pnrpCloudInfo.Cloud.ScopeId);
            Marshal.FreeHGlobal(pResults);
            return cloud;
        }
    }

    public bool MoveNext()
    {
        int err = Pnrp.LookupServiceNext(hLookup, out pResults);
        if (err != 0) return false;
        return true;
    }

#endregion

    public IEnumerator GetEnumerator()
    {
        return clouds;
    }
    
    IEnumerator IEnumerable.GetEnumerator() 
    {
        return GetEnumerator();
    }
}

The Reset function passes a complicated set of data structures into the WSALookupServiceBegin function. The LUP_RETURN_ALL parameter tells the subsequent call to WSALookupServiceNext to fully populate the PNRPCLOUDINFO data structure. MoveNext uses the WSALookupServiceNext to retrieve the results. true or false is returned depending on whether a result was returned. The Current function marshals the data structures into managed equivalents and creates a PeerCloud class to represent each cloud as a managed object. The following VB code shows how to use this cloud collection:

VB
For Each cloud As PeerCloud In New PeerCloudCollection(scope)
   Debug.Writeline(cloud.Name)
Next

Of course, before peers in a cloud can communicate, they must be able to locate each other. This is were Peer Name Resolution comes in.

Peer Name Resolution (PNRP)

PNRP; it's a mouthful. It's also a serverless DNS technology that allows nodes to discover each other. Think about that for a second. It allows your Windows XP box to become its own DNS server. No need to pay $10 to register each domain name. But there's a catch; it's limited to the domain name pnrp.net.

As the first letter in PNRP suggests, you must register a Peer Name. Peer names are fixed names for resources such as computers, users, groups, or services. This is similar to today's DNS except, instead of just IP addresses, the resources can be more granular. A Peer Name is a case-sensitive text string that has the format "Authority.Classifier". The value of Authority depends on whether the name is secured or unsecured. The value is always 0 for an unsecured Authority (secured names will be discussed in a later article). The value of Classifier is a text string name you give for the resource and cannot contain spaces. The following list shows some examples of peer names:

  • 0.test
  • 0.my.peername
  • 6520c005f63fc1864b7d8f3cabebd4916ae7f33d.test

PNRP uses peer names to identify resources in a peer network. The key here is "Peer Network". This isn't the whole IPv6 network that the computer is connected to, it's limited to just the resources available within a Cloud. Registering any resource not managed by the Peer-to-Peer networking APIs either will result in an error or won't be resolved later.

Register and Unregister a Peer Name

To register an unsecured Peer Name, you must provide a valid unsecured Peer Name and an IP address. Optionally, you can indicate the Cloud name (Global by default) and an additional comment or description associated with the resource. This information is stored in the WSAQUERYSET data structure and passed to the WSASetService with the Register option.

C#
public static void Register(PeerRegistration Registration)
{
    CSADDR_INFO csaAddr = new CSADDR_INFO();
    PNRPINFO pnrpInfo = new PNRPINFO();
    BLOB blPnrpData = new BLOB();
    WSAQUERYSET querySet = new WSAQUERYSET();
    
    //
    // fill a CSADDR_INFO structure from the address
    //
    csaAddr.iProtocol = 6; // IPPROTO_TCP
    csaAddr.iSocketType = 1; // SOCK_STREAM;
    csaAddr.LocalAddr.iSockaddrLength = 
            Marshal.SizeOf(typeof(SOCKADDR_IN6));
    csaAddr.LocalAddr.lpSockaddr = 
            Marshal.AllocHGlobal(csaAddr.LocalAddr.iSockaddrLength);
    Marshal.StructureToPtr(Registration.address, 
            csaAddr.LocalAddr.lpSockaddr, false);

    //
    // build the WSAQUERYSET required to register
    //
    pnrpInfo.dwSize = Marshal.SizeOf(typeof(PNRPINFO));
    pnrpInfo.dwLifetime = 60*60*8; // 8 hours
    if (Registration.Identity != string.Empty)
        pnrpInfo.lpwszIdentity = 
          Marshal.StringToHGlobalUni(Registration.Identity);
    
    blPnrpData.cbSize = Marshal.SizeOf(typeof(PNRPINFO));
    blPnrpData.pBlobData = Marshal.AllocHGlobal(blPnrpData.cbSize);
    Marshal.StructureToPtr(pnrpInfo, blPnrpData.pBlobData, false);
    
    querySet.dwSize = Marshal.SizeOf(typeof(WSAQUERYSET));
    querySet.dwNameSpace = 38; // NS_PNRPNAME
    querySet.dwNumberOfCsAddrs = 1; // one address
    querySet.lpServiceClassId = 
      Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Guid)));
    Marshal.StructureToPtr(SVCID_PNRPNAMEV1, 
            querySet.lpServiceClassId, false);
    querySet.lpszServiceInstanceName = Registration.PeerName;
    if (Registration.CloudName != string.Empty)
        querySet.lpszContext = 
            Marshal.StringToHGlobalUni(Registration.CloudName);
    if (Registration.Comment != string.Empty)
        querySet.lpszComment = 
            Marshal.StringToHGlobalUni(Registration.Comment);

    querySet.lpcsaBuffer = 
       Marshal.AllocHGlobal(Marshal.SizeOf(typeof(CSADDR_INFO)));
    Marshal.StructureToPtr(csaAddr, querySet.lpcsaBuffer, false);
    
    querySet.lpBlob = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(BLOB)));
    Marshal.StructureToPtr(blPnrpData, querySet.lpBlob, false);
    
    IntPtr qryptr = 
      Marshal.AllocHGlobal(Marshal.SizeOf(typeof(WSAQUERYSET)));
    Marshal.StructureToPtr(querySet, qryptr, false);
    
    int err = PnrpNative.WSASetService(qryptr, 
                WSAESETSERVICEOP.Register, 0);
    if (err != 0)
     throw new 
      System.Net.Sockets.SocketException(PnrpNative.WSAGetLastError());
}

To unregister an unsecured Peer Name, you must provide a valid unsecured Peer Name and optionally the Cloud name (Global by default). This information is stored in the WSAQUERYSET data structure and passed to the WSASetService with the Delete option. While not listed here, the code is included in the download.

Searching for a Peer Name

PNRP includes the ability to search for registered Peer Names. To resolve a Peer Name, you must provide the Peer Name, search criteria and optional cloud name (Global by default) and IP address hint. Typically, a lookup is used to determine if a Peer Name already exists or to contact it directly. The following search criteria options are supported:

Default Uses the NonCurrentProcessPeerName option.
AnyPeerName The matching peer name can be registered locally or remotely.
NearestNonCurrentProcessName The matching peer name can be registered locally or remotely, but the resolve request excludes any peer name registered by the process making the resolve request and looks for the service closest to the local IP address.
NearestPeerName The matching peer name can be registered locally or remotely, but the resolve request looks for the service closest to the local IP address.
NearestRemotePeerName The resolve request excludes any peer name registered locally on this computer and looks for the service closest to the local IP address.
NonCurrentProcessPeerName The matching peer name can be registered locally or remotely, but the resolve request excludes any peer name registered by the process making the resolve request.
RemotePeerName The resolve request excludes any peer name registered locally on this computer.

Again, a series of Windows Socket calls are used to synchronously begin a lookup.

  • Call WSALookupServiceBegin to begin the enumeration and return a handle.
  • Call WSALookupServiceNext to resolve the peer name.
  • Call WSALookupServiceEnd to complete the enumeration.

The following code shows this:

C#
public static bool Lookup(PeerRegistration Registration)
{
    PNRPINFO pnrpInfo = new PNRPINFO();
    BLOB blPnrpData = new BLOB();
    WSAQUERYSET querySet = new WSAQUERYSET();
    
    pnrpInfo.dwSize = Marshal.SizeOf(typeof(PNRPINFO));
    pnrpInfo.nMaxResolve = 1;
    pnrpInfo.dwTimeout = 30;
    pnrpInfo.enResolveCriteria = Registration.Criteria;
    
    blPnrpData.cbSize = Marshal.SizeOf(typeof(PNRPINFO));
    blPnrpData.pBlobData = Marshal.AllocHGlobal(blPnrpData.cbSize);
    Marshal.StructureToPtr(pnrpInfo, blPnrpData.pBlobData, false);
    
    querySet.dwSize = Marshal.SizeOf(typeof(WSAQUERYSET));
    querySet.dwNameSpace = 38; // NS_PNRPNAME
    querySet.lpServiceClassId = 
          Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Guid)));
    Marshal.StructureToPtr(SVCID_PNRPNAMEV1, 
            querySet.lpServiceClassId, false);
    querySet.lpszServiceInstanceName = Registration.PeerName;
    if (Registration.CloudName != string.Empty)
        querySet.lpszContext = 
            Marshal.StringToHGlobalUni(Registration.CloudName);

    querySet.lpBlob = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(BLOB)));
    Marshal.StructureToPtr(blPnrpData, querySet.lpBlob, false);
    
    IntPtr qryptr = 
           Marshal.AllocHGlobal(Marshal.SizeOf(typeof(WSAQUERYSET)));
    Marshal.StructureToPtr(querySet, qryptr, false);
    
    IntPtr hLookup;
    int err = PnrpNative.WSALookupServiceBegin(qryptr, 
              WSALookup.LUP_RETURN_NAME | WSALookup.LUP_RETURN_ADDR | 
              WSALookup.LUP_RETURN_COMMENT, out hLookup);
    if (err != 0)
     throw new 
       System.Net.Sockets.SocketException(PnrpNative.WSAGetLastError());

    IntPtr pResults;
    err = LookupServiceNext(hLookup, out pResults);
    
    bool found = false;
    if (err == 0)
    {
        querySet = (WSAQUERYSET)Marshal.PtrToStructure(pResults, 
                                           typeof(WSAQUERYSET));
        CSADDR_INFO csaAddr = (CSADDR_INFO)
                Marshal.PtrToStructure(querySet.lpcsaBuffer, 
                typeof(CSADDR_INFO));
        // return the first IPv6 address found
        for (int i = 0; i < querySet.dwNumberOfCsAddrs; i++)
        {
            if (csaAddr.iProtocol == (int)System.Net.Sockets.ProtocolType.Tcp 
                && csaAddr.RemoteAddr.iSockaddrLength == 
                Marshal.SizeOf(typeof(SOCKADDR_IN6)))
            {
                SOCKADDR_IN6 addr = (SOCKADDR_IN6)
                    Marshal.PtrToStructure(csaAddr.RemoteAddr.lpSockaddr, 
                    typeof(SOCKADDR_IN6));
                Registration.Address = new System.Net.IPEndPoint(new 
                    System.Net.IPAddress(addr.sin6_addr), addr.sin6_port);
                found = true;
                break;
            }
        }
        Marshal.FreeHGlobal(pResults);
    }

    PnrpNative.WSALookupServiceEnd(hLookup);
    return found;
}

DNS Name Corresponding to a Peer Name

Since PNRP is a serverless DNS, it makes sense to be able to lookup the DNS name associated with a Peer Name. It's also useful to determine the Peer Name given a DNS name. Starting in Windows Vista, two additional Peer-to-Peer APIs are provided to do just this. The code below shows calling these functions from C#:

C#
public static string PnrpToDns(string PnrpName)
{
    int length = 255;
    System.Text.StringBuilder DnsName = 
                   new System.Text.StringBuilder(length);
    int err = PnrpNative.PeerNameToPeerHostName(PnrpName, 
                                    DnsName, ref length);
    if (err != 0)
        throw new System.Net.Sockets.SocketException(err);
    return DnsName.ToString();
}

public static string DnsToPnrp(string DnsName)
{
    int length = 255;
    System.Text.StringBuilder PnrpName = 
           new System.Text.StringBuilder(255);
    int err = PnrpNative.PeerHostNameToPeerName(DnsName, 
                                  PnrpName, ref length);
    if (err != 0)
        throw new System.Net.Sockets.SocketException(err);
    return PnrpName.ToString();
}

Points of Interest

Now that you've seen the gory details of using the WSAxx functions to interact with PNRP, you'll be glad to know that Microsoft will be providing wrapper functions to hide these details. These wrapper functions will be available for Windows Vista Beta 2 and later. I can only guess that these functions will also be available in Windows XP Service Pack 3. While there is currently no documentation for these functions, I've taken a guess at their purpose.

  • PeerPnrpGetCloudInfo - returns an array of PNRPCLOUDINFO structures matching the given scope.
  • PeerPnrpGetEndpoint - lookup a peer name to get its address.
  • PeerPnrpRegister, PeerPnrpUnregister - register and unregister a peer name.
  • PeerPnrpUpdateRegistration - allows info such as address and comment to be updated for a previously registered peer name.
  • PeerPnrpResolve - synchronously lookup if a peer name exists.
  • PeerPnrpStartResolve, PeerPnrpEndResolve - asynchronously lookup a peer name.

Links to Resources

I have found the following resources to be very useful in understanding Microsoft's Peer-to-Peer technology:

Conclusion

I hope you have found this article as a useful starting point. I'm considering writing more articles on the following subjects to further your understanding of Microsoft's Peer-to-Peer technology:

  1. Peer Name Resolution - netsh.
  1. Peer Graph - The Basics
  2. Peer Graph - Nodes and Connections
  3. Peer Graph - Records
  4. Peer Graph - Attributes
  5. Peer Graph - Searching
  1. Peer Groups and Identity
  1. Peer Collaboration - People Near Me
  2. Peer Collaboration - EndPoints
  3. Peer Collaboration - Capabilities
  4. Peer Collaboration - Presence
  5. Peer Collaboration - Invitations

If you have suggestions or other topics, please leave a comment. Oh! and don't forget to vote.

History

  • Initial revision.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here