Was this page helpful?
Your feedback about this content is important. Let us know what you think.
Additional feedback?
1500 characters remaining
Export (0) Print
Expand All

Peer-to-Peer Programming with WCF and .NET Framework 3.5

Amit Bahree (Avanade UK, www.desigeek.com)

Chris Peiris (Avanade Australia, www.chrispeiris.com)

February 2008

Applies to:

  Microsoft .NET Framework 3.5

  Windows Vista

  Microsoft Visual Studio 2008

Summary: This article explores peer-to-peer (P2P) concepts and how they are supported in Microsoft .NET Framework 3.5. It aims to provide an understanding of P2P fundamentals, as well as show how to use the new features of Windows Communication Foundation (WCF). Finally, it introduces the new features of managing P2P networks, including the use of People Near Me and Windows Address Book—new features released as part of Windows Vista. (39 pages)

Introduction

What Is New in .NET Framework 3.5?

What Is Peer-to-Peer (P2P) Computing?

QuickReturnTraderChat Application

P2P Security

QuickReturnSecureTraderChat Sample

Peer Name

People Near Me and Contacts

Working with NetShell

Debugging Tips

Bringing It All Together

Conclusion

References

Introduction

Although the term peer-to-peer (P2P) computing has gained popularity recently, the usefulness of P2P to address business and technical problems remains poorly understood. In this article, we explore P2P concepts and how they are supported in Microsoft .NET Framework 3.5. We will also build a sample application to demonstrate the concepts that are involved.

Because many of the new features of P2P in .NET Framework 3.5 are available only on Windows Vista, most of the elements that we discuss in this article require Windows Vista. You can run only a handful of things on Microsoft Windows XP; these things are discussed in the “Debugging Tips” section.

Peer channel (see the “What Is Peer Channel?” section) was originally released as part of .NET Framework 3.0. This was a significant step for the ability to write P2P applications in managed code. However, there were many basic elements still lacking within the managed stack. The only way to get around these basic elements was via the unmanaged C++ stack.

.NET Framework 3.5 adds a couple of new namespaces—System.Net.PeerToPeer and System.Net.PeerToPeer.Collaboration—that significantly improve on the availability of these basic elements via the managed stack. These new namespaces are part of the System.Net.dll assembly and are designed with the P2P application flow (described earlier) and collaboration in mind. Also, they provide access to the P2P networking infrastructure with options to find a P2P, join a mesh, start interaction, and so on. For the first time, a developer can write complete P2P applications that are developed in managed code without having to get into the unmanaged stack in C++.

On the collaboration front, the System.Net.PeerToPeer.Collaboration namespace, which is also new in .NET Framework 3.5, provides two contexts for collaborating: People Near Me (see the “P2P Application Flow” section) and Contacts. People Near Me (PNM) is a new feature that allows applications to discover people on the local subnet and interact with them. The Contacts feature, which is also new, essentially refers to people who are trusted users and have been added as contacts. Unlike PNM, these contacts do not have to be on the same subnet for you to be able to interact with them.

P2P computing is a term that has gained a lot of popularity in recent times. Today, organizations and businesses increasingly depend on collaboration between individuals and groups to perform essential tasks. P2P applications form more ad hoc online groups for business, entertainment, and cultural purposes. As a result, collaboration has become more essential at an individual level.

Essentially, P2P computing is a set of networked computers that rely on the computing power and bandwidth of the individual computers on the network, as opposed to the traditional approach of relying on a smaller number of more powerful server computers on the network. A computer that is connected to a P2P network can be categorized as a node or peer. The nodes in a P2P network are usually connected on an ad hoc basis. This is where the real power in a P2P network lies, because the peers are responsible for uploading and downloading data among themselves without the need for a server.

Two types of P2P network exist: a pure network and a hybrid network. A pure P2P network has no concept of a client or a server; it has only nodes, which act in the capacity of both a server and a client, as needed. A hybrid P2P network, on the other hand, has a central server that keeps track of the various peers on the network. This server responds to requests from the peers for information only and does not store any data. The peers are responsible for hosting the information. For example, in a file-sharing P2P application, the files are stored by the peer, and the server is aware of the files that are stored at each peer.

Most P2P solutions rely on some nonpeer elements in the solution, such as Domain-Name System (DNS, used to translate computer host names to IP addresses). Some P2P solutions also have the notion of a superpeer, in which other peers are connected to this superpeer in a star-like fashion. Over time, these superpeers could also be used as local servers.

How Are Nodes Identified?

The networks in P2P applications are also called meshes (or, sometimes, a mesh network), in that they are akin to a wire mesh. Each node in a mesh at a minimum has bidirectional communication capability with its neighbors. A cloud is a mesh network that has a specific address scope. These scopes are closely related to IPv6 scopes; the peers in a cloud are those that can communicate within the same IPv6 scope.

On a mesh, each node must be identified by a unique ID that is usually called a peer ID or mesh ID. To resolve these peer IDs to their corresponding Internet addresses, the Peer Name Resolution Protocol (PNRP) is used, instead of DNS. Each peer node, irrespective of type (such as computer, user, group, device, service, and so on), can have its own peer ID. This list of IDs is distributed among the peers by using a multilevel cache and referral system that allows name resolution to scale to billions of IDs, while requiring minimal resources on each node.

An endpoint is defined as a combination of a peer ID, port number, and communication protocol. By using an endpoint, a peer can send data between nodes in two ways. One way is for a peer to send the data to another peer directly. The other way is for a peer to send the data to all of the other peers on the same mesh, which is also known as flooding. A flooded message could arrive at the same peer multiple times via different routes on the mesh.

Peer Names

Peer names can be registered as either secured or unsecured. Unsecured names are recommended for use in a private network only, because the names are strings and can easily be spoofed. Secured names, on the other hand, must be registered, and they are protected with a certificate and digital signature.

A PNRP ID is 256 bits long; the high-order 128 bits are a hash of the peer name that is assigned to the endpoint, and the lower 128 bits of the PNRP ID are an automatically generated number that is used for service location. The format for the peer name is “Authority.Classifier”. When using a secured network, the Authority is a secure hash (using Secure Hash Algorithm, or SHA1) of the public key of the peer name in hex. When using an unsecured network, the Authority is the single character 0 (zero). The Classifier is a Unicode string that is up to 150 characters long and identifies the application. The autogenerated number, which is used by the lower 128 bits, uniquely identifies different instances by using the classifier that participates in the same mesh. The combination of 256-bit mesh ID and the service location allow multiple PNRP IDs to be registered from a single computer.

Name Resolution with PNRP

When one peer wants to resolve the name of another peer, it constructs the peer ID (as discussed earlier) and tries to find that entry in its cache for the ID. If a match is found, it sends a PNRP request message to the peer and waits for a response. This approach ensures that the target peer node, with which another peer is trying to communicate, is active in the cloud. If no match is found, an iterative process is used with the target peer that informs the sender of the peer that is the closest match to the ID that is trying to be resolved. At this stage, it is up to the original sender to send the same request to the matching peer as the one to which it was pointing. If the new peer to whom the sender was pointed is also incorrect, that in turn will return the next-closest matching peer to the sender, and so on.

When a PNRP request message is forwarded, the nodes that are forwarded to and the responses that are received are both cached. This prevents a situation in which things could get into an endless loop.

P2P Application Flow

There are three types of P2P application: one-to-one, one-to-many, and many-to-many. When one peer in a mesh wants to communicate with another, it must find the other peer, send an invitation, and create a session between the two. These steps are described as follows:

  • Find peer—Essentially, to “talk” to another peer, you must first find it. There are two ways to go about this. The first is to find other peers on the LAN of which you are part. The second is to find peer or peer groups using by PNRP. If you are finding other peers on the LAN, you should use the PNM feature and integrate that into your application. PNM uses WS-Discovery to find users who are signed in.
    There are many requirements for this to work, such as people discovery, application discovery, metadata discovery, security, invitation, and so on. We discuss PNM in more detail later in this article. When using PNRP, on the other hand, it is a server-less name resolution that can be either on the local network or over the Internet.
  • Send invitation—Invitations are real-time and can go to PNM or peers over the Internet, via either a user message or application data such as mesh name, endpoint, and so on. A listener at the other end detects this incoming invitation request and launches the appropriate application.
  • Join mesh—The last step to establish a session is to specify the mesh name and credentials (if applicable) that one is intending to join.

