Automatically setting up NAT traversal

TomP2P allows to automatically set up NAT traversal. If a peer is not reachable by its external address, the TomP2P will first try to set up port forwarding on the router using UPNP and NATPMP. If that fails, TomP2P allows setting up distributed relaying.

Discovery

The discovery procedure makes sure that the peer knows how it is seen by other peers, and whether or not it is reachable from outside the LAN.

In order to optain this information, a peer first sends a ping to any known peer. The other peer then reports back how the peer is seen from outside the LAN. After that, a probe is sent to the new peer, to test wether the new peer is reachable by its external address. If the probe succeeded, the peer can bootstrap and participate in the network.

Port Forwarding and Relaying

If the probe failed, the unreachable peer can try to set up port forwarding with UPNP and NATPMP. If that failed as well, as a last measure the peer will set up relay peers that forward all messages intended for the unreachable peer through an open TCP channel to the unreachable peer.

Example All above steps can be performed in the following way:

Random rnd = new Random();
Peer peer = new PeerMaker(new Number160(rnd)).ports(4000).setBehindFirewall().makeAndListen();
PeerNAT peerNAT = new PeerNAT(peer);
PeerAddress pa = new PeerAddress(Number160.ZERO, InetAddress.getByName(ip), 4000, 4000);
//Check if peer is reachable from the internet
FutureDiscover fd = peer.discover().peerAddress(pa).start();
// Try to set up port forwarding with UPNP and NATPMP if peer is not reachable 
FutureNAT fn = peerNAT.startSetupPortforwarding(fd);
//if port forwarding failed, this will set up relay peers
FutureRelayNAT frn = peerNAT.startRelay(fn);
fd.awaitUninterruptibly();
frn.awaitUninterruptibly();
//now the peer should be reachable

After that, the peer should be reachable from all peers in any case.

Manually setting up relays

For a more fine-grained set up of the relays, the relay peers can be set up as follows. First, The firewalled flags have to be set, so that other peers don’t add the unreachable peer to their peer maps.

// Set the isFirewalledUDP and isFirewalledTCP flags
PeerAddress upa = unreachablePeer.getPeerBean().serverPeerAddress();
upa = upa.changeFirewalledTCP(true).changeFirewalledUDP(true);
unreachablePeer.getPeerBean().serverPeerAddress(upa);

After that, the unreachable peer has to bootstrap in order to get a list of his close neighbors. Since the peer is flagged as unreachable, other peers won’t include this peer in their peer maps.

// find neighbors
FutureBootstrap futureBootstrap = unreachablePeer.bootstrap().setPeerAddress(bootstrapAddress).start();
futureBootstrap.awaitUninterruptibly();

Now the peer is ready to set up the relays. By calling startSetupRelay(), the unreachable peer will call the RelayRPC on its closest neighbors. The RelayRPC will register a message handler for all messages that are intended for the unreachable peer. The TCP channel that was opened for the procedure call is kept open. If the RelayRPC call succeeded, the unreachable peer will add the relay’s socket address to its PeerAddress.

//setup relay
PeerNAT uNat = new PeerNAT(unreachablePeer);
// set up 3 relays
FutureRelay futureRelay = uNat.minRelays(3).startSetupRelay();
rf.awaitUninterruptibly();

By default, the peer tries to set up five relay peers. The relay setup is considered a success if at least three relay peers were set up. From this point on, all messages that are received on any of the relay peer but have the unreachable peer’s peer address as recipient address will be serialized and sent to the unreachable peer through the open TCP channel. The reply message from the unreachable peer is again serialized, and sent back to the relay peer.

The peer is now reachable from the internet, and can now bootstrap again. The isFirewalledTCP and isFirewalledUDP flags are automatically set to false after the relays have been set up. If a relay fails, the peer will automatically try to set up a new relay peer.

// find neighbors again
FutureBootstrap fb = unreachablePeer.bootstrap().setPeerAddress(bootstrapAddress).start();
fb.awaitUninterruptibly();

Since routing has to be fast, routing messages are not relayed to the unreachable peers, but handled by its relay peers on behalf of the unreachable peer. Therefore, all relay peers need an up-to-date version of the unreachable peer’s peer map. By enabling the maintanace, the unreachable peer will periodically bootstrap to optain an up-to-date peer map, and push the peer map to all its relay peers.

