diff --git a/net/include/ftl/net/protocol.hpp b/net/include/ftl/net/protocol.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..59de85de039fc56e47a459ffb527b82bf619870b
--- /dev/null
+++ b/net/include/ftl/net/protocol.hpp
@@ -0,0 +1,6 @@
+#ifndef _FTL_NET_PROTOCOL_HPP_
+#define _FTL_NET_PROTOCOL_HPP_
+
+#define FTL_PROTOCOL_P2P		0x1000
+
+#endif // _FTL_NET_PROTOCOL_HPP_
diff --git a/p2p-rm/CMakeLists.txt b/p2p-rm/CMakeLists.txt
index 6046cecdf3c88fdcc8c3f276ef5f893704b9c86e..f13d00cef1fd2f847c8976434535ded1c5db6608 100644
--- a/p2p-rm/CMakeLists.txt
+++ b/p2p-rm/CMakeLists.txt
@@ -23,7 +23,7 @@ set(CMAKE_CXX_FLAGS_RELEASE "-O3")
 SET(CMAKE_USE_RELATIVE_PATHS ON)
 
 set(FTLSOURCE
-	src/blob.cpp
+	src/cluster.cpp
 	src/p2prm.cpp
 )
 add_library(ftl-p2prm ${FTLSOURCE})
diff --git a/p2p-rm/include/ftl/p2p-rm.hpp b/p2p-rm/include/ftl/p2p-rm.hpp
index 9c94e5db2be2d551a5b9fa5a2bbfa85c236e8683..00ea8d20eec849dd2d6ec3d36e06cec575cd07c1 100644
--- a/p2p-rm/include/ftl/p2p-rm.hpp
+++ b/p2p-rm/include/ftl/p2p-rm.hpp
@@ -1,87 +1,15 @@
-#ifndef _FTL_P2P_RA_HPP_
-#define _FTL_P2P_RA_HPP_
+#ifndef _FTL_P2P_RM_HPP_
+#define _FTL_P2P_RM_HPP_
 
-#include "ftl/p2p-rm/mapped_ptr.hpp"
-#include "ftl/p2p-rm/internal.hpp"
-
-#include <type_traits>
+#include <ftl/p2p-rm/cluster.hpp>
 
 namespace ftl {
 namespace rm {
 
-	void reset();
-	inline void destroy() { reset(); }
-	
-	/**
-	 * Obtain a remote pointer from a URI. A nullptr is returned if the URI is
-	 * not valid. If the URI is actually local then a remote pointer is still
-	 * returned and may be used normally, although it will possibly result in
-	 * unwanted memory copies.
-	 */
-	template <typename T>
-	ftl::mapped_ptr<T> get(const char *uri) {
-		auto b = _lookup(uri);
-		// TODO Verify type and size
-		return ftl::mapped_ptr<T>{b,0};
-	}
-	
-	/**
-	 * Get a read-only memory reference from a URI.
-	 */
-	template <typename T>
-	ftl::read_ref<T> getReadable(const char *uri) {
-		return get<T>(uri).readable();
-	}
+	std::shared_ptr<Cluster> cluster(const char *uri);
 	
-	/**
-	 * Get a read/writable memory reference from a URI.
-	 */
-	template <typename T>
-	ftl::write_ref<T> getWritable(const char *uri) {
-		return get<T>(uri).writable();
-	}
-	
-	/**
-	 * Register a memory area locally mapped to a given URI. The URI
-	 * must not already exist within the peer group.
-	 */
-	template <typename T>
-	ftl::mapped_ptr<T> map(const char *uri, T *addr, size_t size=1) {
-		if (std::is_pointer<T>::value) return ftl::null_ptr<T>;
-		if (std::is_function<T>::value) return ftl::null_ptr<T>;
-		if (std::is_void<T>::value) return ftl::null_ptr<T>;
-
-		if (addr == NULL) return ftl::null_ptr<T>;
-
-		return ftl::mapped_ptr<T>{_create(uri, (char*)addr, sizeof(T), size,
-				static_cast<flags_t>(std::is_integral<T>::value * ftl::rm::FLAG_INTEGER |
-				std::is_signed<T>::value * ftl::rm::FLAG_SIGNED |
-				std::is_trivial<T>::value * ftl::rm::FLAG_TRIVIAL),
-				typeid(T).name()),0};
-	}
-	
-	void unmap(const char *uri);
-
-	template <typename T>
-	void unmap(ftl::mapped_ptr<T> ptr) {}
-	
-	/**
-	 * Obtain a list or URI memory blocks in the current peer group that match
-	 * the provided base URI.
-	 */
-	std::vector<std::string> search(const char *partial_uri);
-	
-	/**
-	 * Connect to a new peer node using the specified socket.
-	 */
-	void addPeer(ftl::net::Socket *s);
-
-	/**
-	 * Connect to a new peer using a URL string.
-	 */
-	void addPeer(const char *url);
 }
 }
 