What Is Peer Channel?

Until fairly recently, the P2P networking stack was mostly available only as unmanaged code, and a developer had to use C++ to be able to use any of it. This stack is part of Windows XP, and it has been improved as part of Windows Vista and Microsoft Windows Server 2008. Microsoft also has a managed code implementation for a subset of the functionality that is exposed by the P2P networking stack—called peer channel—and is released as part of WCF. Because peer channel is a “managed stack,” you can use any .NET language—making implementation of P2P applications easier and more productive, when compared to unmanaged code.

A typical WCF channel has two participants—namely, a client and a server. A peer channel, on the other hand, can have any number of participants. A message that is sent by one participant will be received by all other participants on the channel. However, certain mechanisms in the peer channel allow you to send a message to only part of the mesh, instead of the whole mesh.

To resolve the addresses of a node in a peer-channel mesh, you can use either PNRP or a custom resolver. When a node is resolved, that target node can either accept or decline the connection. If the connection is accepted by the target node, it sends it a welcome message that will contain, among other things, the list of other nodes that are part of the mesh. If the connection is refused, the existing node sends the prospective node a refusal message that contains the reason for the refusal and a list of the addresses of the other nodes in the mesh.

In WCF, a node that will be part of a mesh is defined via the PeerNode class in your application. The endpoint address for that node is defined via the PeerNodeAddress class (which internally implements the EndpointAddress class). The number of neighbors of each node dictates the overall structure of a peer-channel mesh that is actively maintained, which results in an evenly distributed mesh. For example, a node in the mesh tries to maintain between two to seven connections to its neighbors. Although an ideal state for the node is to have three connections, it will accept up to seven connections. As soon as a node has reached that threshold, it will start refusing any new connections. If a node loses all of its neighbors, it will enter a maintenance cycle in which it tries to acquire new neighbors to get to its optimum state of three connections. Note that you cannot change or configure either the thresholds or the underlying mesh, because the peer channel owns and maintains this.

The peer channel also tries to improve efficiency by limiting communication within the mesh by keeping repetitive messages passed to a minimum. When a node sends a message to the mesh, it sends it to the neighbors to which it is connected. These neighbors, in turn, inspect the message and then forward it to their neighbors; but they do not forward it to the neighbor from whom they got the message to start. In addition, a connection to a neighbor might be terminated if it keeps trying to resend a message that has been processed previously. Internally, each node keeps a local cache of the WS-Addressing message ID and the ID of the neighbor that delivered that message. This allows an optimized mesh network that does not waste resources with repeating data.

A node can send messages to a subset of the mesh by assigning a hop count to the message. A hop count keeps a count of the number of nodes to which a message has been forwarded. This count is expressed as an integer within the message header and is decremented with each hop until it reaches a value of zero, after which it is not forwarded.

To get a better understanding of how everything comes together with the peer channel, let us start with a simple application that is called QuickReturnTraderChat. We have a few traders who are spread across a stock exchange and need the ability to chat with each other. The exchange, being a secure environment, does not allow any access to IM clients and wants to use the QuickReturnTraderChat application to allow talking to each other. This application allows more than one trader to broadcast a message to the other traders—similar to an IRC channel. You will look first at the unsecured version of this sample, and then later make it secure, so that no one else can eavesdrop on the conversation.

The application is simple and is implemented as a Windows application that contains one form. For clarity, we will not show the Windows Form boilerplate code, so that you can concentrate on the peer-channel aspects.

Message Interface

A peer-channel service contract is just a WCF service contract in which the OperationContract attribute is defined in one way, as shown in Listing 1. The interface is called IQuickReturnTraderChat and has only one operation, which is called “Say” and accepts two parameters: user and message.

[ServiceContract()]
public interface IQuickReturnTraderChat
{
    [OperationContract(IsOneWay = true)]
    void Say(string user, string message);
}

Listing 1. IQuickReturnTraderChat service contract

Service Configuration

Listing 2 shows the service side of the configuration. This application listens at the net.p2p://QuickReturnTraderChat address. Being a P2P application, its binding is set to netPeerTcpBinding and the contract for the endpoint is set to QuickReturnTraderChat.IQuickReturnTraderChat (following the namespace.interface format). The binding configuration is intentionally kept separate and is shown later in Listing 3.

<service name="QuickReturnTraderChat.Main">
    <host>
        <baseAddresses>
            <add baseAddress="net.p2p://QuickReturnTraderChat"/>
        </baseAddresses>
    </host>

    <endpoint 
        name="QuickTraderChat"
        address=""
        binding="netPeerTcpBinding" 
        bindingConfiguration="BindingUnsecure"
        contract="QuickReturnTraderChat.IQuickReturnTraderChat" 
      />
</service>

Listing 2. Service configuration

Binding-Configuration File

As we stated earlier, a P2P application’s binding is set to netPeerTcpBinding, and the resolver mode is set to PNRP. Because this application is not secure, we have the security mode switched off by setting it to None.

<bindings>
    <netPeerTcpBinding>
        <binding  name="BindingUnsecure">
            <security mode="None"/>
            <resolver mode="Pnrp"/>
        </binding>
    </netPeerTcpBinding>
</bindings>

Listing 3. Binding configuration

Main Application

The main application that is shown in Figure 1 consists of a Windows Form that has two text boxes: one for the message that is being sent (called textBoxMessage), and one to show the conversation (called textBoxChat). The form contains also one button (called buttonSend).

Figure 1. QuickReturnTraderChat application

The class that implements the Windows Form is called Main and is implemented as is shown in Listing 4. This form inherits from the .NET Form class and also implements the IQuickReturnTraderChat interface that was defined earlier. Because this is a WCF Service, the class is decorated with the ServiceBehavior attribute—the InstanceContextMode controlling when a new Service object should be created. In our case, we want this to behave as a singleton; as a result, the InstanceContextMode is set to Single.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public partial class Main : Form, IQuickReturnTraderChat
{
}

Listing 4. Service-host class definition

As shown in Listing 5, the Main class implements two methods—StartService and StopService—that start and stop, respectively, the service host (as the names suggest). The Main class also has a few member variables that expose the Channel, ServiceHost, and ChannelFactory.

IQuickReturnTraderChat channel;
ServiceHost host = null;
ChannelFactory<IQuickReturnTraderChat> channelFactory = null;
string userID = "";
private void StartService()
{
    //Instantiate new ServiceHost
    host = new ServiceHost(this);
     //Open ServiceHost
    host.Open();
    //Create a ChannelFactory and load the configuration setting
    channelFactory = new ChannelFactory<IQuickReturnTraderChat>
                                     ("QuickTraderChatEndpoint");
    channel = channelFactory.CreateChannel();
    //Lets others know that someone new has joined
    channel.Say("Admin", "*** New User " + userID + " Joined ****" + 
                                                Environment.NewLine);
}
private void StopService()
{
     if (host != null) {
          channel.Say("Admin", "*** User " + userID + " Leaving ****" + 
                                             Environment.NewLine);
          if (host.State != CommunicationState.Closed) {
               channelFactory.Close();
               host.Close();
          }
     }
}

Listing 5. Service-host implementation

IQuickReturnTraderChat Implementation (the Receiver)

We have both the service side and the receiver side of things in the same class. The configuration for the receiver is shown in Listing 6; it is quite similar to the sender configuration and uses the same binding.