uNat.bootstrapBuilder(unreachablePeer.bootstrap().setPeerAddress(bootstrapAddress));
Shutdown shutdown = uNat.startRelayMaintenance(futureRelay);

Relay options

The following options for the relays are available:

UPNP NAT and Port Forwarding detection

Since version 3.2.7, TomP2P supports port forwarding detection and port forwarding settings via UPNP and NAT-PMP. Such a port forwarding is important if peers are behind NAT to be reachable. If a peer is behind a NAT without port forwarding, the peer cannot participate in the P2P network.

Port Forwarding Detection

TomP2P can detect if there is already an existing port forwarding rule in place. First the peer behind the NAT needs to conact a well-known peer (typically the bootstrap peer). This peer reports back how the peer is seen behind the NAT. With this information the peer behind the NAT can guess how the port forwarding rule might look like. It is a guess, since the peer assumes that the internal and external port is the same. In cases where internal and external ports is not the same, there is the option to set the external port manually.

Automatic Port Forwarding Setting

With UPNP and NAT-PMP, TomP2P can set the port forwarding rules on the router automatically. This happens during the port forwarding detection. As soon as TomP2P knows how other peers see a peer, and the peer detects that it is behind a NAT, it tries to set the port forwarding. The configuration in TomP2P for the port forwarding allows to enable or disable it with P2PConfiguration.setBehindFirewall(). Since the default behavior to announced the external IP changed in 3.2.7, a peer needs to discover its external IP address before it can participate (boostrap) in the network:

Peer peer = new PeerMaker(new Number160(rnd)).setPorts(4001).setBindings(b).makeAndListen();
PeerAddress pa = new PeerAddress(Number160.ZERO, {boostrap IP}, 4000, 4000);
peer.getP2PConfiguration().setBehindFirewall(true);
//find out how the other peers see me
FutureDiscover fd = peer.discover().setPeerAddress(pa).start();
fd.awaitUninterruptibly();
if (fd.isSuccess()) {
	System.out.println("found that my outside address is "+ fd.getPeerAddress());
} else {
	System.out.println("failed " + fd.getFailedReason());
}
peer.boostrap().start();

Custom Commands

Routing is always performed for the built-in add/put/get methods in TomP2P. If a user wants to use its own commands together with the routing, the user can either use send(key, ...), which calls send(peer, ...) in the end. With this approach the user has to define what to reply with setRawDataReply() or setObjectDataReply() depending on whether an object is used or a ChannelBuffer. The second approach is to extend StorageMemory, e.g., with MyStorageMemory, and intercept the calls for a specific domain. The following example code shows such a custom behavior.

public class MyStorageMemory extends StorageMemory {
    public Collection<Number160> put(Number160 locationKey, Number160 domainKey, PublicKey publicKey, 
        Map<Number160, Data> contentMap, boolean putIfAbsent, boolean domainProtection)
    {
        if (PEER_VOTE.equals(domainKey)) {
            //here we could get an int from the data and sum it up
        } else {
            super.put(locationKey, domainKey, peerAddress, publicKey, contentMap, putIfAbsent, domainProtection);
        }
    }
...
}

The second approach is recommended. For setting up the storage memory it is important to do this after the listen(...) method call.

Replication

In TomP2P, there are indirect and direct replication mechanisms available. The direct replication can be described as peers constantly publishing their content. This means that a single peer is responsible for its content and periodically republishes the content. If this peer stops doing so, the content will eventually time out and gets removed. The information about responsibilities need to be stored along with the data in the Storage class. The indirect replication can be described as peers publishing content for others. The peer closest to a location ID is considered as the responsible peer and replicates data if necessary.

Direct Replication

The direct replication can be turned on by setting DHTBuilder.setRefreshSeconds(int refreshSeconds) to a value greater than 0. This tells TomP2P to refresh the entry every refreshSeconds seconds. The information what content has to be refreshed is stored in the Storage class, which can be either memory or disk-based. The add and put methods are similar with respect to the direct replication except that add calculates a hash of the data on the sender, while put does not. The time-to-live (TTL) in combination with storage tells all other peers (except the sender) to invalidate the entry after this time, if no refresh has been performed. The sender never expires “its” content unless the sender calls remove.

The TTL and refreshSeconds for the remove operation has a different meaning. The refresh tells the sender to remove the entry every refreshSeconds until TTL has been reached. These values are used on the sender only.