-#endif // _FTL_P2P_RA_HPP_
+#endif // _FTL_P2P_RM_HPP_
 
diff --git a/p2p-rm/include/ftl/p2p-rm/cluster.hpp b/p2p-rm/include/ftl/p2p-rm/cluster.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..b60de3f99655e6d315cf52485929d13ca24be4dd
--- /dev/null
+++ b/p2p-rm/include/ftl/p2p-rm/cluster.hpp
@@ -0,0 +1,107 @@
+#ifndef _FTL_P2P_RM_CLUSTER_HPP_
+#define _FTL_P2P_RM_CLUSTER_HPP_
+
+#include "ftl/p2p-rm/mapped_ptr.hpp"
+#include "ftl/p2p-rm/internal.hpp"
+
+#include <type_traits>
+#include <memory>
+#include <vector>
+
+namespace ftl {
+namespace net {
+	class Socket;
+	class Listener;
+};
+
+namespace rm {
+
+class Cluster {
+	public:
+	Cluster(const char *uri, std::shared_ptr<ftl::net::Listener> l);
+	~Cluster();
+	
+	void reset();
+	inline void destroy() { reset(); }
+	
+	/**
+	 * Obtain a remote pointer from a URI. A nullptr is returned if the URI is
+	 * not valid. If the URI is actually local then a remote pointer is still
+	 * returned and may be used normally, although it will possibly result in
+	 * unwanted memory copies.
+	 */
+	template <typename T>
+	ftl::mapped_ptr<T> get(const char *uri) {
+		auto b = _lookup(uri);
+		// TODO Verify type and size
+		return ftl::mapped_ptr<T>{b,0};
+	}
+	
+	/**
+	 * Get a read-only memory reference from a URI.
+	 */
+	template <typename T>
+	ftl::read_ref<T> getReadable(const char *uri) {
+		return get<T>(uri).readable();
+	}
+	
+	/**
+	 * Get a read/writable memory reference from a URI.
+	 */
+	template <typename T>
+	ftl::write_ref<T> getWritable(const char *uri) {
+		return get<T>(uri).writable();
+	}
+	
+	/**
+	 * Register a memory area locally mapped to a given URI. The URI
+	 * must not already exist within the peer group.
+	 */
+	template <typename T>
+	ftl::mapped_ptr<T> map(const char *uri, T *addr, size_t size=1) {
+		if (std::is_pointer<T>::value) return ftl::null_ptr<T>;
+		if (std::is_function<T>::value) return ftl::null_ptr<T>;
+		if (std::is_void<T>::value) return ftl::null_ptr<T>;
+
+		if (addr == NULL) return ftl::null_ptr<T>;
+
+		return ftl::mapped_ptr<T>{_create(this, uri, (char*)addr, sizeof(T), size,
+				static_cast<flags_t>(std::is_integral<T>::value * ftl::rm::FLAG_INTEGER |
+				std::is_signed<T>::value * ftl::rm::FLAG_SIGNED |
+				std::is_trivial<T>::value * ftl::rm::FLAG_TRIVIAL),
+				typeid(T).name()),0};
+	}
+	
+	void unmap(const char *uri);
+
+	template <typename T>
+	void unmap(ftl::mapped_ptr<T> ptr) {}
+	
+	/**
+	 * Obtain a list or URI memory blocks in the current peer group that match
+	 * the provided base URI.
+	 */
+	std::vector<std::string> search(const char *partial_uri);
+	
+	/**
+	 * Connect to a new peer node using the specified socket.
+	 */
+	void addPeer(std::shared_ptr<ftl::net::Socket> s);
+
+	/**
+	 * Connect to a new peer using a URL string.
+	 */
+	void addPeer(const char *url);
+	
+	private:
+	std::string uri_;
+	std::string root_;
+	std::shared_ptr<ftl::net::Listener> listener_;
+	std::vector<std::shared_ptr<ftl::net::Socket>> peers_;
+};
+
+};
+};
+
+#endif // _FTL_P2P_RM_CLUSTER_HPP_
+
diff --git a/p2p-rm/include/ftl/p2p-rm/internal.hpp b/p2p-rm/include/ftl/p2p-rm/internal.hpp
index 8519b942b1ea48bde33354a1d05553e2c0d95287..5d35fdac7417924dd5c14f68b095b2a6ca5e326c 100644
--- a/p2p-rm/include/ftl/p2p-rm/internal.hpp
+++ b/p2p-rm/include/ftl/p2p-rm/internal.hpp
@@ -5,6 +5,7 @@ namespace ftl {
 namespace rm {
 
 class Blob;
+class Cluster;
 
 enum flags_t : uint32_t {
 	FLAG_INTEGER = 1,
@@ -13,7 +14,7 @@ enum flags_t : uint32_t {
 };
 
 ftl::rm::Blob *_lookup(const char *uri);
-ftl::rm::Blob *_create(const char *uri, char *addr, size_t size, size_t count, flags_t flags, const std::string &tname);
+ftl::rm::Blob *_create(Cluster *c, const char *uri, char *addr, size_t size, size_t count, flags_t flags, const std::string &tname);
 
 }; // namespace rm
 }; // namespace ftl
diff --git a/p2p-rm/src/cluster.cpp b/p2p-rm/src/cluster.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/p2p-rm/test/CMakeLists.txt b/p2p-rm/test/CMakeLists.txt
index 17dd869af271582c34c9319ccb25e4d8b6b08186..82c07480af28c7fac5aa2f89d686e16eab6a5919 100644
--- a/p2p-rm/test/CMakeLists.txt
+++ b/p2p-rm/test/CMakeLists.txt
@@ -9,7 +9,7 @@ add_executable(mapped_ptr EXCLUDE_FROM_ALL
 add_executable(p2p_rm EXCLUDE_FROM_ALL
 	./tests.cpp
 	../src/p2prm.cpp
-	../src/blob.cpp
+	../src/cluster.cpp
 	./p2p-rm.cpp
 )
 target_link_libraries(p2p_rm uriparser)
diff --git a/p2p-rm/test/peer.cpp b/p2p-rm/test/peer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3cf2321bed5c5308b117ba62bcaba4b2103ab106
--- /dev/null
+++ b/p2p-rm/test/peer.cpp
@@ -0,0 +1,37 @@
+#include "ftl/p2p-rm.hpp"
+#include <ftl/net.hpp>
+
+#include <gflags/gflags.h>
+
+DEFINE_string(listen, "tcp://*:9000", "Listen URI");
+DEFINE_string(peer, "", "Peer to connect to");
+
+using namespace ftl;
+
+int main(int argc, char *argv) {
+	gflags::ParseCommandLineFlags(&argc, &argv, true);
+	
+	auto net = net::listen(FLAGS_listen);
+	auto cluster = rm::cluster("ftl://utu.fi", net);
+	
+	int data = 20;
+	auto ptr = cluster->map<int>("ftl://utu.fi/memory/test1", &data);
+	
+	if (FLAGS_peer) {
+		cluster->addPeer(FLAGS_peer);
+		std::cout << "Value = " << *ptr << std::endl; // 25.
+		std::cout << "Raw = " << data << std::endl; // 25.
+		
+		*ptr = 30;
+	} else {
+		*ptr = 25;
+		
+		ptr.onChange(()=> {
+			std::cout << "Value changed = " << *ptr << std::endl; // 30
+		});
+	}
+	
+	while (net());
+	return 0;
+}
+