<client>
    <endpoint
        name="QuickTraderChatEndpoint"
        address="net.p2p://QuickReturnTraderChat"
        binding="netPeerTcpBinding"
        bindingConfiguration="BindingUnsecure"
        contract="QuickReturnTraderChat.IQuickReturnTraderChat"
/>

Listing 6. Receiver configuration

The receiver here is fairly simple; all it does is echo out the message to the chat text box on the Windows Form, as shown in Listing 7.

void IQuickReturnTraderChat.Say(string user, string message)
{
    textBoxChat.Text += user + " says: " + message;
}

Listing 7. Receiver implementation

Invoking the Service

The service is invoked in the Click event of the Send button, as shown in Listing 8. The second line is where we invoke service. If you recall, channel is of type IQuickReturnTraderChat and is defined in the Main class (shown in Listing 4).

private void buttonSend_Click(object sender, EventArgs e)
{
    string temp = textBoxMessage.Text + Environment.NewLine;
    //Invoke the Service
    channel.Say(userID, temp);
    textBoxMessage.Clear();
}

Listing 8. Service invocation

As you can see, creating a simple P2P application by using WCF is fairly trivial, and you do not need to do anything with the Windows P2P Networking stack. Our QuickReturnTraderChat application has been kept fairly simple, to show you the concept and how to implement a P2P application.

Security in a P2P network is an interesting challenge. As far as an application is concerned, when securing a P2P network, there are two points of interest. Firstly, only authorized users get on the network. Secondly, the message that you received originated from a known and trusted source, and the message itself has not been tampered with during transmission.

The first aspect is relatively easy to achieve: When a new application or user logs on to the mesh, that application or user can be challenged to authenticate before being allowed to join the mesh. The second aspect is a little more difficult, because you are not directly connected to another peer in the mesh. However, with WCF, this is relatively straightforward, because the NetPeerTcpBinding class exposes the PeerSecuritySettings class via the Security property.

So, how does it all come together with WCF? For OutputChannels, which reside on the sender, each message that is sent is signed by using a certificate; and all messages, before being sent to an application, are validated for this credential. The certificate that is needed is provided by using the PeerCredential.Certificate property. The validation that was stated earlier can be implemented via an instance of the X509CertificateValidator class, which is provided as part of the PeerCredential.MessageSenderAuthentication property. When the message arrives on the other end, the peer channel ensures the validity of the message before forwarding it up the chain to the application.

Peer-Channel Security

You specify the security settings for the peer channel by using the Security property, which is available on the NetPeerTcpBinding class. This property operates like any other standard binding in WCF. You can apply four types of security at this level, and they are exposed via the Mode property; the underlying class is in the PeerSecuritySettings class. These four options for security are the following:

  • None—No security is required.
  • Transport—No message security is implemented; only neighbor-to-neighbor security is required.
  • Message—Only message authentication is required when communicating over an open channel.
  • TransportWithMessageCredential—This is essentially a combination of Transport and Message (defined previously). It would require that the message be secure and that authentication be required over secure neighbor-to-neighbor channels.

Note: If security is enabled on the binding and is set to Message or TransportWithMessageCredential, messages that pass through both on the outbound and on the inbound must be secured by using X509Certificate.

The PeerChannel class provides two ways to authenticate two peers, which are configured by using the PeerTransportSecurityElement.CredentialType property. This is either Password or Certificate. When this is set to Password, every peer needs a password to connect. The owner of the mesh is responsible for setting the password initially and communicating it to peers whom you will allow to join the mesh. On the other hand, when this is set to Certificate, authentication is based on X509Certificate.

When an application initiates a peer-channel instance, an instance of the peer-channel transport manager is started. The transport manager resolves the endpoint address of the requested peers and the mesh. PNRP acts as the default resolver for this; however, you can choose to implement a custom resolver, too. As soon as the address has been resolved, the transport manager initiates a connection request to each of the peers.

Password-Based Authentication

When using the password-based authentication, the steps that are used to initiate a connection with the transport manager are the same. The main difference is that when a peer initiates a connection request, the link between the two peers is over a SSL connection. Also, as the first step after initiating connection between the two peers, the initiator peer will send a custom handshake message that authenticates the password. If the responder peer is satisfied with this, it accepts the connection and sends a similar response to the originating peer. If the initiator peer is satisfied with this, the connection is established; if not, the connection is abandoned.

This handshake must contain some metadata for it to function. The first piece of the metadata is the certificate with which the secure connection is established; the second metadata is the password with which the handshake is established. The PeerCredential class is exposed as the Peer property on the ChannelFactory.Credentials property. This is demonstrated in the secure version of the chat sample that was discussed earlier in the article. This secure version is called QuickReturnSecureTraderChat (you’ll see it a bit later).

Certificate-Based Authentication

When using the certificate-based authentication mode, the application has control of the authentication process, compared to the WCF runtime. There is no custom handshake involved; instead, the transport manager, after receiving the certificate, passes that on to the application to authenticate. To get this functionality, the application must provide a couple of certificates. The first certificate establishes the SSL connection and the identity between the peers. The second certificate provides a concrete implementation by the application for the abstract X509CertificateValidator class. This is also demonstrated in the secure version of the chat sample (you’ll see it a bit later).

Message Security

If you are interested in securing the message itself to ensure that it has not been tampered with during transmission, you must use the Message security option. Effectively, when this is requested, the peer channel on every outbound message includes a signature; vice versa, on every inbound message, it validates the signature. The signature is validated against the same certificate (without the specific private keys, of course). The signatures that are added to the message are compatible with all of the peers on the mesh.

A peer channel can verify signatures that are specific to the application through a “hook” that allows you to participate in its signature-verification routine. This hook is in a concrete implementation of the abstract X509CertificateValidator class. This allows you to have any criteria for the pass or fail validation.

QuickReturnSecureTraderChat essentially is the same as the chat application that was discussed earlier, with the addition of security. For the sake of simplicity, this was implemented as a separate solution. In the real world, you would probably read the security information via a configuration setting; based on that, you would either enable or disable the security options. As discussed earlier, security can be set by using either a password or an X509 certificate. For our sample here, we will use a password; however, we will show how easy it is to change this to use a certificate.

Service Configuration

The service side of the configuration that is shown in Listing 9 is very similar to the service configuration that was used in the earlier example. While the address and the namespace have been updated, the real configuration change is the use of a different binding type for the bindingConfiguration parameter.

<service name="QuickReturnSecureTraderChat.Main">
    <host>
        <baseAddresses>
            <add baseAddress="net.p2p://QuickReturnSecureTraderChat"/>
        </baseAddresses>
    </host>
    <endpoint 
        name="QuickTraderChatSecurePasswordEndPoint"
        address=""
        binding="netPeerTcpBinding"
        bindingConfiguration="BindingSecurePassword"
        contract="QuickReturnSecureTraderChat.IQuickReturnTraderChat"
      />
</service>

Listing 9. Service configuration

Binding Configuration

The updated binding configuration that is used by both the host and the client in our example is called BindingSecurePassword. The main difference between this and the previous example is the addition of the security details, as shown in Listing 10. As you can see, we have the security mode set to Transport and the type set to Password.

<binding name="BindingSecurePassword">
    <security mode="Transport">
        <transport credentialType="Password"/>
    </security>
    <resolver mode="Pnrp"/>
</binding>

Listing 10. Secure binding configuration

Main Application

The main application is the same as shown in Figure 1. The only difference between this and the earlier example is the addition of a new member variable to hold the password, which is read from the app.config file.

Note: We recommend that you do not save the password in the app.config file in clear text, because anyone can open it and read the password. We recommend that you save the password in an encrypted storage, or accept the password from the user at run time. To hold the password in memory, use the SecureString class that was introduced in .NET Framework 2.0.

The updated member variable that is used by the solution is shown in Listing 11. The channel is of type IQuickReturnTraderChat; this is the contract that was implemented by the service. The members that are named host and channelFactory are the service host and the channel factory, respectively. The two string variables respectively store the user and password, which are read from the app.config file by using ConfigurationManager.AppSettings in the constructor for our Main class.