The tracker does only direct replication and never active replication. It has a fixed timing, which can be set with TrackerStorageMemory.setTrackerTimoutMillis(TTL). for the TrackerStorage and its TTL * 0.75 for the refresh interval.

The following example executes 5 times put

FutureCreate<FutureDHT> futureCreate1 = new FutureCreate<FutureDHT>() {
    @Override
    public void repeated(final FutureDHT future) {
        System.out.println("put again...");
    }
};
FutureDHT futureDHT = peers[1].put(Number160.ONE).setData(new Data("test")).
    setFutureCreate(futureCreate1).setRefreshSeconds(2).setDirectReplication().start();
Utils.sleep(9*1000);
//outputs five times chain...

Indirect Replication

Since version 3.2, TomP2P supports indirect replication. This feature can be enabled for put/get storage with DHTBuilder.setDefaultStorageReplication() and for tracker storage TrackerBuilder.setDefaultTrackerReplication(). Indirect replication is triggered by two events:

Security in TomP2P

Security features are typically needed in uncontrolled environments, e.g., the Internet. The built-in security features of TomP2P (>= 3.1.10) are signature-based. Encryption-based security is not built-in, but a user can encrypt its data if necessary. Thus, the following sections explain how to use the signature-based security features. For more information about signing and encryption in P2P, please see Secure routing for structured peer-to-peer overlay networks.

Signatures

Two types of signatures exist in TomP2P: message signatures and data signatures. While the message signature signs the complete message including header (IP and peerID), the data signature only signs the data object.

Data Signatures

Any data objects can be signed. For example the following object Data data=new Data("content"); is signed with data.signAndSetPublicKey(keyPair);. Thus, the data can be signed with a different keys as the message signature. As the name signAndSetPublicKey suggests, the data object is signed and the public key is attached (self-signature). A recipient of this data object can verify if the data object is signed with public key provided and compare the received public key with already stored public keys. A public key is stored on first contact and any subsequent storage, removal, etc. can be verified if its the same peer.

Data signatures are typically used in the Internet with active replication, where peers move around data objects. A message signature would not work in such a situation, since a message signature always includes the IP and the peerID of the sender, and this can change with active replication.

Message Signatures

Message signatures sign the complete message. Messages that can be signed are remove, add, store, move, get, and copy messages. These signatures are used to protect entries, excpet the get message. An example scenario, where such a protection is necessary, is in a tracker, where peers announces its availability of a file. For non-protected entries, anyone could overwrite those entries making an attack easy. Thus, the protection works that the entry can only be modified by the original author. Data and message signatures have been separated since signed data objects that should replace signed messages would require additional mechanisms such as message type indication, or peerID identification, etc.

Domain and Entry Protection Mechanisms

With signed messages, domains and entries can be protected. Protected means, that nobody else can overwrite it.

The following modes and methods exist for domains and entries:

The following modes and methods exist for entries in unprotected domains:

The following modes exist for entries in a protected domain:

If a protected domain has no entries, i.e., they all expired, the domain gets unprotected. The following table describes those modes and methods.

NO_*_MASTER or MASTER_*_PUBLIC_KEYThis mode, in both domain and entry specifies, if a hash of the public key of a signed message can overwrite a currently preset value. NO_MASTER cannot overwrite, while MASTER_PUBLIC_KEY can, even if the domain is protected.
DOMAIN_PROTECTION_ALL or DOMAIN_PROTECTION_NONEThis modes either sets all domains, which can be protected by any peer, or none.
removeDomainProtection(x)This methods removes domains, which can be protected. The opposite addDomainProtection does not make much sense here, because every peer has to specify it and is publicly known, which makes it a perfect target for malicious peers.
ENTRY_PROTECTION_ALL or ENTRY_PROTECTION_NONEThis mode turns of or on protection for entries.

These combinations are possible:

Examples

These modes are set by default, which should be fine for most applications.

If other values should be set, e.g., disable domain protection, use setProtection(protectionDomainEnable, protectionDomainMode, protectionEntryEnable, protectionEntryMode, protectionEntryInDomain).

A peer can protect a domain as follows:

FutureDHT futureDHT = peer1.put(Number160.ONE).setData(new Data("test"))
  .setProtectDomain().setDomainKey(Number160.ZERO).start();

The domain with the number peer2Owner is now protected and only peer1 can use this domain for data storage for any content key if the master public key mode is not set. If it is set, the peer with the hash of the public key equals 0, can overtake this domain as shown in the next example:

KeyPairGenerator gen = KeyPairGenerator.getInstance( "DSA" );
KeyPair pair2 = gen.generateKeyPair();
final Number160 peer2Owner = Utils.makeSHAHash( pair2.getPublic().getEncoded()); // results in 0
FutureDHT futureDHT = peer1.put(Number160.ONE).setData(new Data("test"))
  .setProtectDomain().setDomainKey(peer2Owner).start();

Domains can also be set to never be protected with storage.removeDomainProtection(new Number160(11));. Also, a public key with this hash cannot protect this domain if the domain is set to unprotectable. The entry protection works similar:

Data data=new Data("test1");
data.setProtectedEntry(true);
FutureDHT futureDHT = peer1.put(Number160.ONE).setData(data)
  .setProtectDomain().setDomainKey(peer2Owner).start();

The entry with the key 12 can only be changed by the peer_xy if the master content key mode is not set.

Tracker Security

A tracker has the built in modes: NO_DOMAIN_MASTER, DOMAIN_PROTECTION_NONE, ENTRY_PROTECTION_NONE, MASTER_ENTRY_PUBLIC_KEY, which means that if the peer decides to protect its entry on the tracker, the key has to be the hash of its public key and the add message has to be signed. This can be done by calling TrackerBuilder.setSign(true);.

Configurations

Each DHT operation in TomP2P can be configured. All configurations extend ConfigurationBase, which lets you set the content key (default 0), the domain (default “P2P domain”), the routing configuration and if the message should be signed. Reasonable defaults are created when using the Configurations class. The routing configuration lets you configure in particular:

Furthermore, each DHT configuration has a RequestP2PConfiguration, which configures the direct calls, such as get or put. The P2P configuration lets you configure in particular:

DHT Configurations: PutBuilder

PutBuilder lets you configure:

DHT Configurations: RemoveBuilder

Some of the options of RemoveBuilder are the same as of PutBuilder. The differences are:

DHT Configurations: SendDirectBuilder

Some of the options of SendDirectBuilder are the same as of PutBuilder / RemoveBuilder. The differences are:

DHT Configurations: GetBuilder

Since multiple results can be returned, an evaluation of the results is necessary. Such a result evaluation needs to implement EvaluatingSchemeDHT. The default scheme is a majority voting. Furthermore, the retrieval can specify a public key to get data from a specify peer only.

Tracker Configurations: AddTrackerBuilder

The AddTrackerBuilder configuration lets you configure:

Tracker Configurations: GetTrackerBuilder

Similar to the DHT get operation, an evaluation of the results is necessary. The flag expectAttachement has to be set if the caller is interested in the attachment.

TomP2P on Android

TomP2P has been tested and used on Anroid in a couple of academic projects. To install Android, please read the Quick Start. For any kind of development it is useful to use logcat for the error messages.

Extra Work

Although, the Android API is very similar to Java 1.5, you need to do some extra work.

Example

The following example code has been tested on Android 4.0.3 with TomP2P 4.0.7 (newer TomP2P versions have a slighly different API). Please note that the current TomP2P does not work with Android 2.3 or 2.2:

public class TestP2P extends Activity {
  final private static Random rnd = new Random(42L);
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
      
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        System.setProperty("java.net.preferIPv6Addresses", "false");
        Peer master = null;
        try
        {
                master = new Peer(new Number160(rnd));
                Bindings bindings=new Bindings();
                bindings.addProtocol(Protocol.IPv4);
                master.listen(4001, 4001, bindings);
                Peer[] nodes = createAndAttachNodes(master, 10);
                bootstrap(master, nodes);
                examplePutGet(nodes);
                exampleAddGet(nodes);
        }
        catch (Throwable e)
        {
          Log.wtf("tomp2p", e);
          e.printStackTrace();
        }
        finally
        {
                master.shutdown();
        }
        
    }
    private static void bootstrap(Peer master, Peer[] nodes)
    {
            List<FutureBootstrap> futures = new ArrayList<FutureBootstrap>();
            for (int i = 1; i < nodes.length; i++)
            {
                    FutureBootstrap tmp = nodes[i].bootstrap(master.getPeerAddress());
                    futures.add(tmp);
            }
            for (FutureBootstrap future : futures)
                    future.awaitUninterruptibly();
    }

    public static void examplePutGet(Peer[] nodes) throws IOException
    {
            Number160 nr = new Number160(rnd);
            String toStore = "hallo";
            Data data = new Data(toStore.getBytes());
            FutureDHT futureDHT = nodes[3].put(nr, data);
            futureDHT.awaitUninterruptibly();
            System.out.println("stored: " + toStore + " (" + futureDHT.isSuccess() + ")");
            futureDHT = nodes[7].get(nr);
            futureDHT.awaitUninterruptibly();
            //there are easier ways to get it, but this is for demonstration
            Data d1=futureDHT.getRawData().values().iterator().next().values().iterator().next();
            System.out.println("got: "
                            + new String(d1.getData(),d1.getOffset(),d1.getLength()) + " (" + futureDHT.isSuccess() + ")");
    }

    private static void exampleAddGet(Peer[] nodes) throws IOException
    {
            Number160 nr = new Number160(rnd);
            String toStore1 = "hallo1";
            String toStore2 = "hallo2";
            Data data1 = new Data(toStore1.getBytes());
            Data data2 = new Data(toStore2.getBytes());
            FutureDHT futureDHT = nodes[3].add(nr, data1);
            futureDHT.awaitUninterruptibly();
            System.out.println("added: " + toStore1 + " (" + futureDHT.isSuccess() + ")");
            futureDHT = nodes[5].add(nr, data2);
            futureDHT.awaitUninterruptibly();
            System.out.println("added: " + toStore2 + " (" + futureDHT.isSuccess() + ")");
            futureDHT = nodes[7].getAll(nr);
            futureDHT.awaitUninterruptibly();
            System.out.println("size " + futureDHT.getData().size());
            Iterator<Data> iterator = futureDHT.getData().values().iterator();
            Data d1=iterator.next();
            Data d2=iterator.next();
            System.out.println("got: " + new String(d1.getData(),d1.getOffset(),d1.getLength()) + " ("
                            + futureDHT.isSuccess() + ")");
            System.out.println("got: " + new String(d2.getData(),d2.getOffset(),d2.getLength()) + " ("
                            + futureDHT.isSuccess() + ")");
    }

    private static Peer[] createAndAttachNodes(Peer master, int nr) throws Exception
    {
            Peer[] nodes = new Peer[nr];
            nodes[0] = master;
            for (int i = 1; i < nr; i++)
            {
                    nodes[i] = new Peer(new Number160(rnd));
                    nodes[i].listen(master);
            }
            return nodes;
    }
}

The output of this example is for examplePutGet

stored: hallo (true)
got: hallo (true)

and for exampleAddGet

added: hallo2 (true)
size 2
got: hallo2 (true)
got: hallo1 (true)

The Eclispe workspace for TomP2P_Android is available for download: TomP2P-4.0.7_Android.zip (works with Android 4.0.3). An older version (works with Android 2.3) can be found here TomP2P-3.2.9_Android.zip.

Running and Connecting TomP2P on Two Emulators

To connect two TomP2P_Android applications and make them talk to each other, one has to setup port redirecting in Android. I’m not sure if this is needed, but I created two Android Virtual Devices (AVD): TomP2P-15 and TomP2P-15-1. Before setting up the port redirection, both AVD needs to be running (your-path-to-android-sdk/tools/emulator -avd TomP2P-15 and your-path-to-android-sdk/tools/emulator -avd TomP2P-15-1). The port redirection is set up by connecting to the device (telnet localhost 5554 and telnet localhost 5556) and run the commands redir add udp:5001:4001 and redir add tcp:5001:4001 for the emulator on port 5554 and redir add udp:6001:4001 / redir add tcp:6001:4001 for the emulator listening on port 5556. Now with the test application TestP2P, you should be able to connect from one emulator to the other by using the IP 10.0.2.2. The screenshot shows how the input (GUI) and output (console) should look like.

There is also a thread @stackoverflow.

Newest Versions of TomP2P on Android

The newest versions of TomP2P use logback, which could cause problems on Android. If you see an error such as:

Error:[TomP2P_Android] EXCEPTION FROM SIMULATION:
Error:[TomP2P_Android] local variable type mismatch: attempt to set or access a value of type java.lang.Object using a local variable of type java.lang.Object[]. This is symptomatic of .class transformation tools that ignore local variable information.
Error:[TomP2P_Android] ...at bytecode offset 000000c7
Error:[TomP2P_Android] locals[0000]: Lch/qos/logback/classic/gaffer/ComponentDelegate;