IQuickReturnTraderChat channel;
ServiceHost host = null;
ChannelFactory<IQuickReturnTraderChat> channelFactory = null;
string userID = "";
string password = null;

Listing 11. Member-variable list

The StartService method in the Main class has been updated slightly, as shown in Listing 12. This now uses a different endpoint configuration file and sets the password for both the host and the channel. The StopService method remains the same as earlier, and it is not listed again here. As shown in Listing 12, the password for both the host and the ChannelFactory is set via the Credentials.Peer.MeshPassword property. The binding configuration has been updated and is read from QuickTraderChatSecurePasswordEndPoint.

private void StartService()
{
    //Instantiate new ServiceHost
    host = new ServiceHost(this);
    //Set the password
    host.Credentials.Peer.MeshPassword = password;
    //Open ServiceHost
    host.Open();
    //Create a ChannelFactory and load the configuration setting
    channelFactory = new ChannelFactory<IQuickReturnTraderChat>
                         ("QuickTraderChatSecurePasswordEndPoint");
    //Set the password for the ChannelFactory
    channelFactory.Credentials.Peer.MeshPassword = password;
    //Create the Channel
    channel = channelFactory.CreateChannel();
    //Lets others know that someone new has joined
    channel.Say("Admin", "*** New User " + userID +
                                " Joined ****" + Environment.NewLine);
}

Listing 12. Service-host implementation

One interesting behavior with the security is that if you have a set of peers listening on the same endpoint, but with different passwords, they will be isolated from each other. For example, say that you have four users—User1, User2, User3, and User4. User1 and User2 are chatting and connected to the mesh by using “password1”. If User3 and User4 start chatting with another password—say, “password2”—even though all four users are on the mesh and listening on the same endpoint, the messages between User1 and User2 cannot be seen by Users3 and User4, and vice versa.

To use an X509 certificate instead of a password to secure a mesh, set the transport credentialType in the binding to Certificate, and set the Credentials.Peer.Certificate property to Certificate on both the host and the client.

A peer, as we know, is the most basic building block of a P2P network and the final endpoint of the communication. A peer can be an application, computer, Windows service, and so on. A peer is defined by using the PeerName class, and, as we know, it can be either secure or unsecure.

Creating

Creating a new peer is quite straightforward by using the PeerName class. Listing 13 shows an example of how to define both an unsecure peer and a secure peer. The PeerName class has three overloaded constructors; however, to create a new instance of a fully qualified peer, the third overload that accepts two parameters is the best option. The first parameter is the classifier, and the second parameter is the secure or unsecure peer.

PeerName myPeer = new PeerName("MyUnsecurePeer",PeerNameType. Unsecured);
PeerName mySecurePeer = new PeerName("MySecurePeer",
                                PeerNameType.Secured);

Listing 13. Peer definition

A classifier is a string that refers to the peer name and does not guarantee uniqueness; that is, you can have more than one peer in the cloud with the same classifier. A peer name is case-sensitive. A secure peer automatically creates a 40-character-long hex string as the authority, which makes it difficult to spoof the peer. The authority for an unsecure peer is always set to 0 (zero). The authority for a peer can be retrieved via the Authority property.

Publishing

Creating a peer by itself is quite meaningless, so that the next logical step is to register (or publish) a peer and associate it with a cloud. A peer must be part of a cloud that will then enable it to communicate with other peers within the cloud. A cloud is no more than a collection of peers and is uniquely identified with a name. Clouds are closely tied to a network interface. In addition to the Global cloud, of which there is only one, each network card on a computer would have a cloud attached to it. For example, if you have a laptop that has a wired and a wireless network connection, there will be three clouds available: Global, and one for each of the network connections.

Registering a peer is done by using the PeerNameRegisteration class, which has a few overloaded constructors. The three basic steps are as follows:

  1. Peer that you are trying to register
  2. TCP port over which the peer will listen
  3. Endpoint that is affiliated with the peer

You can use the EndPointCollection property of the PeerNameRegisteration class to add a new endpoint, which expects an IPEndPoint object. However, in most cases, we don’t want to add a new endpoint explicitly; instead, we want to use the address that is assigned to the local computer that hosts the peer. To do so, we use the UseAutoEndPointSelection property; setting it to True will automatically pick up the endpoints when registering the peers. Listing 14 shows how to register the two peers that we had defined earlier.

PeerNameRegistration registeration = new                             PeerNameRegistration(myPeer,3030);
registeration.UseAutoEndPointSelection = true;            
registeration.Start();

Listing 14. Peer registration

The Start method registers the peer on the cloud. If a specific cloud is not specified via the Cloud property, the peer is registered on all of the clouds that are available to the client. By default, the port for PeerNameRegisteration is 0 (zero) and the UseAutoEndPointSelection property is set to True. Thus, the code in Listing 14 and Listing 15 are equivalent.

PeerNameRegistration registeration = new PeerNameRegistration();
registeration.PeerName = myPeer;
registeration.Port = 3030;
registeration.Start();

Listing 15. Peer registration

It is also possible to associate one peer with multiple PeerNameRegisteration objects. If the peer is not secure, it can be registered in the same cloud multiple times, as long as each registration is from a different process on the operating system. On the other hand, if a peer is secure, every instance of the peer is registered in a different cloud. A peer that is registered in a cloud is identified via a PeerRecord object that also encapsulates all of the relevant details.

Resolving

The final logical step after creating and publishing a peer is resolving a peer. What good is it to publish something to the cloud if another peer cannot find you? We use the PeerNameResolver class to resolve for a specific peer in a given cloud. The PeerNameResolver can resolve a peer to either a PeerRecord or a cloud, depending on the parameters that are passed. The resolution process finishes either when the maximum number of record entries for the PeerRecordCollection is reached or when it has reached the end of various clouds.

The PeerNameResolver class exposes an overloaded method that is called Resolve and is used to resolve a given peer synchronously. The scope of what will be resolved depends on the parameters that are passed. Also, because we cannot resolve a peer from the same process that published it, we have a simple console application that registers a secure peer and an unsecure peer on a cloud, as shown in Listing 16.

static void Main(string[] args)
{
    PeerName myPeer = new PeerName("MyUnsecurePeer",            PeerNameType.Unsecured);
    PeerName mySecurePeer = new PeerName("MySecurePeer",            PeerNameType.Secured);
    PeerNameRegistration registeration = new            PeerNameRegistration(myPeer, 3030);
    PeerNameRegistration registeration2 = new            PeerNameRegistration(mySecurePeer, 3030);
    registeration.Start();
    registeration2.Start();

    Console.WriteLine("Registeration of Peer {0} comeplete with the        hostname {1}.", myPeer.ToString(), myPeer.PeerHostName);
    Console.WriteLine("Registeration of Peer {0} comeplete with the        hostname {1}.", mySecurePeer.ToString(), myPeer.PeerHostName);

    Console.WriteLine("Press any key to continue...");
    Console.ReadKey();

    registeration.Stop();
    registeration2.Stop();
}

Listing 16. Secure and unsecure peers registered

Listing 17 shows us how to try to resolve for a peer that is called MySecurePeer. The Resolve method returns a collection of type PeerNameRecordCollection through which we iterate. Listing 18 shows the result of this when running on a computer that has three network cards.

PeerName myPeer = new PeerName("MySecurePeer", PeerNameType.Secured);
PeerNameResolver resolver = new PeerNameResolver();
            
PeerNameRecordCollection results = resolver.Resolve(myPeer);

Console.WriteLine("{0} Peers Found:", results.Count.ToString());
int i = 1;

foreach (PeerNameRecord peer in results)
{
    Console.WriteLine("{0} Peer:{1}", i++, peer.PeerName.ToString());
    foreach (IPEndPoint ip in peer.EndPointCollection)
    {
        Console.WriteLine("\t Endpoint: {0}", ip.ToString());
    }
}

Listing 17. Resolving a peer on a cloud

Because the peer that we are resolving is secure, we see a random 40-character hex string as the authority in the peer. The peer was originally published on all available network connections (we did not limit the scope and use the default). The computer on which this code was executed had three network cards; hence, we see four result sets—one for each network adapter and the Global cloud. On the network adapters that have both IPv4 and IPv6, you see two endpoints.

4 Peers Found:
1 Peer:449dc64ecb28347e9f7ce600dba063662f2eb0e7.MySecurePeer
       Endpoint: fe80::8861:85c7:d256:ae5a%15:3030
       Endpoint: 192.168.100.1:3030
2 Peer:449dc64ecb28347e9f7ce600dba063662f2eb0e7.MySecurePeer
       Endpoint: 5.17.198.73:3030
3 Peer:449dc64ecb28347e9f7ce600dba063662f2eb0e7.MySecurePeer
       Endpoint: fe80::9518:2d5d:6699:61b6%9:3030
       Endpoint: 169.254.97.182:3030
4 Peer:449dc64ecb28347e9f7ce600dba063662f2eb0e7.MySecurePeer
       Endpoint: fe80::56d:e1b3:9:212d%13:3030
       Endpoint: 192.168.62.1:3030

Listing 18. Peer-resolution result

Clouds

Clouds are tightly integrated with the network interfaces on a computer. If there are n network interfaces, we will get n+1 clouds—one LinkLocal for every network interface, and one Global cloud. The System.Net.PeerToPeer namespace contains a static class that is called Cloud and can be used to interact with the cloud of a specific peer. This class has two static members—GetAvailableClouds and GetCloudByName—that return a CloudCollection that contains all of the known clouds or the specific cloud, respectively. The Cloud class has a couple of interesting fields—namely, AllLinkLocal and Available.

  • AllLinkLocal—Returns a reference to a single cloud that represents all of the link-local clouds in which the peer is currently participating
  • Available—Returns a reference to a single cloud that represents all of the clouds in which the peer is currently participating

Consider Listing 19, which lists all of the clouds and writes the result to the console. The result of this is shown in Listing 20.

CloudCollection clouds = Cloud.GetAvailableClouds();
Console.WriteLine("{0} clouds found.",clouds.Count);
int i = 1;
foreach (Cloud someCloud in clouds)
{
    Console.WriteLine("{0}: Name:{1}", i++, someCloud.Name);
    Console.WriteLine("\tScope ID:{0}, Scope:{1}",                     someCloud.ScopeId,someCloud.Scope);
}

Listing 19. List all available clouds

5 clouds found.
1: Name:Global_
      Scope ID:0, Scope:Global
2: Name:LinkLocal_ff00::%15/8
      Scope ID:15, Scope:LinkLocal
3: Name:LinkLocal_ff00::%13/8
      Scope ID:13, Scope:LinkLocal
4: Name:LinkLocal_ff00::%8/8
      Scope ID:8, Scope:LinkLocal
5: Name:LinkLocal_ff00::%9/8
      Scope ID:9, Scope:LinkLocal

Listing 20. List of available clouds

People Near Me (PNM) is a new feature of Windows Vista, and provides a standard set of APIs that allow one to write applications that can interact and collaborate with other users on the local subnet. PNM provides four key services—Discover, Interact, Collaborate, Publish—that enable a user to share objects and applications, subscribe to events, and send invites for others to join. This section covers in detail only the Discover and Interact services of PNM.

Discover

Internally, PNM uses Web discovery to publish various details, such as name, computer name, IP address, port, and so on. A user is not signed in automatically; instead, a user signs in via the Control Panel. After having done so, a user can choose the option to sign in automatically whenever Windows starts.

Figure 2 and Figure 3 show the PNM icon when a user is signed in and signed out, respectively.

Figure 2. PNM, signed in

Figure 3. PNM, signed out

As shown in Figure 4, the Settings tab of PNM allows a user to sign in and out of PNR and change settings, such as the friendly name that others will see, which profile of users one would want to accept invitations from, and so on.

Figure 4. People Near Me options

A significant portion of the PNM functionality is provided by a static class that is called PeerCollaboration and can be found in the System.Net.PeerToPeer.Collaboration namespace. This class allows you to interact with the collaboration infrastructure, which enables one to sign in, sign out, register and unregister applications, and so on. PeerCollaboration has a static method that is called SignIn and with which one can sign in to the collaboration infrastructure, as shown in Listing 21. Note that you must have IPv6 installed for this to work; otherwise, you will get a PeerToPeerException that shows the message, “The sign-in succeeded, but there are no IPv6 addresses available at this time.”

Each PNM is represented by the PeerNearMe class, which exposes all of the details for a peer, such as the endpoints at which they can be reached, nickname, whether they are online, and so on. After you have signed in, you can try to find any peers that are within the PNM infrastructure via the GetPeersNearMe method. This returns a collection of PeerNearMe that represents the peers who are signed-in in the same local subnet.

PeerCollaboration.SignIn(PeerScope.All);
PeerNearMeCollection peers = PeerCollaboration.GetPeersNearMe();
Console.WriteLine("{0} peers found.", peers.Count);

Listing 21. Sign in to collaboration

Contacts

The other option for discovering people is to search in the Windows Address Book (WAB). The entries that are saved in the WAB are called as Contacts and enable interaction beyond the local subnet. The WAB is a new feature of Windows Vista. You can find the WAB saved as “Contacts”; it is usually stored in C:\Users\<user-name>\Contacts.

The WAB features are exposed via a class that is called ContactManager and can be retrieved via the PeerCollaboration class. The ContactManager class exposes a method that is called GetContacts and returns a collection of PeerContacts that are available in the signed-in scope, as shown in the following snippet:

ContactManager contactMgr = PeerCollaboration.ContactManager;
PeerContactCollection contacts = contactMgr.GetContacts();

You can add a found peer to your address book by using the AddToContactManager() method on the PeerContact. If that peer already exists as a contact, you will get a PeerToPeerException that shows the message, “Peer collaboration add contact failed. Contact already exists in contact manager.”

Listing 22 shows the code and the output when there is one more peer called “Amit2” on the subnet. Figure 5 shows what the contact looks like when it is added to the WAB.

PeerNearMeCollection peers = PeerCollaboration.GetPeersNearMe();
Console.WriteLine("{0} peers found.", peers.Count);
foreach (PeerNearMe peer in peers)
{
    PeerContact contact = peer.AddToContactManager();              
    Console.WriteLine("Peer {0} is online:{1} with {2} endpoints",        peer.Nickname, peer.IsOnline, peer.PeerEndPoints.Count);
}

1 peers found.
1: Peer Amit2 is online:True with 1 endpoints

Listing 22. Peers found on the subnet

Figure 5. Contact added to Windows Address Book

Interact

To interact with another peer by using some application (typically, a line-of-business (LOB) application), the same application must be installed on both of the computers—in other words, on yours and on the peer with whom you are planning to interact. Furthermore, this application must be installed on both the computers with the same GUID and must be registered with the peer-collaboration infrastructure as one of the applications that is available for interaction. To find the endpoint at which an application can be reached, you can use the EndPoint property of the PeerNearMe class. This property returns an IPEndPoint object.

The invitation that a user sends to a peer to launch a certain application contains an XML payload and is sent over a secure connection. The XML payload consists of the following:

  • A unique GUID that identifies the application
  • A user-defined message that is displayed to the peer who receives the invitation
  • Additional data that essentially is a placeholder in which to put any blob of data that the target application might expect (for example, the endpoint details of the peer who sends the invitation)

Figure 6 shows an example of what the invitation looks like on the target computer when one is inviting to launch Windows Meeting Space. This request is from an unknown peer. (If this were from a known contact—that is, part of WAB—the request would look like what is shown in Figure 8.)

Figure 6. Unknown-peer invitation