Then you should try the following library instead

Direct Messages

TomP2P can send direct messages in an RPC-style to other peers. There are two types of send() functions defined in TomP2P: sending objects and sending raw data. While the first serializes the object to a byte array, the later one does not and should be used if there is alreay a byte array.

For the usage of ChannelBuffer, please refer to this http://docs.jboss.org/netty/3.2/api/org/jboss/netty/buffer/class-use/ChannelBuffer.html. Basically, to create a ChannelBuffer from a byte[], you can use e.g., ChannelBuffers.wrappedBuffer(byte[] array) as described http://docs.jboss.org/netty/3.2/api/org/jboss/netty/buffer/ChannelBuffers.html.

Example

The following example shows how to send objects that can be serialized to an other peer.

Peer peer1 = null;
Peer peer2 = null;
try {
    peer1 = new PeerMaker(new Number160(RND)).setPorts(port1).makeAndListen();
    peer2 = new PeerMaker(new Number160(RND)).setPorts(port2).makeAndListen();
    //attach reply handler
    peer2.setObjectDataReply(new ObjectDataReply() {
        @Override
        public Object reply(final PeerAddress sender, final Object request) throws Exception {
            return "world!";
        }
    });
    FutureData futureData=p1.send(p2.getPeerAddress(), "hello");
    futureData.awaitUninterruptibly();
    System.out.println("reply ["+futureData.getObject()+"]");
} finally {
    peer1.shutdown();
    peer2.shutdown();
}

The output of this example is:

request [hello]
reply [world]

Setup TomP2P with Eclipse

The following steps describe how to work with TomP2P either with a released version or with the latest development version.

Setup TomP2P with a released version

  1. Download Eclipse. The latest version is recommended
  2. Unzip and run it
  3. Click on “Help” -> “Eclipse Marketplace” -> search for “m2e” and install "Maven Integration for Eclipse"
  4. File -> New Java Project -> enter name, e.g. TestTomP2P
  5. Right click on TestTomP2P in the package explorer -> Configure -> Convert to Maven Project
  6. edit pom.xml -> add below the version-tag:
<repositories>
  <repository>
    <id>tomp2p.net</id>
    <url>http://tomp2p.net/dev/mvn/</url>
  </repository>
</repositories>
<dependencies>
  <dependency>
    <groupId>net.tomp2p</groupId>
    <artifactId>TomP2P</artifactId>
    <version>4.x</version>
  </dependency>
</dependencies>

Replace the 4.x with the latest version and now you can start a new class and you can use TomP2P.

Setup TomP2P with the latest development version

  1. Download Eclipse. The latest version is recommended
  2. Unzip and run it
  3. Click on “Help” -> “Eclipse Marketplace” -> search for “m2e” and install "Maven Integration for Eclipse"
  4. After restarting Eclipse, go to “Window” -> “Show View” -> “Git Repository Exploring”. Copy the URL “https://github.com/tomp2p/TomP2P.git” and paste in the the Git Repository View (Paste Repository Path or URI). Click “next” -> select “master”, unselect the others -> next -> finish
  5. Right click on “TomP2P” in the repository view and click “Import Projects...” -> Select “Import as general project” -> next -> finsh
  6. Go to the Java perspective and click on “View Menu” (this is the triangle right next to minimize and maximize in the Package Explorer pane). Select “Filters...” and unselect “*.resources” -> ok
  7. Open .project and replate these empty XML tags in .project
<buildSpec>
</buildSpec>
<natures>
</natures>

with

<buildSpec>
  <buildCommand>
    <name>org.eclipse.jdt.core.javabuilder</name>
    <arguments>
    </arguments>
  </buildCommand>
  <buildCommand>
    <name>org.eclipse.m2e.core.maven2Builder</name>
    <arguments>
    </arguments>
  </buildCommand>
</buildSpec>
<natures>
  <nature>org.eclipse.m2e.core.maven2Nature</nature>
  <nature>org.eclipse.jdt.core.javanature</nature>
</natures>

Then right click on “TomP2P” -> “Maven” -> “Update Project...” -> ok. Now you are ready to go and you can reference this project in your own project (Properties -> Java Build Path -> Projects -> Add... -> and select TomP2P).