Clicking the View button shows the details of the invitation that is shown in Figure 7. If you accept the invitation, the application will launch. At this point, the peer infrastructure is no longer part of the process; instead, the applications that are on both ends are communicating by using regular communication protocols, such as TCP, HTTP, and so on.

Figure 7. Unknown Windows Meeting Space invitation details

The GetLocalRegisteredApplications method on the PeerCollaboration class returns a collection of type PeerApplications that represents all of the applications that are registered on the local computer. By default, this method returns the applications that are registered for all users; however, if you want to restrict that to the logged-on user, you can use the overloaded method that accepts a PeerApplicationRegisterationType.

Listing 23 shows a sample listing of the applications. It is this list of applications that is available for a peer to use.

PeerApplicationCollection apps = PeerCollaboration.GetLocalRegisteredApplications();
int i = 1;
Console.WriteLine("{0} Applications Found:", apps.Count);
foreach (PeerApplication someApp in apps)
{
    Console.WriteLine("\t{0}: ID:{1}", i, someApp.Id);
    Console.WriteLine("\t\tDesc:{0}", someApp.Description);
    Console.WriteLine("\t\tPath:{0}", someApp.Path);
    Console.WriteLine("\t\tArguments:{0}", someApp.CommandLineArgs);
}

Listing 23. Iterating through all available peer applications

You use the Invite() method on the PeerNearMe class to invite a selected peer to launch the application. This method takes three parameters: the application that is to be launched, a message for the user, and some data (as shown in Listing 24). The data parameter is specific for each application; based on its implementation, only the application knows what to expect. In many cases, it would contain your endpoint address, so that the application knows the address to which to “talk back.” The Invite() method returns a PeerInvitationReponse and can be one of the following options:

  • Declined
  • Accepted
  • Expired
PeerInvitationResponse response = peer.Invite(myApp, "Want to work on this P2P article together?", null);
if (response.PeerInvitationResponseType != PeerInvitationResponseType.Accepted)
    MessageBox.Show(peer.Nickname + " declined the request.");

Listing 24. Inviting a peer to launch an application

Figure 8 shows an invitation from a trusted contact to launch Windows Meeting Space. Clicking on the View button shows you the details of the invitation that is shown in Figure 9.

Figure 8. Known-peer invitation

Figure 9. Known Windows Meeting Space invitation details

Creating Your PeerApplication

A PeerApplication can be any application that can run on Windows. The only difference between a regular application and a peer application is that the latter is registered as available on the P2P infrastructure. Typically, an application is registered during the installation process.

Registering a new application manually or programmatically is fairly straightforward. The first step is to create a new instance of the PeerApplication class, and the second step is to register that instance on the P2P infrastructure. We need a unique ID (represented by a GUID) to identify this application. This ID must be the same on all of the computers on which this application will be installed and is used to identify the application that is to be launched on the target computer.

In addition to the path of the executable and a human-readable description, we also must assign a scope to the application. Typically, this scope is dictated by the design of the application and what it is supposed to be doing.

Listing 25 shows an example of creating a new PeerApplication that configures Microsoft Notepad to be available as part of the P2P infrastructure.

PeerApplication myApplication = new PeerApplication();
myApplication.Description = "My Test Application";
myApplication.Id = new Guid("B0C29122-398A-4416-8AA7-0A8A9AE82B23");
myApplication.Path = @"c:\windows\notepad.exe";
myApplication.PeerScope = PeerScope.All;

Listing 25. Creating a new PeerApplication

The third step is to register the application on the P2P infrastructure by using the RegisterApplication method on the PeerCollaboration class. This accepts two parameters: the application that you want to register, and the scope of the users for which the application is available (that is, the currently logged-on user or All Users). A PeerToPeerException is thrown if the registration fails.

PeerCollaboration.RegisterApplication(myApplication, PeerApplicationRegistrationType.CurrentUser);

The same application can be registered many times, as long as a unique ID is associated with each registration. For example, Listing 26 shows a sample output of all of the locally registered peer applications. Note that we have Notepad registered twice, with two different IDs.

3 Applications Found:
      1: ID:81facd1e-c761-4330-b56c-a7286a3424aa
            Desc:Amit Test Application
            Path:c:\windows\notepad.exe
            Arguments:
      1: ID:b0c29122-398a-4416-8aa7-0a8a9ae82b23
            Desc:Amit Test Application
            Path:c:\windows\notepad.exe
            Arguments:
      1: ID:153999c2-cec7-4233-8599-819dc4840a0c
            Desc:Windows Meeting Space
            Path:C:\Program Files\Windows Collaboration\WinCollab.exe
            Arguments:/e

Listing 26. Sample output of locally registered applications

If you are trying to register an application that will be available for All Users, the process that is executing this requires administrator privileges, because this writes to the registry. If you don’t run this with elevated privileges, the registration will fail and throw a PeerToPeerException that shows the message, “Peer collaboration register application failed.” However, if you examine the inner exception, you see the more meaningful message of “Access Denied.” Of course, if you have User Account Control (UAC) switched off, you already are running with administrator privileges, and the registration will succeed.

PeerCollaboration.RegisterApplication(myApplication, PeerApplicationRegistrationType.AllUsers);

As mentioned earlier, an application that is designed to be invoked over a peer infrastructure (such as Windows Meeting Space) typically would be registered during the installation process of that application. If you want to register this manually or if you are creating the setup (for example, by using the Setup and Deployment project in Microsoft Visual Studio or WiX), you must create an entry in the following Windows registry hives, depending on the scope. If the application is available for All Users, you must register it in the HKLM hive. If the application is available only for the current user, you must use the HKCU hive.

  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\PeerNet\AppInvite\MyApplications
  • HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\PeerNet\AppInvite\MyApplications

Each application is a new key that consists of the application’s ID and the structure, as shown in the following snippet and in Figure 10:

"CommandLineArgs"=""
"PublicationScope"=dword:00000002
"Filename"="C:\\Windows\\notepad.exe"
"Description"="Some App"

Figure 10. Registry details of an application registered on the peer infrastructure

If you accidentally use the same ID for two different applications, each of which is in a different scope (that is, Local User and All Users), the application that is found in Local User takes precedence. For example, if the 81facd1e-c761-4330-b56c-a7286a3424aa key is used to register both Notepad and UltraEdit in the All Users and Current User scopes, respectively, when iterating through the available list of registered applications, you will find only the reference to UltraEdit, because that takes precedence over the reference to Notepad.

If you tried to register an application by using an existing ID in the same scope, you will get an exception that says that the application was already registered. Also, if the application on the target system was not found (for example, there might be a typing error in the path of the executable), you will get an “Unable to start the program” error message, as shown in Figure 11.

Figure 11. Peer application not found

If your peer application needs some metadata at startup—such as how is it launched, or to read any initial data such as any LOB-specific details or endpoint details, and so on—you must use the ApplicationLaunchInfo method on the PeerCollaboration class to get those details. One of the parameters on the Invite() method (shown in Listing 24) accepts this metadata and passes it through. Listing 27 shows an example of this.

PeerApplicationLaunchInfo launchInfo = PeerCollaboration.ApplicationLaunchInfo;
if (launchInfo != null)
{
    StringBuilder s = new StringBuilder();
    s.Append("Message:" + launchInfo.Message + Environment.NewLine);
    s.Append("PeerApplication:" + launchInfo.PeerApplication.ToString()                         + Environment.NewLine);
    s.Append("Peer Contact:" + launchInfo.PeerContact.ToString() +                         Environment.NewLine);
    s.Append(launchInfo.ToString());
        MessageBox.Show(s.ToString());
}

Listing 27. Finding out application-launch details

If you want to “respect” the context of a peer and get its presence information, based on which you want to make some business decision (such as to initiate a session with another peer, if it is busy), use the LocalPresenceInfo property that is exposed by the PeerCollaboration class. This returns an object of type PeerPresenceInfo that exposes the PeerPresenceStatus enumeration.

NetShell, also known as netsh, is an indispensable command-line utility for anyone who works with P2P. Although netsh is primarily aimed at administrators, allowing them to administer network services, it is equally useful to a developer. To start netsh, open a command prompt, and then type netsh.

Note: NetShell is available only when the P2P networking option is installed in Windows XP. By default, NetShell is installed on Windows Vista; however, for it to work, you must allow it as an exception in the firewall.

The commands in netsh work with the concept of a “context” that determines the networking aspect within which you want to operate and accumulates various possible commands in that context. In most situations, you would switch to some context for the specific operation in which you are interested. Contexts can have subcontexts, and these, in turn, can have further subcontexts—forming a tree-like hierarchy. Figure 12 shows how we switch the context to P2P → PNRP → Cloud.

Figure 12. netsh context

The commands that you enter in netsh factor into the context on which you are working. For example, the show command that is entered (see Figure 12) knows that the context is cloud within a PNRP network and shows the commands for that context. You can switch from one context to another at any time.

Listing Clouds

If you want to see the clouds to which you are currently connected, you will use the show list command, as shown in Figure 13. In this example, you can see two clouds, where one of those clouds is synchronizing. Later, you will see when the cloud has finished synchronizing.

Figure 13. Listing of clouds

To see the configuration and status of the cloud, you use the show initialization command (or show init, in its short form). If a computer is connected to the Internet, it is part of the global cloud that is called Global_. If a cloud is connected to one or two LANs, individual clouds are available for each network adapter (or link).

As its name suggests, scope (see Figure 13) represents the scope of the cloud and essentially shows the PNRPCLOUDINFO data structure that is part of the P2P SDK. The following list defines each of the columns that are defined in PNRPCLOUDINFO:

  • Scope—The scope of the cloud. It can be one of four values, from 0 to 3. Each of these values is described in the next section.
  • Id—The unique identifier for that cloud.
  • State—The state of the cloud. It is represented by the PNRP_CLOUD_STATE structure in the SDK. This can be one of eight values and is described in the next section.

Cloud Scopes

The scope of a cloud can be one of the four values that are shown in the following list:

  • 0—The cloud can be in any scope (represented by PNRP_SCOPE_ANY).
  • 1—The cloud is a global scope (represented by PNRP _GLOBAL_ SCOPE).
  • 2—The cloud is a site-local scope (represented by PNRP_SITE_LOCAL_SCOPE).
  • 3—The cloud is a link-local scope (represented by PNRP_LINK_LOCAL_SCOPE).

The state of a cloud can be one of the eight values that are shown in the following list:

  • Virtual—The cloud is not yet initialized.
  • Synchronizing—The cloud is in the process of being initialized, but is not active yet.
  • Active—The cloud is active.
  • Dead—The cloud has lost its connection to the network, but was initialized.
  • Active—The cloud is active.
  • Disabled—The cloud is disabled in the registry.
  • No Net—The cloud has lost its connection to the network, but was active.
  • Alone—The cloud is in stand-alone mode.

Listing Peers in a Cloud

To see the locally registered nodes in a cloud, use the show names command in netsh. In the following example, we see that we have two peers that are identified as P2P Name and are connected to the cloud. Note that the exact list of peers that you see will of course be different from the ones that are shown (if the QuickReturnTraderChat application has been running from earlier, you should see that).

P2P Name:       0.quickreturntraderchat
Identity:       2460f44f457b670116f55709f3e6324dd12ad70e.PnrpProtocolV2
Comment:        a?????????
PNRP ID:        cf284a913c76d8289f16c4fefbe18b7a.5bcca4c6a1090f379d15b0f12fc89b08
State:          OK
IP Addresses:   192.168.1.73:11989 tcp
                [2001:0000:4136:e37a:2847:1735:a83d:dc55]:11989 tcp
P2P Name:       0.78873591048
Identity:       2460f44f457b670116f55709f3e6324dd12ad70e.PnrpProtocolV2
Comment:        Local Machine Id
PNRP ID:        ad1d55aa343d35df9d118343e3c3de09.7700660055004400f956ced74b6beb3cState:          OK

Listing 28. Peer listing

  • P2P Name—Name of the peer that is connected to the cloud. In our preceding example, the first peer, 0.quickreturntraderchat, is the QuickReturnTraderChat application that was discussed earlier.
  • Identity—Represents the identities, as the name suggests. Note that the identity of both of the peers is the same. This is so, because these peers are unsecure, and the default identity is used for them.
  • PNRP ID—Represents the corresponding 256-bit PNRP ID.
  • IP Addresses—Represents the endpoints (including the ports) that are associated with this peer.

Cloud Statistics

To see the cloud statistics, enter the show statistics command (the abbreviated command show stat will work, too) in netsh. This will display the statistics for all of the active clouds. For example, Listing 29 lists statistics for the global cloud. Although most of the entries are self-explanatory, the IP Addresses column is a list of the addresses that is used to connect to the cloud.

IP Addresses: [2001:0000:4136:e37a:2847:1735:a83d:dc55]:3540
Number of cache entries: 34
Estimated cloud size: 142
Number of registered names: 3
Throttled resolves: 0
Throttled solicits: 0
Throttled floods: 0
Throttled repairs: 0

Listing 29. Cloud statistics

There are more commands within the cloud context; to give you a basic understanding, we discussed only the more important ones. We encourage you to use the documentation and the SDK to explore other commands in netsh.

Working with Peers

To switch to the peer context from within PNRP, just type peer in netsh. The peer’s context, as the name suggests, allows you to work with peers and gives you the ability to add, delete, and enumerate entries, among other things. We will not be covering all of the commands—just a couple of the more interesting ones. As you know, before one peer can talk to another peer, it must resolve that peer. To do this with netsh, you use the resolve command—passing it the peer name. In this example, if you try to resolve the peer that is called 0.quickreturntraderchat, you get the result from Listing 30.

netsh p2p pnrp peer>resolve 0.quickreturntraderchat
Resolve started...
Found:
Comment: aD????????
Addresses: [fe80:0000:0000:0000:79ae:4fe7:e034:eac7]%8:28365
Extended payload (binary):
Comment: aD????????
Addresses: [fe80:0000:0000:0000:79ae:4fe7:e034:eac7]%8:28136
Extended payload (binary):
Comment: aD????????
Addresses: 169.254.2.2:28365
192.168.1.73:28365
[2001:0000:4136:e37a:2847:1735:a83d:dc55]%0:28365
Extended payload (binary):

Listing 30. Peer resolution

As you can see in the preceding example, two instances of the QuickReturnTraderChat application are running. Also, there are two network cards—one of which is connected to the Internet, and one of which is on an internal network. The first network adapter (which is connected to the Internet connection) has the IP address of 192.168.1.73 (after NAT, of course), and the local one has the IP address of 169.254.2.2—both of which are listening on port 28365.

The other command of interest is the traceroute command, which resolves a peer with path tracing. If the name is registered, the result will be quite similar to the resolve command that was used earlier, as shown in Listing 31.

netsh p2p pnrp peer>traceroute 0.quickreturntraderchat Global_
Resolve started...
Found:
        Addresses:  169.254.2.2:28365 tcp
                    192.168.1.73:28365 tcp
                    [2001:0000:4136:e37a:2847:1735:a83d:dc55]%0:28365
tcp
        Extended payload (string):
        Extended payload (binary):
Resolve Path:
[2001:0000:4136:e37a:2847:1735:a83d:dc55]:3540, (0), (0)
       Accepted
[2001:0000:4136:e37a:2847:1735:a83d:dc55]:3540, (0), (0)
       Accepted Final Inquire

Listing 31. Known-peer trace route

If, on the other hand, the peer is not registered, we will see a more interesting behavior, as shown in Listing 32. Note that an invalid name (0.quickreturntraderchatwedontkow) is provided to mimic this behavior. Also, Listing 32 has been abbreviated for clarity. The exact number of hops would vary, depending on the size of your cloud.

netsh p2p pnrp peer>traceroute 0.quickreturntraderchatwedontkow Global_
Resolve started...
Not Found.
Resolve Path:
[2001:0000:4136:e37a:2847:1735:a83d:dc55]:3540, (0), (0)
       Accepted
[2001:0000:4136:e37e:140b:26c5:affa:3034]:3540, (8), (31)
       Rejected (Dead end)
[2001:0000:4136:e37e:244b:1e65:abdb:f294]:3540, (7), (140)
       Rejected (Dead end)
[2001:0000:4136:e37e:1c75:1b9a:bef2:f5a3]:3540, (4), (312)
       Accepted Suspicious
[2001:0000:4136:e37e:0c31:07f8:5351:cf06]:3540, (4), (2000)
       Rejected (Unreachable)
[2001:0000:4136:e37e:1c75:1b9a:bef2:f5a3]:3540, (4), (125)
       Rejected (Dead end)
[2001:0000:4136:e37a:384f:1905:bde1:91be]:3540, (4), (297)
       Rejected (Dead end)
[2001:0000:4136:e378:1cb4:2170:a795:ebd9]:3540, (3), (78)
       Rejected (Dead end)
 [2001:0000:4136:e37a:0c25:34ef:e7ef:9e09]:3540, (2), (203)
       Accepted

Listing 32. Unknown-peer trace route

Windows XP and Windows Vista

If you cannot get a computer that is running Windows XP to talk to another computer, check to see if the PNRP is installed. Also, if the target computer is running Windows Vista, you might have to upgrade to PNRP version 2.0. Windows XP ships with PNRP version 1.0, which is not compatible with PNRP version 2.0 (the latter version ships with Windows Vista). To enable PNRP on Windows XP, perform the following steps:

  1. Go to Add/Remove Programs.
  2. Select Add/Remove Windows Components.
  3. Select Networking Details, and then click Details.
  4. Select Peer to Peer, and then click OK.

As soon as you have enabled PNRP, you must upgrade to version 2.0 by downloading the update from http://support.microsoft.com/default.aspx/kb/920342. However, note that you must have Windows XP SP2 applied to install this update. To test whether PNRP is installed and working correctly, you can list the clouds to which you are connected by opening a command prompt and running the following netsh command:

netsh p2p pnrp cloud show list

IPv6 Issues

If you are running Windows Vista (or Windows XP with PNRP 2.0 installed) and you don’t see the Global_ cloud listed, as shown in Figure 14, it probably means that IPv6 is not working on the computer.

Figure 14. Missing Global_ cloud

The following list shows the possible status that the Global_ cloud can have:

  • Virtual—The cloud has not finished initializing.
  • No Net—The network connection has dropped (either the cable is unplugged or you are not in a Wi-Fi range anymore).
  • Active—All is well, and you are connected to other peers.
  • Synchronizing—The cloud is still bootstrapping (this is a quick process; it is quite rare for you to encounter this status).
  • Alone—You are not reachable and cannot publish or resolve other peers. If you know that there are other peers on the network, a firewall might be blocking UDO ports 3540 and 1900. Check your seed server by using the netsh p2p pnrp cloud show seed Global_ command. (The default for Windows Vista is pnrpv2.ipv6.microsoft.com and pnrpv21.ipv6.microsoft.com.)

Registering a Name

If you want to register a peer name, you can use the netsh command, as shown in the following snippet. Figure 15 shows the result when it is run in a command prompt. Note that this registration is valid only for the life of the process (this is the netsh executable, in this case). You can use the netsh command to convert to host-name encoding also, as shown here:

netsh p2p pnrp peer add registration 0.AmitBahree Global_
netsh p2p pnrp peer show convertedname 0.AmitBahree

When you register a name, it is also recorded in the registry and can be found in the: “HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\PeerNet\PNRP\MachineNamePublication\MachineName” key.

Figure 15. Registering a name

Seed Server

If you are having problems, you might want to try to ping the seed by using the p2p pnrp diag ping seed Global_ command. Although Microsoft owns pnrp.net, you can set up your own seed server, run a private PNRP cloud, and configure the peers to bootstrap from the custom clouds. There are two ways to change the seed server:

  • Change the seed server setting in the registry to point to your seed server: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\PeerNet\PNRP\IPV6-Global
  • Override the Windows host file to point pnrpv2.ipv6.microsoft.com and pnrpv21.ipv6.microsoft.com to your seed server.

To show all of the various elements that we discuss in this article, we have three simple applications that work in tandem. Two of these applications are the secure and unsecure implementations of our chat application. The last application is a Peer and Contact Explorer that shows you all of the PNMs on your subnet, as well as the Contacts in your WAB. If you choose to, you can also see more details of each of those entities, such as endpoint address, port, online status, and so on.

As an example, Figure 16 shows that there is one another user who is called “Amit2” and is logged on to the PNM infrastructure via the “AMIT-VPC” computer.

Figure 16. Peer and Contact Explorer

Clicking Tools and then Options gives us a few more options, such as listing the Available Clouds and the peer applications that have been registered on this computer. As shown in Figure 17, you use this dialog box to register a new application for the P2P infrastructure. In our example, we are registering the QuickReturnTraderChat application. If you don’t have a unique ID for this computer, you can generate one. Of course, if you do generate one, you must provide the same ID to other users, because it is this ID only that identifies the application to launch on the target computer.

Figure 17. Options dialog box

Right-clicking on a specific peer gives us the option to invite them to start an application. Figures 18, 19, and 20 show us inviting another peer to launch the QuickReturnTraderChat application, the invitation that they accept, and the application finally being launched, respectively.

Figure 18. Inviting peer to launch an application

Figure 19. Invitation alert

Figure 20. Invitation details

Figure 21 shows us two instances of the QuickReturnTraderChat application running on one computer, and a third instance running in a VM. It also shows us two other instances of the QuickReturnSecureTraderChat sample. Although both the unsecure and secure versions are running on the same computer, they cannot eavesdrop on each other’s messages.

Figure 21. Various application instances running in parallel

This article provides an understanding of the fundamentals of P2P and shows how to use the new features of WCF—allowing us to build P2P applications by using managed code in .NET Framework 3.0. The article also introduces the new features of managing P2P networks, including using People Near Me and Windows Address Book, both of which are new features that are released as part of Windows Vista.

About the authors

Amit Bahree is a Senior Solution Architect with Avanade and has over 14 years of experience in IT—developing and designing mission-critical systems. His background is a mixture of product development, embedded systems, and custom solutions across both public and private sectors. Amit has experience in a wide range of industry verticals—ranging from financial services to utilities to insurance—and has implemented solutions for many Fortune 100 companies. For Amit, computers are a passion first, a hobby second, and a career third, and he is glad that he gets paid to do what he loves the most. He can be contacted via his blog at www.desigeek.com.

Chris Peiris is an avid publisher in the application integration space and works for Avanade Australia as a Solutions Architect. He is a frequent speaker at professional developer conferences on Microsoft technologies. Chris has written many articles, reviews, and columns for various online publications, including MSDN, 15 Seconds, ASPToday, Wrox (Apress), and Developer Exchange (DevX). He has also coauthored many books on WCF, Web services, UDDI, C#, IIS, Java, and Security topics. Chris’s current passions include WCF, .NET Framework 3.0, IBM Message Broker, Microsoft BizTalk, and other EAI implementations. His complete list of publications and contact details are available at http://www.chrispeiris.com.

Avanade is a global IT consultancy dedicated to using the Microsoft platform to help enterprises achieve profitable growth. Through proven solutions that extend Microsoft technologies, Avanade helps enterprises increase revenue, reduce costs, and reinvest in innovation to gain competitive advantage. Our consultants deliver value according to each customer's requirements, time line, and budget by combining insight, innovation, and the talent of our global workforce. Additional information can be found at www.avanade.com.

Show:
© 2015 Microsoft