diff --git a/AddressBook.h b/AddressBook.h
index 61b82f4b..d67089fa 100644
--- a/AddressBook.h
+++ b/AddressBook.h
@@ -18,7 +18,11 @@ namespace i2p
 {
 namespace client
 {
+#ifdef MESHNET
+	const char DEFAULT_SUBSCRIPTION_ADDRESS[] = "http://i42ofzetmgicvui5sshinfckpijix2udewbam4sjo6x5fbukltia.b32.i2p/hosts.txt";
+#else
 	const char DEFAULT_SUBSCRIPTION_ADDRESS[] = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt";
+#endif
 	const int INITIAL_SUBSCRIPTION_UPDATE_TIMEOUT = 3; // in minutes	
 	const int INITIAL_SUBSCRIPTION_RETRY_TIMEOUT = 1; // in minutes			
 	const int CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT = 720; // in minutes (12 hours)			
diff --git a/Config.cpp b/Config.cpp
index 2a7668c5..27f0fe1d 100644
--- a/Config.cpp
+++ b/Config.cpp
@@ -27,6 +27,10 @@ namespace config {
   variables_map       m_Options;
 
   void Init() {
+    bool nat = true;
+#ifdef MESHNET
+    nat = false;
+#endif
     options_description general("General options");
     general.add_options()
       ("help",     "Show this message")
@@ -39,6 +43,8 @@ namespace config {
 	  ("family",    value<std::string>()->default_value(""),     "Specify a family, router belongs to")
 	  ("datadir",   value<std::string>()->default_value(""),     "Path to storage of i2pd data (RI, keys, peer profiles, ...)")
       ("host",      value<std::string>()->default_value("0.0.0.0"),     "External IP")
+      ("ifname",    value<std::string>()->default_value(""), "network interface to bind to")
+      ("nat",       value<bool>()->zero_tokens()->default_value(nat), "should we assume we are behind NAT?")
       ("port",      value<uint16_t>()->default_value(0),                "Port to listen for incoming connections (default: auto)")
       ("ipv4",      value<bool>()->zero_tokens()->default_value(true),  "Enable communication through ipv4")
       ("ipv6",      value<bool>()->zero_tokens()->default_value(false), "Enable communication through ipv6")
@@ -47,6 +53,8 @@ namespace config {
       ("notransit", value<bool>()->zero_tokens()->default_value(false), "Router will not accept transit tunnels at startup")
       ("floodfill", value<bool>()->zero_tokens()->default_value(false), "Router will be floodfill")
       ("bandwidth", value<std::string>()->default_value(""), "Bandwidth limit: integer in kbps or letters: L (32), O (256), P (2048), X (>9000)")
+      ("ntcp", value<bool>()->zero_tokens()->default_value(true), "enable ntcp transport")
+      ("ssu", value<bool>()->zero_tokens()->default_value(true), "enable ssu transport")
 #ifdef _WIN32
       ("svcctl",    value<std::string>()->default_value(""),     "Windows service management ('install' or 'remove')")
       ("insomnia", value<bool>()->zero_tokens()->default_value(false), "Prevent system from sleeping")
@@ -128,7 +136,13 @@ namespace config {
 #endif	   
 	   "Enable or disable elgamal precomputation table")
 	  ;
-	  
+
+  options_description trust("Trust options");
+  trust.add_options()
+    ("trust.enabled", value<bool>()->default_value(false), "enable explicit trust options")
+    ("trust.family", value<std::string>()->default_value(""), "Router Familiy to trust for first hops")
+    ("trust.hidden", value<bool>()->default_value(false), "should we hide our router from other routers?");
+  
     m_OptionsDesc
       .add(general)
 	  .add(limits)	
@@ -139,7 +153,8 @@ namespace config {
       .add(bob)
 	  .add(i2cp)	
       .add(i2pcontrol)
-	  .add(precomputation) 	  
+	  .add(precomputation)
+      .add(trust)
       ;
   }
 
diff --git a/Daemon.cpp b/Daemon.cpp
index d34712a3..ab2c052f 100644
--- a/Daemon.cpp
+++ b/Daemon.cpp
@@ -22,6 +22,7 @@
 #include "I2PControl.h"
 #include "ClientContext.h"
 #include "Crypto.h"
+#include "util.h"
 
 #ifdef USE_UPNP
 #include "UPnP.h"
@@ -114,7 +115,7 @@ namespace i2p
 			}
 			i2p::log::Logger().Ready();
 
-			LogPrint(eLogInfo,  "i2pd v", VERSION, " starting");
+			LogPrint(eLogInfo,	"i2pd v", VERSION, " starting");
 			LogPrint(eLogDebug, "FS: main config file: ", config);
 			LogPrint(eLogDebug, "FS: data directory: ", datadir);
 
@@ -122,6 +123,43 @@ namespace i2p
 			i2p::crypto::InitCrypto (precomputation);
 			i2p::context.Init ();
 
+			bool ipv6;		i2p::config::GetOption("ipv6", ipv6);
+			bool ipv4;		i2p::config::GetOption("ipv4", ipv4);
+#ifdef MESHNET
+			// manual override for meshnet
+			ipv4 = false;
+			ipv6 = true;
+#endif
+
+			i2p::context.SetSupportsV6		 (ipv6);
+			i2p::context.SetSupportsV4		 (ipv4);
+
+			bool nat; i2p::config::GetOption("nat", nat);
+			if (nat)
+			{
+				LogPrint(eLogInfo, "Daemon: assuming be are behind NAT");
+				// we are behind nat, try setting via host 
+				std::string host; i2p::config::GetOption("host", host);
+				if (!i2p::config::IsDefault("host"))
+				{
+					LogPrint(eLogInfo, "Daemon: setting address for incoming connections to ", host);
+					i2p::context.UpdateAddress (boost::asio::ip::address::from_string (host));	
+				}
+			}
+			else
+			{
+				// we are not behind nat
+				std::string ifname; i2p::config::GetOption("ifname", ifname);
+				if (ifname.size())
+				{
+					// bind to interface, we have no NAT so set external address too
+					auto addr = i2p::util::net::GetInterfaceAddress(ifname, ipv6);
+					LogPrint(eLogInfo, "Daemon: bind to network interface ", ifname, " with public address ", addr);
+					i2p::context.UpdateAddress(addr);
+				}
+			}
+
+			
 			uint16_t port; i2p::config::GetOption("port", port);
 			if (!i2p::config::IsDefault("port"))
 			{	
@@ -129,15 +167,7 @@ namespace i2p
 				i2p::context.UpdatePort (port);
 			}	
 
-			std::string host; i2p::config::GetOption("host", host);
-			if (!i2p::config::IsDefault("host"))
-			{
-				LogPrint(eLogInfo, "Daemon: setting address for incoming connections to ", host);
-				i2p::context.UpdateAddress (boost::asio::ip::address::from_string (host));	
-			}
-
-			bool ipv6;		i2p::config::GetOption("ipv6", ipv6);
-			bool ipv4;		i2p::config::GetOption("ipv4", ipv4);
+      
 			bool transit; i2p::config::GetOption("notransit", transit);
 			i2p::context.SetSupportsV6		 (ipv6);
 			i2p::context.SetSupportsV4		 (ipv4);
@@ -192,31 +222,62 @@ namespace i2p
 			i2p::context.SetFamily (family);
 			if (family.length () > 0)
 				LogPrint(eLogInfo, "Daemon: family set to ", family);	
-			
-			return true;
+
+      bool trust; i2p::config::GetOption("trust.enabled", trust);
+      if (trust)
+      {
+        LogPrint(eLogInfo, "Daemon: explicit trust enabled");
+        std::string fam; i2p::config::GetOption("trust.family", fam);
+        if (fam.length() > 0)
+        {
+          LogPrint(eLogInfo, "Daemon: setting restricted routes to use family ", fam);
+          i2p::transport::transports.RestrictRoutes({fam});
+        } else
+          LogPrint(eLogError, "Daemon: no family specified for restricted routes");
+      }
+      bool hidden; i2p::config::GetOption("trust.hidden", hidden);
+      if (hidden)
+      {
+        LogPrint(eLogInfo, "Daemon: using hidden mode");
+        i2p::data::netdb.SetHidden(true);
+      }
+      return true;
 		}
 			
 		bool Daemon_Singleton::start()
 		{
-			bool http; i2p::config::GetOption("http.enabled", http);
-			if (http) {
-				std::string httpAddr; i2p::config::GetOption("http.address", httpAddr);
-				uint16_t    httpPort; i2p::config::GetOption("http.port",    httpPort);
-				LogPrint(eLogInfo, "Daemon: starting HTTP Server at ", httpAddr, ":", httpPort);
-				d.httpServer = std::unique_ptr<i2p::http::HTTPServer>(new i2p::http::HTTPServer(httpAddr, httpPort));
-				d.httpServer->Start();
-			}
-
 			LogPrint(eLogInfo, "Daemon: starting NetDB");
 			i2p::data::netdb.Start();
 
 #ifdef USE_UPNP
 			LogPrint(eLogInfo, "Daemon: starting UPnP");
 			d.m_UPnP.Start ();
-#endif			
+#endif
+			bool ntcp; i2p::config::GetOption("ntcp", ntcp);
+			bool ssu; i2p::config::GetOption("ssu", ssu);
 			LogPrint(eLogInfo, "Daemon: starting Transports");
-			i2p::transport::transports.Start();
+			if(!ssu) LogPrint(eLogDebug, "Daemon: ssu disabled");
+			if(!ntcp) LogPrint(eLogDebug, "Daemon: ntcp disabled");
+			i2p::transport::transports.Start(ntcp, ssu);
+			if (i2p::transport::transports.IsBoundNTCP() || i2p::transport::transports.IsBoundSSU()) {
+				LogPrint(eLogInfo, "Daemon: Transports started");
+			} else {
+				LogPrint(eLogError, "Daemon: failed to start Transports");
+				/** shut down netdb right away */
+				i2p::data::netdb.Stop();
+				return false;
+			}
+						
+			bool http; i2p::config::GetOption("http.enabled", http);
+			if (http) {
+				std::string httpAddr; i2p::config::GetOption("http.address", httpAddr);
+				uint16_t		httpPort; i2p::config::GetOption("http.port",		 httpPort);
+				LogPrint(eLogInfo, "Daemon: starting HTTP Server at ", httpAddr, ":", httpPort);
+				d.httpServer = std::unique_ptr<i2p::http::HTTPServer>(new i2p::http::HTTPServer(httpAddr, httpPort));
+				d.httpServer->Start();
+			}
 
+			
 			LogPrint(eLogInfo, "Daemon: starting Tunnels");
 			i2p::tunnel::tunnels.Start();
 
@@ -232,6 +293,7 @@ namespace i2p
 				d.m_I2PControlService = std::unique_ptr<i2p::client::I2PControlService>(new i2p::client::I2PControlService (i2pcpAddr, i2pcpPort));
 				d.m_I2PControlService->Start ();
 			}
+
 			return true;
 		}
 
diff --git a/Destination.cpp b/Destination.cpp
index 6cc24537..abfe3227 100644
--- a/Destination.cpp
+++ b/Destination.cpp
@@ -378,7 +378,7 @@ namespace client
 		uint32_t msgID = bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET);
 		if (msgID == m_PublishReplyToken)
 		{
-			LogPrint (eLogDebug, "Destination: Publishing LeaseSet confirmed");
+			LogPrint (eLogDebug, "Destination: Publishing LeaseSet confirmed for ", GetIdentHash().ToBase32());
 			m_ExcludedFloodfills.clear ();
 			m_PublishReplyToken = 0;
 			// schedule verification
@@ -459,19 +459,16 @@ namespace client
 				// "this" added due to bug in gcc 4.7-4.8
 				[s,this](std::shared_ptr<i2p::data::LeaseSet> leaseSet)
 				{
-					if (leaseSet)
+					if (leaseSet && s->m_LeaseSet)
 					{
-						if (s->m_LeaseSet && *s->m_LeaseSet == *leaseSet)
-						{
-							// we got latest LeasetSet
-							LogPrint (eLogDebug, "Destination: published LeaseSet verified");
-							s->m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_REGULAR_VERIFICATION_INTERNAL));
-							s->m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, s, std::placeholders::_1));	
-							return;
-						}		
+						// we got latest LeasetSet
+						LogPrint (eLogDebug, "Destination: published LeaseSet verified for ", GetIdentHash().ToBase32());
+						s->m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_REGULAR_VERIFICATION_INTERNAL));
+						s->m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, s, std::placeholders::_1));	
+						return;
 					}	
 					else
-						LogPrint (eLogWarning, "Destination: couldn't find published LeaseSet");
+						LogPrint (eLogWarning, "Destination: couldn't find published LeaseSet for ", GetIdentHash().ToBase32());
 					// we have to publish again
 					s->Publish ();	
 				});
diff --git a/HTTP.cpp b/HTTP.cpp
index 7fd87c55..71d277de 100644
--- a/HTTP.cpp
+++ b/HTTP.cpp
@@ -72,7 +72,11 @@ namespace http {
   bool URL::parse(const std::string& url) {
     std::size_t pos_p = 0; /* < current parse position */
     std::size_t pos_c = 0; /* < work position */
-    if (url.at(0) != '/') {
+    if (url.at(0) == '/' && url.find("/http://") == 0) {
+      /* special case for i2p.rocks inproxy */
+      pos_p ++;
+    }
+    if(url.at(0) != '/' || pos_p > 0) {
       /* schema */
       pos_c = url.find("://");
       if (pos_c != std::string::npos) {
diff --git a/I2CP.cpp b/I2CP.cpp
index 4884583e..061f220c 100644
--- a/I2CP.cpp
+++ b/I2CP.cpp
@@ -424,13 +424,24 @@ namespace client
 			if (m_Destination)
 			{
 				i2p::data::IdentityEx identity;
-				offset += identity.FromBuffer (buf + offset, len - offset);
-				uint32_t payloadLen = bufbe32toh (buf + offset);
-				offset += 4;
-				uint32_t nonce = bufbe32toh (buf + offset + payloadLen);
-				if (m_IsSendAccepted) 
-					SendMessageStatusMessage (nonce, eI2CPMessageStatusAccepted); // accepted
-				m_Destination->SendMsgTo (buf + offset, payloadLen, identity.GetIdentHash (), nonce);
+				size_t identsize = identity.FromBuffer (buf + offset, len - offset);
+        if (identsize)
+        {
+          offset += identsize;
+          uint32_t payloadLen = bufbe32toh (buf + offset);
+          if (payloadLen + offset <= len)
+          {            
+            offset += 4;
+            uint32_t nonce = bufbe32toh (buf + offset + payloadLen);
+            if (m_IsSendAccepted) 
+              SendMessageStatusMessage (nonce, eI2CPMessageStatusAccepted); // accepted
+            m_Destination->SendMsgTo (buf + offset, payloadLen, identity.GetIdentHash (), nonce);
+          }
+          else
+            LogPrint(eLogError, "I2CP: cannot send message, too big");
+        }
+        else
+          LogPrint(eLogError, "I2CP: invalid identity");
 			} 
 		}	
 		else
diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp
index c47a1657..1b2d0317 100644
--- a/I2NPProtocol.cpp
+++ b/I2NPProtocol.cpp
@@ -11,6 +11,7 @@
 #include "Transports.h"
 #include "Garlic.h"
 #include "I2NPProtocol.h"
+#include "version.h"
 
 using namespace i2p::transport;
 
@@ -101,7 +102,7 @@ namespace i2p
 		{
 			RAND_bytes ((uint8_t *)&msgID, 4);
 			htobe32buf (buf + DELIVERY_STATUS_MSGID_OFFSET, msgID);
-			htobe64buf (buf + DELIVERY_STATUS_TIMESTAMP_OFFSET, 2); // netID = 2
+			htobe64buf (buf + DELIVERY_STATUS_TIMESTAMP_OFFSET, I2PD_NET_ID); 
 		}	
 		m->len += DELIVERY_STATUS_SIZE;
 		m->FillI2NPMessageHeader (eI2NPDeliveryStatus);
diff --git a/LeaseSet.cpp b/LeaseSet.cpp
index 16a470e0..fafe14b7 100644
--- a/LeaseSet.cpp
+++ b/LeaseSet.cpp
@@ -190,7 +190,7 @@ namespace data
 
 	bool LeaseSet::IsExpired () const
 	{
-		if (IsEmpty ()) return true;
+		if (m_StoreLeases && IsEmpty ()) return true;
 		auto ts = i2p::util::GetMillisecondsSinceEpoch ();
 		return ts > m_ExpirationTime;
 	}
diff --git a/Makefile b/Makefile
index fe8ae7e3..22f016ea 100644
--- a/Makefile
+++ b/Makefile
@@ -11,6 +11,7 @@ include filelist.mk
 
 USE_AESNI  := yes
 USE_STATIC := no
+USE_MESHNET := no
 USE_UPNP   := no
 
 ifeq ($(UNAME),Darwin)
@@ -31,6 +32,10 @@ else # win32 mingw
 	include Makefile.mingw
 endif
 
+ifeq ($(USE_MESHNET),yes)
+	CXXFLAGS += -DMESHNET
+endif
+
 all: mk_obj_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD)
 
 mk_obj_dir:
diff --git a/NTCPSession.cpp b/NTCPSession.cpp
index 953c0707..9a2b6687 100644
--- a/NTCPSession.cpp
+++ b/NTCPSession.cpp
@@ -760,30 +760,46 @@ namespace transport
 			auto& addresses = context.GetRouterInfo ().GetAddresses ();
 			for (auto address: addresses)
 			{
-				if (address->transportStyle == i2p::data::RouterInfo::eTransportNTCP && address->host.is_v4 ())
-				{	
-					m_NTCPAcceptor = new boost::asio::ip::tcp::acceptor (m_Service,
-						boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address->port));
-
-					LogPrint (eLogInfo, "NTCP: Start listening TCP port ", address->port);
-					auto conn = std::make_shared<NTCPSession>(*this);
-					m_NTCPAcceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAccept, this, 
-						conn, std::placeholders::_1));	
-				
-					if (context.SupportsV6 ())
+				if (address->transportStyle == i2p::data::RouterInfo::eTransportNTCP)
+				{
+					if (address->host.is_v4())
 					{
-						m_NTCPV6Acceptor = new boost::asio::ip::tcp::acceptor (m_Service);
-						m_NTCPV6Acceptor->open (boost::asio::ip::tcp::v6());
-						m_NTCPV6Acceptor->set_option (boost::asio::ip::v6_only (true));
-						m_NTCPV6Acceptor->bind (boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address->port));
-						m_NTCPV6Acceptor->listen ();
-
-						LogPrint (eLogInfo, "NTCP: Start listening V6 TCP port ", address->port);
-						auto conn = std::make_shared<NTCPSession> (*this);
-						m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6,
-							this, conn, std::placeholders::_1));
+						try
+						{
+							m_NTCPAcceptor = new boost::asio::ip::tcp::acceptor (m_Service,
+								boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address->port));
+						} catch ( std::exception & ex ) {
+							/** fail to bind ip4 */
+							LogPrint(eLogError, "NTCP: Failed to bind to ip4 port ",address->port, ex.what());
+							continue;
+						}
+						
+						LogPrint (eLogInfo, "NTCP: Start listening TCP port ", address->port);
+						auto conn = std::make_shared<NTCPSession>(*this);
+						m_NTCPAcceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAccept, this, 
+							conn, std::placeholders::_1));	
+					}
+					else if (address->host.is_v6() && context.SupportsV6 ())
+					{
+						m_NTCPV6Acceptor = new boost::asio::ip::tcp::acceptor (m_Service);							
+						try
+						{
+							m_NTCPV6Acceptor->open (boost::asio::ip::tcp::v6());
+							m_NTCPV6Acceptor->set_option (boost::asio::ip::v6_only (true));
+							
+							m_NTCPV6Acceptor->bind (boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address->port));
+							m_NTCPV6Acceptor->listen ();
+							
+							LogPrint (eLogInfo, "NTCP: Start listening V6 TCP port ", address->port);
+							auto conn = std::make_shared<NTCPSession> (*this);
+							m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6,
+								this, conn, std::placeholders::_1));
+						} catch ( std::exception & ex ) {
+							LogPrint(eLogError, "NTCP: failed to bind to ip6 port ", address->port);
+							continue;
+						}
 					}	
-				}	
+				}
 			}	
 		}	
 	}
@@ -795,9 +811,11 @@ namespace transport
 		if (m_IsRunning)
 		{	
 			m_IsRunning = false;
-			delete m_NTCPAcceptor;
+      if (m_NTCPAcceptor)
+        delete m_NTCPAcceptor;
 			m_NTCPAcceptor = nullptr;
-			delete m_NTCPV6Acceptor;
+      if (m_NTCPV6Acceptor)
+        delete m_NTCPV6Acceptor;
 			m_NTCPV6Acceptor = nullptr;
 
 			m_Service.stop ();
diff --git a/NTCPSession.h b/NTCPSession.h
index f4ce18a6..2a60f1dc 100644
--- a/NTCPSession.h
+++ b/NTCPSession.h
@@ -144,7 +144,10 @@ namespace transport
 			void RemoveNTCPSession (std::shared_ptr<NTCPSession> session);
 			std::shared_ptr<NTCPSession> FindNTCPSession (const i2p::data::IdentHash& ident);
 			void Connect (const boost::asio::ip::address& address, int port, std::shared_ptr<NTCPSession> conn);
-			
+
+      bool IsBoundV4() const { return m_NTCPAcceptor != nullptr; };
+      bool IsBoundV6() const { return m_NTCPV6Acceptor != nullptr; };
+      
 			boost::asio::io_service& GetService () { return m_Service; };
 			void Ban (const boost::asio::ip::address& addr);			
 
diff --git a/NetDb.cpp b/NetDb.cpp
index 59b10f26..f89617be 100644
--- a/NetDb.cpp
+++ b/NetDb.cpp
@@ -24,7 +24,7 @@ namespace data
 {		
 	NetDb netdb;
 
-	NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr), m_Storage("netDb", "r", "routerInfo-", "dat")
+	NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr), m_Storage("netDb", "r", "routerInfo-", "dat"), m_HiddenMode(false)
 	{
 	}
 	
@@ -67,7 +67,7 @@ namespace data
 			}
 			m_LeaseSets.clear();
 			m_Requests.Stop ();
-		}	
+		}
 	}	
 	
 	void NetDb::Run ()
@@ -121,8 +121,12 @@ namespace data
 						ManageLookupResponses ();
 					}	
 					lastSave = ts;
-				}	
-				if (ts - lastPublish >= 2400) // publish every 40 minutes
+				}
+
+        // if we're in hidden mode don't publish or explore
+				// if (m_HiddenMode) continue;
+				
+				if (ts - lastPublish >= NETDB_PUBLISH_INTERVAL) // publish 
 				{
 					Publish ();
 					lastPublish = ts;
@@ -161,6 +165,11 @@ namespace data
 		return false;
 	}
 
+  void NetDb::SetHidden(bool hide) {
+    // TODO: remove reachable addresses from router info
+    m_HiddenMode = hide;
+  }
+  
 	bool NetDb::AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len)
 	{	
 		bool updated = true;	
@@ -174,10 +183,8 @@ namespace data
 				// TODO: check if floodfill has been changed
 			}
 			else
-			{
 				LogPrint (eLogDebug, "NetDb: RouterInfo is older: ", ident.ToBase64());
-				updated = false;
-			}
+			
 		}	
 		else	
 		{	
@@ -217,29 +224,29 @@ namespace data
 					it->second->Update (buf, len); 
 					if (it->second->IsValid ())
 					{
-						LogPrint (eLogInfo, "NetDb: LeaseSet updated: ", ident.ToBase64());
+						LogPrint (eLogInfo, "NetDb: LeaseSet updated: ", ident.ToBase32());
 						updated = true;	
 					}
 					else
 					{
-						LogPrint (eLogWarning, "NetDb: LeaseSet update failed: ", ident.ToBase64());
+						LogPrint (eLogWarning, "NetDb: LeaseSet update failed: ", ident.ToBase32());
 						m_LeaseSets.erase (it);
 					}	
 				}
 				else
-					LogPrint (eLogDebug, "NetDb: LeaseSet is older: ", ident.ToBase64());
+					LogPrint (eLogDebug, "NetDb: LeaseSet is older: ", ident.ToBase32());
 			}
 			else
 			{	
 				auto leaseSet = std::make_shared<LeaseSet> (buf, len, false); // we don't need leases in netdb 
 				if (leaseSet->IsValid ())
 				{
-					LogPrint (eLogInfo, "NetDb: LeaseSet added: ", ident.ToBase64());
+					LogPrint (eLogInfo, "NetDb: LeaseSet added: ", ident.ToBase32());
 					m_LeaseSets[ident] = leaseSet;
 					updated = true;
 				}
 				else
-					LogPrint (eLogError, "NetDb: new LeaseSet validation failed: ", ident.ToBase64());
+					LogPrint (eLogError, "NetDb: new LeaseSet validation failed: ", ident.ToBase32());
 			}	
 		}	
 		return updated;
@@ -461,7 +468,7 @@ namespace data
 		bool updated = false;
 		if (buf[DATABASE_STORE_TYPE_OFFSET]) // type
 		{
-			LogPrint (eLogDebug, "NetDb: store request: LeaseSet");
+			LogPrint (eLogDebug, "NetDb: store request: LeaseSet for ", ident.ToBase32());
 			updated = AddLeaseSet (ident, buf + offset, len - offset, m->from);
 		}	
 		else
@@ -487,7 +494,7 @@ namespace data
 			uint8_t * payload = floodMsg->GetPayload ();		
 			memcpy (payload, buf, 33); // key + type
 			htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0); // zero reply token
-			auto msgLen = len - payloadOffset;
+			size_t msgLen = len - payloadOffset;
 			floodMsg->len += DATABASE_STORE_HEADER_SIZE + msgLen;
 			if (floodMsg->len < floodMsg->maxLen)
 			{	
@@ -501,16 +508,18 @@ namespace data
 					auto floodfill = GetClosestFloodfill (ident, excluded);
 					if (floodfill)
 					{
-						transports.SendMessage (floodfill->GetIdentHash (), CopyI2NPMessage(floodMsg));
-						excluded.insert (floodfill->GetIdentHash ());
+						auto h = floodfill->GetIdentHash();
+						LogPrint(eLogDebug, "NetDb: Flood lease set for ", ident.ToBase32(), " to ", h.ToBase64());
+						transports.SendMessage (h, CopyI2NPMessage(floodMsg));
+						excluded.insert (h);
 					}
 					else
 						break;
 				}	
 			}	
 			else
-				LogPrint (eLogError, "Database store message is too long ", floodMsg->len);
-		}	
+				LogPrint (eLogError, "NetDb: Database store message is too long ", floodMsg->len);
+		}	 
 	}	
 
 	void NetDb::HandleDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg)
@@ -615,6 +624,9 @@ namespace data
 		int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48);
 		key[l] = 0;
 		uint8_t flag = buf[64];
+
+		IdentHash replyIdent(buf + 32);
+		
 		LogPrint (eLogDebug, "NetDb: DatabaseLookup for ", key, " recieved flags=", (int)flag);
 		uint8_t lookupType = flag & DATABASE_LOOKUP_TYPE_FLAGS_MASK;
 		const uint8_t * excluded = buf + 65;		
@@ -673,7 +685,12 @@ namespace data
 			    lookupType == DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP))
 			{
 				auto leaseSet = FindLeaseSet (ident);
-				if (leaseSet && !leaseSet->IsExpired ()) // we don't send back our LeaseSets
+				if (!leaseSet)
+				{
+					// no lease set found
+					LogPrint(eLogDebug, "NetDb: requested LeaseSet not found for ", ident.ToBase32());
+				}
+				else if (!leaseSet->IsExpired ()) // we don't send back our LeaseSets
 				{
 					LogPrint (eLogDebug, "NetDb: requested LeaseSet ", key, " found");
 					replyMsg = CreateDatabaseStoreMsg (leaseSet);
@@ -730,12 +747,12 @@ namespace data
 				auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool ();
 				auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : nullptr;
 				if (outbound)
-					outbound->SendTunnelDataMsg (buf+32, replyTunnelID, replyMsg);
+					outbound->SendTunnelDataMsg (replyIdent, replyTunnelID, replyMsg);
 				else
-					transports.SendMessage (buf+32, i2p::CreateTunnelGatewayMsg (replyTunnelID, replyMsg));
+					transports.SendMessage (replyIdent, i2p::CreateTunnelGatewayMsg (replyTunnelID, replyMsg));
 			}
 			else
-				transports.SendMessage (buf+32, replyMsg);
+				transports.SendMessage (replyIdent, replyMsg);
 		}
 	}	
 
@@ -862,7 +879,7 @@ namespace data
 	{
 		if (m_RouterInfos.empty())
 			return 0;
-		uint32_t ind = rand () % m_RouterInfos.size ();	
+		uint32_t ind = rand () % m_RouterInfos.size ();
 		for (int j = 0; j < 2; j++)
 		{	
 			uint32_t i = 0;
@@ -966,6 +983,14 @@ namespace data
 		return res;
 	}
 
+  std::shared_ptr<const RouterInfo> NetDb::GetRandomRouterInFamily(const std::string & fam) const {
+    return GetRandomRouter(
+      [fam](std::shared_ptr<const RouterInfo> router)->bool
+      {
+        return router->IsFamily(fam);
+      });
+  }
+  
 	std::shared_ptr<const RouterInfo> NetDb::GetClosestNonFloodfill (const IdentHash& destination, 
 		const std::set<IdentHash>& excluded) const
 	{
diff --git a/NetDb.h b/NetDb.h
index 823dbb54..3b54ae4c 100644
--- a/NetDb.h
+++ b/NetDb.h
@@ -31,6 +31,12 @@ namespace data
 	const int NETDB_INTRODUCEE_EXPIRATION_TIMEOUT = 65*60;
 	const int NETDB_MIN_EXPIRATION_TIMEOUT = 90*60; // 1.5 hours
 	const int NETDB_MAX_EXPIRATION_TIMEOUT = 27*60*60; // 27 hours
+
+#ifdef MESHNET
+	const int NETDB_PUBLISH_INTERVAL = 60;
+#else
+	const int NETDB_PUBLISH_INTERVAL = 60*40;
+#endif
 	
 	class NetDb
 	{
@@ -64,10 +70,14 @@ namespace data
 			std::vector<IdentHash> GetClosestFloodfills (const IdentHash& destination, size_t num,
 				std::set<IdentHash>& excluded, bool closeThanUsOnly = false) const;
 			std::shared_ptr<const RouterInfo> GetClosestNonFloodfill (const IdentHash& destination, const std::set<IdentHash>& excluded) const;
+      std::shared_ptr<const RouterInfo> GetRandomRouterInFamily(const std::string & fam) const;
 			void SetUnreachable (const IdentHash& ident, bool unreachable);			
 
 			void PostI2NPMsg (std::shared_ptr<const I2NPMessage> msg);
 
+      /** set hidden mode, aka don't publish our RI to netdb and don't explore */
+      void SetHidden(bool hide); 
+      
 			void Reseed ();
 			Families& GetFamilies () { return m_Families; };
 
@@ -88,8 +98,8 @@ namespace data
 			void ManageRequests ();
 			void ManageLookupResponses ();
 
-			template<typename Filter>
-			std::shared_ptr<const RouterInfo> GetRandomRouter (Filter filter) const;	
+    	template<typename Filter>
+        std::shared_ptr<const RouterInfo> GetRandomRouter (Filter filter) const;	
 		
 		private:
 
@@ -113,6 +123,9 @@ namespace data
 			NetDbRequests m_Requests;
 
 			std::map<IdentHash, std::pair<std::vector<IdentHash>, uint64_t> > m_LookupResponses; // ident->(closest FFs, timestamp)
+
+      /** true if in hidden mode */
+      bool m_HiddenMode;
 	};
 
 	extern NetDb netdb;
diff --git a/Reseed.cpp b/Reseed.cpp
index 722d7eff..096035da 100644
--- a/Reseed.cpp
+++ b/Reseed.cpp
@@ -20,20 +20,26 @@ namespace i2p
 {
 namespace data
 {
-	static std::vector<std::string> httpsReseedHostList = 
+	static std::vector<std::string> httpsReseedHostList =
 	{
+#ifdef MESHNET
+		// meshnet i2p reseeds
+		"https://reseed.i2p.rocks:8443/"
+#else
+		// mainline i2p reseeds
 		"https://reseed.i2p-projekt.de/", // Only HTTPS
-        "https://i2p.mooo.com/netDb/",
-        "https://netdb.i2p2.no/", // Only SU3 (v3) support, SNI required
+				"https://i2p.mooo.com/netDb/",
+				"https://netdb.i2p2.no/", // Only SU3 (v3) support, SNI required
 		"https://us.reseed.i2p2.no:444/",	
-        "https://uk.reseed.i2p2.no:444/",
+				"https://uk.reseed.i2p2.no:444/",
 		"https://i2p.manas.ca:8443/",
 		"https://i2p-0.manas.ca:8443/",
-        "https://reseed.i2p.vzaws.com:8443/", // Only SU3 (v3) support
-        "https://user.mx24.eu/", // Only HTTPS and SU3 (v3) support
-        "https://download.xxlspeed.com/" // Only HTTPS and SU3 (v3) support
+				"https://reseed.i2p.vzaws.com:8443/", // Only SU3 (v3) support
+				"https://user.mx24.eu/", // Only HTTPS and SU3 (v3) support
+				"https://download.xxlspeed.com/" // Only HTTPS and SU3 (v3) support
+#endif
 	};
-	
+ 
 	Reseeder::Reseeder()
 	{
 	}
diff --git a/RouterInfo.cpp b/RouterInfo.cpp
index 0ef2f623..3462d7d8 100644
--- a/RouterInfo.cpp
+++ b/RouterInfo.cpp
@@ -290,7 +290,11 @@ namespace data
 
 		if (!m_SupportedTransports || !m_Addresses.size() || (UsesIntroducer () && !introducers))
 			SetUnreachable (true);
-	}	
+	}
+
+  bool RouterInfo::IsFamily(const std::string & fam) const {
+    return m_Family == fam;
+  }
 
 	void RouterInfo::ExtractCaps (const char * value)
 	{
diff --git a/RouterInfo.h b/RouterInfo.h
index a55924a8..8c8af691 100644
--- a/RouterInfo.h
+++ b/RouterInfo.h
@@ -171,6 +171,9 @@ namespace data
 			void DeleteBuffer () { delete[] m_Buffer; m_Buffer = nullptr; };
 			bool IsNewer (const uint8_t * buf, size_t len) const;			
 
+      /** return true if we are in a router family and the signature is valid */
+      bool IsFamily(const std::string & fam) const;
+      
 			// implements RoutingDestination
 			const IdentHash& GetIdentHash () const { return m_RouterIdentity->GetIdentHash (); };
 			const uint8_t * GetEncryptionPublicKey () const { return m_RouterIdentity->GetStandardIdentity ().publicKey; };
diff --git a/SAM.cpp b/SAM.cpp
index dea3614e..55eae222 100644
--- a/SAM.cpp
+++ b/SAM.cpp
@@ -56,7 +56,8 @@ namespace client
 				if (m_Session)
 				{	
 					m_Session->DelSocket (shared_from_this ());
-					m_Session->localDestination->StopAcceptingStreams ();	
+					if (m_Session->localDestination)
+						m_Session->localDestination->StopAcceptingStreams ();
 				}
 				break;
 			}
diff --git a/SSU.cpp b/SSU.cpp
index d635a7f9..4693a4f6 100644
--- a/SSU.cpp
+++ b/SSU.cpp
@@ -10,12 +10,31 @@ namespace i2p
 {
 namespace transport
 {
-	SSUServer::SSUServer (int port): m_Thread (nullptr), m_ThreadV6 (nullptr), m_ReceiversThread (nullptr),
+
+	SSUServer::SSUServer (const boost::asio::ip::address & addr, int port):
+		m_OnlyV6(true), m_IsRunning(false),
+		m_Thread (nullptr), m_ThreadV6 (nullptr), m_ReceiversThread (nullptr),
+		m_Work (m_Service), m_WorkV6 (m_ServiceV6), m_ReceiversWork (m_ReceiversService), 
+		m_EndpointV6 (addr, port), 
+		m_Socket (m_ReceiversService, m_Endpoint), m_SocketV6 (m_ReceiversService), 
+		m_IntroducersUpdateTimer (m_Service), m_PeerTestsCleanupTimer (m_Service)	
+	{
+		m_SocketV6.open (boost::asio::ip::udp::v6());
+		m_SocketV6.set_option (boost::asio::ip::v6_only (true));
+		m_SocketV6.set_option (boost::asio::socket_base::receive_buffer_size (65535));
+		m_SocketV6.set_option (boost::asio::socket_base::send_buffer_size (65535));
+		m_SocketV6.bind (m_EndpointV6);
+	}
+	
+	SSUServer::SSUServer (int port):
+		m_OnlyV6(false), m_IsRunning(false),
+		m_Thread (nullptr), m_ThreadV6 (nullptr), m_ReceiversThread (nullptr),
 		m_Work (m_Service), m_WorkV6 (m_ServiceV6), m_ReceiversWork (m_ReceiversService), 
 		m_Endpoint (boost::asio::ip::udp::v4 (), port), m_EndpointV6 (boost::asio::ip::udp::v6 (), port), 
 		m_Socket (m_ReceiversService, m_Endpoint), m_SocketV6 (m_ReceiversService), 
 		m_IntroducersUpdateTimer (m_Service), m_PeerTestsCleanupTimer (m_Service)	
 	{
+		
 		m_Socket.set_option (boost::asio::socket_base::receive_buffer_size (65535));
 		m_Socket.set_option (boost::asio::socket_base::send_buffer_size (65535));
 		if (context.SupportsV6 ())
@@ -35,13 +54,16 @@ namespace transport
 	void SSUServer::Start ()
 	{
 		m_IsRunning = true;
-		m_ReceiversThread = new std::thread (std::bind (&SSUServer::RunReceivers, this)); 
-		m_Thread = new std::thread (std::bind (&SSUServer::Run, this));
-		m_ReceiversService.post (std::bind (&SSUServer::Receive, this));  
+		m_ReceiversThread = new std::thread (std::bind (&SSUServer::RunReceivers, this));
+		if (!m_OnlyV6)
+		{
+			m_Thread = new std::thread (std::bind (&SSUServer::Run, this));
+			m_ReceiversService.post (std::bind (&SSUServer::Receive, this));
+		}
 		if (context.SupportsV6 ())
 		{	
 			m_ThreadV6 = new std::thread (std::bind (&SSUServer::RunV6, this));
-			m_ReceiversService.post (std::bind (&SSUServer::ReceiveV6, this));  
+			m_ReceiversService.post (std::bind (&SSUServer::ReceiveV6, this));	
 		}
 		SchedulePeerTestsCleanupTimer ();	
 		ScheduleIntroducersUpdateTimer (); // wait for 30 seconds and decide if we need introducers
diff --git a/SSU.h b/SSU.h
index 8ee58ffa..0fdf0621 100644
--- a/SSU.h
+++ b/SSU.h
@@ -37,6 +37,7 @@ namespace transport
 		public:
 
 			SSUServer (int port);
+			SSUServer (const boost::asio::ip::address & addr, int port);			// ipv6 only constructor
 			~SSUServer ();
 			void Start ();
 			void Stop ();
@@ -62,7 +63,7 @@ namespace transport
 			std::shared_ptr<SSUSession> GetPeerTestSession (uint32_t nonce);
 			void UpdatePeerTest (uint32_t nonce, PeerTestParticipant role);
 			void RemovePeerTest (uint32_t nonce);
-
+      
 		private:
 
 			void Run ();
@@ -93,8 +94,9 @@ namespace transport
 				uint64_t creationTime;
 				PeerTestParticipant role;
 				std::shared_ptr<SSUSession> session; // for Bob to Alice
-			};	
+			};
 			
+			bool m_OnlyV6;			
 			bool m_IsRunning;
 			std::thread * m_Thread, * m_ThreadV6, * m_ReceiversThread;	
 			boost::asio::io_service m_Service, m_ServiceV6, m_ReceiversService;
diff --git a/SSUData.h b/SSUData.h
index 02135350..bfc75128 100644
--- a/SSUData.h
+++ b/SSUData.h
@@ -18,7 +18,11 @@ namespace transport
 {
 
 	const size_t SSU_MTU_V4 = 1484;
+	#ifdef MESHNET
+	const size_t SSU_MTU_V6 = 1286;
+	#else
 	const size_t SSU_MTU_V6 = 1472;
+	#endif
 	const size_t IPV4_HEADER_SIZE = 20;
 	const size_t IPV6_HEADER_SIZE = 40;	
 	const size_t UDP_HEADER_SIZE = 8;
diff --git a/SSUSession.cpp b/SSUSession.cpp
index 9b480888..9c90ff88 100644
--- a/SSUSession.cpp
+++ b/SSUSession.cpp
@@ -20,14 +20,14 @@ namespace transport
 		if (router)
 		{
 			// we are client
-			auto address = router->GetSSUAddress ();
+			auto address = router->GetSSUAddress (false);
 			if (address) m_IntroKey = address->key;
 			m_Data.AdjustPacketSize (router); // mtu
 		}
 		else
 		{
 			// we are server
-			auto address = i2p::context.GetRouterInfo ().GetSSUAddress ();
+			auto address = i2p::context.GetRouterInfo ().GetSSUAddress (false);
 			if (address) m_IntroKey = address->key;
 		}
 		m_CreationTime = i2p::util::GetSecondsSinceEpoch ();
@@ -108,7 +108,7 @@ namespace transport
 				else
 				{    
 					// try own intro key
-					auto address = i2p::context.GetRouterInfo ().GetSSUAddress ();
+					auto address = i2p::context.GetRouterInfo ().GetSSUAddress (false);
 					if (!address)
 					{
 						LogPrint (eLogInfo, "SSU is not supported");
@@ -366,7 +366,7 @@ namespace transport
 
 	void SSUSession::SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce)
 	{
-		auto address = i2p::context.GetRouterInfo ().GetSSUAddress ();
+		auto address = i2p::context.GetRouterInfo ().GetSSUAddress (false);
 		if (!address)
 		{
 			LogPrint (eLogInfo, "SSU is not supported");
@@ -1079,7 +1079,7 @@ namespace transport
 	{
 		// we are Alice
 		LogPrint (eLogDebug, "SSU: sending peer test");
-		auto address = i2p::context.GetRouterInfo ().GetSSUAddress ();
+		auto address = i2p::context.GetRouterInfo ().GetSSUAddress (false);
 		if (!address)
 		{
 			LogPrint (eLogInfo, "SSU is not supported. Can't send peer test");
diff --git a/Transports.cpp b/Transports.cpp
index b15ec56f..b4130dfd 100644
--- a/Transports.cpp
+++ b/Transports.cpp
@@ -46,7 +46,7 @@ namespace transport
 			int num;
 			while ((num = m_QueueSize - m_Queue.size ()) > 0)
 				CreateDHKeysPairs (num);
-			std::unique_lock<std::mutex>  l(m_AcquiredMutex);
+			std::unique_lock<std::mutex>	l(m_AcquiredMutex);
 			m_Acquired.wait (l); // wait for element gets aquired
 		}
 	}		
@@ -60,7 +60,7 @@ namespace transport
 			{
 				auto pair = std::make_shared<i2p::crypto::DHKeys> ();
 				pair->GenerateKeys ();
-				std::unique_lock<std::mutex>  l(m_AcquiredMutex);
+				std::unique_lock<std::mutex>	l(m_AcquiredMutex);
 				m_Queue.push (pair);
 			}
 		}
@@ -69,7 +69,7 @@ namespace transport
 	std::shared_ptr<i2p::crypto::DHKeys> DHKeysPairSupplier::Acquire ()
 	{
 		{
-			std::unique_lock<std::mutex>  l(m_AcquiredMutex);
+			std::unique_lock<std::mutex>	l(m_AcquiredMutex);
 			if (!m_Queue.empty ())
 			{
 				auto pair = m_Queue.front ();
@@ -86,7 +86,7 @@ namespace transport
 
 	void DHKeysPairSupplier::Return (std::shared_ptr<i2p::crypto::DHKeys> pair)
 	{
-		std::unique_lock<std::mutex>  l(m_AcquiredMutex);
+		std::unique_lock<std::mutex>	l(m_AcquiredMutex);
 		m_Queue.push (pair);
 	}
 
@@ -105,7 +105,7 @@ namespace transport
 		Stop ();
 	}	
 
-	void Transports::Start ()
+	void Transports::Start (bool enableNTCP, bool enableSSU)
 	{
 		m_DHKeysPairSupplier.Start ();
 		m_IsRunning = true;
@@ -114,19 +114,36 @@ namespace transport
 		auto& addresses = context.GetRouterInfo ().GetAddresses ();
 		for (auto address : addresses)
 		{
-			if (!m_NTCPServer)
-			{	
+			if (!m_NTCPServer && enableNTCP)
+			{
 				m_NTCPServer = new NTCPServer ();
 				m_NTCPServer->Start ();
+				if (!(m_NTCPServer->IsBoundV6() || m_NTCPServer->IsBoundV4())) {
+					/** failed to bind to NTCP */
+					LogPrint(eLogError, "Transports: failed to bind to TCP");
+					m_NTCPServer->Stop();
+					delete m_NTCPServer;
+					m_NTCPServer = nullptr;
+				}
 			}	
 			
-			if (address->transportStyle == RouterInfo::eTransportSSU && address->host.is_v4 ())
+			if (address->transportStyle == RouterInfo::eTransportSSU)
 			{
-				if (!m_SSUServer)
-				{	
-					m_SSUServer = new SSUServer (address->port);
+				if (!m_SSUServer && enableSSU)
+				{
+					if (address->host.is_v4())
+						m_SSUServer = new SSUServer (address->port);
+					else
+						m_SSUServer = new SSUServer (address->host, address->port);
 					LogPrint (eLogInfo, "Transports: Start listening UDP port ", address->port);
-					m_SSUServer->Start ();	
+					try {
+						m_SSUServer->Start ();
+					} catch ( std::exception & ex ) {
+						LogPrint(eLogError, "Transports: Failed to bind to UDP port", address->port);
+						delete m_SSUServer;
+						m_SSUServer = nullptr;
+						continue;
+					}
 					DetectExternalIP ();
 				}
 				else
@@ -206,7 +223,7 @@ namespace transport
 
 	void Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr<i2p::I2NPMessage> msg)
 	{
-		SendMessages (ident, std::vector<std::shared_ptr<i2p::I2NPMessage> > {msg });                             
+		SendMessages (ident, std::vector<std::shared_ptr<i2p::I2NPMessage> > {msg });															
 	}	
 
 	void Transports::SendMessages (const i2p::data::IdentHash& ident, const std::vector<std::shared_ptr<i2p::I2NPMessage> >& msgs)
@@ -231,7 +248,7 @@ namespace transport
 			{
 				auto r = netdb.FindRouter (ident);
 				{
-					std::unique_lock<std::mutex>  l(m_PeersMutex);	
+					std::unique_lock<std::mutex>	l(m_PeersMutex);	
 					it = m_Peers.insert (std::pair<i2p::data::IdentHash, Peer>(ident, { 0, r, {},
 						i2p::util::GetSecondsSinceEpoch (), {} })).first;
 				}
@@ -288,7 +305,7 @@ namespace transport
 					}
 				}	
 				else
-					LogPrint (eLogWarning, "Transports: NTCP address is not present for ", i2p::data::GetIdentHashAbbreviation (ident), ", trying SSU");
+					LogPrint (eLogDebug, "Transports: NTCP address is not present for ", i2p::data::GetIdentHashAbbreviation (ident), ", trying SSU");
 			}
 			if (peer.numAttempts == 1)// SSU
 			{
@@ -320,7 +337,7 @@ namespace transport
 			}	
 			LogPrint (eLogError, "Transports: No NTCP or SSU addresses available");
 			peer.Done ();
-			std::unique_lock<std::mutex>  l(m_PeersMutex);	
+			std::unique_lock<std::mutex>	l(m_PeersMutex);	
 			m_Peers.erase (ident);
 			return false;
 		}	
@@ -352,7 +369,7 @@ namespace transport
 			else
 			{
 				LogPrint (eLogError, "Transports: RouterInfo not found, Failed to send messages");
-				std::unique_lock<std::mutex>  l(m_PeersMutex);	
+				std::unique_lock<std::mutex>	l(m_PeersMutex);	
 				m_Peers.erase (it);
 			}	
 		}	
@@ -396,7 +413,7 @@ namespace transport
 				}	
 			}
 			LogPrint (eLogError, "Transports: Unable to resolve NTCP address: ", ecode.message ());
-			std::unique_lock<std::mutex>  l(m_PeersMutex);		
+			std::unique_lock<std::mutex>	l(m_PeersMutex);		
 			m_Peers.erase (it1);
 		}
 	}
@@ -438,7 +455,7 @@ namespace transport
 				}	
 			}
 			LogPrint (eLogError, "Transports: Unable to resolve SSU address: ", ecode.message ());
-			std::unique_lock<std::mutex>  l(m_PeersMutex);	
+			std::unique_lock<std::mutex>	l(m_PeersMutex);	
 			m_Peers.erase (it1);
 		}
 	}
@@ -446,7 +463,7 @@ namespace transport
 	void Transports::CloseSession (std::shared_ptr<const i2p::data::RouterInfo> router)
 	{
 		if (!router) return;
-		m_Service.post (std::bind (&Transports::PostCloseSession, this, router));    
+		m_Service.post (std::bind (&Transports::PostCloseSession, this, router));		 
 	}	
 
 	void Transports::PostCloseSession (std::shared_ptr<const i2p::data::RouterInfo> router)
@@ -469,18 +486,21 @@ namespace transport
 	{
 		if (m_SSUServer)
 		{
+#ifndef MESHNET
 			i2p::context.SetStatus (eRouterStatusTesting);
+#endif
+
 			for (int i = 0; i < 5; i++)
 			{
 				auto router = i2p::data::netdb.GetRandomPeerTestRouter ();
-				if (router  && router->IsSSU (!context.SupportsV6 ()))
-					m_SSUServer->CreateSession (router, true);  // peer test	
+				if (router	&& router->IsSSU (!context.SupportsV6 ()))
+					m_SSUServer->CreateSession (router, true);	// peer test	
 				else
 				{
 					// if not peer test capable routers found pick any
 					router = i2p::data::netdb.GetRandomRouter ();
 					if (router && router->IsSSU ())
-						m_SSUServer->CreateSession (router);  	// no peer test
+						m_SSUServer->CreateSession (router);		// no peer test
 				}
 			}	
 		}
@@ -503,7 +523,7 @@ namespace transport
 						statusChanged = true;
 						i2p::context.SetStatus (eRouterStatusTesting); // first time only
 					}	
-					m_SSUServer->CreateSession (router, true);  // peer test	
+					m_SSUServer->CreateSession (router, true);	// peer test	
 				}	
 			}	
 		}
@@ -522,7 +542,7 @@ namespace transport
 	void Transports::PeerConnected (std::shared_ptr<TransportSession> session)
 	{
 		m_Service.post([session, this]()
-		{   
+		{		
 			auto remoteIdentity = session->GetRemoteIdentity (); 
 			if (!remoteIdentity) return;
 			auto ident = remoteIdentity->GetIdentHash ();
@@ -535,7 +555,7 @@ namespace transport
 					// check if first message is our DatabaseStore (publishing)
 					auto firstMsg = it->second.delayedMessages[0];
 					if (firstMsg && firstMsg->GetTypeID () == eI2NPDatabaseStore &&
-					    i2p::data::IdentHash(firstMsg->GetPayload () + DATABASE_STORE_KEY_OFFSET) == i2p::context.GetIdentHash ())
+							i2p::data::IdentHash(firstMsg->GetPayload () + DATABASE_STORE_KEY_OFFSET) == i2p::context.GetIdentHash ())
 						sendDatabaseStore = false; // we have it in the list already
 				}	
 				if (sendDatabaseStore)
@@ -547,7 +567,7 @@ namespace transport
 			else // incoming connection
 			{
 				session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); // send DatabaseStore
-				std::unique_lock<std::mutex>  l(m_PeersMutex);	
+				std::unique_lock<std::mutex>	l(m_PeersMutex);	
 				m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, { session }, i2p::util::GetSecondsSinceEpoch (), {} }));
 			}
 		});			
@@ -556,7 +576,7 @@ namespace transport
 	void Transports::PeerDisconnected (std::shared_ptr<TransportSession> session)
 	{
 		m_Service.post([session, this]()
-		{  
+		{	 
 			auto remoteIdentity = session->GetRemoteIdentity (); 
 			if (!remoteIdentity) return;
 			auto ident = remoteIdentity->GetIdentHash ();
@@ -570,7 +590,7 @@ namespace transport
 						ConnectToPeer (ident, it->second);
 					else
 					{
-						std::unique_lock<std::mutex>  l(m_PeersMutex);	
+						std::unique_lock<std::mutex>	l(m_PeersMutex);	
 						m_Peers.erase (it);
 					}
 				}
@@ -595,14 +615,20 @@ namespace transport
 				if (it->second.sessions.empty () && ts > it->second.creationTime + SESSION_CREATION_TIMEOUT)
 				{
 					LogPrint (eLogWarning, "Transports: Session to peer ", it->first.ToBase64 (), " has not been created in ", SESSION_CREATION_TIMEOUT, " seconds");
-					std::unique_lock<std::mutex>  l(m_PeersMutex);	
+					auto profile = i2p::data::GetRouterProfile(it->first);
+					if (profile)
+					{
+						profile->TunnelNonReplied();
+						profile->Save();
+					}
+					std::unique_lock<std::mutex>	l(m_PeersMutex);	
 					it = m_Peers.erase (it);
 				}
 				else
 					it++;
 			}
 			UpdateBandwidth (); // TODO: use separate timer(s) for it
-			if (i2p::context.GetStatus () == eRouterStatusTesting) // if still testing,  repeat peer test
+			if (i2p::context.GetStatus () == eRouterStatusTesting) // if still testing,	 repeat peer test
 				DetectExternalIP ();
 			m_PeerCleanupTimer.expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT));
 			m_PeerCleanupTimer.async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1));
@@ -617,6 +643,30 @@ namespace transport
 		std::advance (it, rand () % m_Peers.size ());	
 		return it != m_Peers.end () ? it->second.router : nullptr;
 	}
+  void Transports::RestrictRoutes(std::vector<std::string> families)
+  {
+    std::lock_guard<std::mutex> lock(m_FamilyMutex);
+    m_TrustedFamilies.clear();
+    for ( auto fam : families )
+      m_TrustedFamilies.push_back(fam);
+  }
+
+  bool Transports::RoutesRestricted() const {
+    std::lock_guard<std::mutex> lock(m_FamilyMutex);
+    return m_TrustedFamilies.size() > 0;
+  }
+
+  /** XXX: if routes are not restricted this dies */
+  std::shared_ptr<const i2p::data::RouterInfo> Transports::GetRestrictedPeer() const {
+    std::string fam;
+    {
+      std::lock_guard<std::mutex> lock(m_FamilyMutex);
+      // TODO: random family (?)
+      fam = m_TrustedFamilies[0];
+    }
+    boost::to_lower(fam);
+    return i2p::data::netdb.GetRandomRouterInFamily(fam);
+  }
 }
 }
 
diff --git a/Transports.h b/Transports.h
index 3bfe1f8b..9b231802 100644
--- a/Transports.h
+++ b/Transports.h
@@ -73,8 +73,11 @@ namespace transport
 			Transports ();
 			~Transports ();
 
-			void Start ();
+			void Start (bool enableNTCP=true, bool enableSSU=true);
 			void Stop ();
+
+			bool IsBoundNTCP() const { return m_NTCPServer != nullptr; }
+			bool IsBoundSSU() const { return m_SSUServer != nullptr; }
 			
 			boost::asio::io_service& GetService () { return m_Service; };
 			std::shared_ptr<i2p::crypto::DHKeys> GetNextDHKeysPair ();	
@@ -98,6 +101,13 @@ namespace transport
 			size_t GetNumPeers () const { return m_Peers.size (); };
 			std::shared_ptr<const i2p::data::RouterInfo> GetRandomPeer () const;
 
+    /** get a trusted first hop for restricted routes */
+    std::shared_ptr<const i2p::data::RouterInfo> GetRestrictedPeer() const;
+    /** do we want to use restricted routes? */
+    bool RoutesRestricted() const;  
+    /** restrict routes to use only these router families for first hops */
+    void RestrictRoutes(std::vector<std::string> families);
+    
 			void PeerTest ();
 			
 		private:
@@ -140,6 +150,10 @@ namespace transport
 			uint64_t m_LastInBandwidthUpdateBytes, m_LastOutBandwidthUpdateBytes;	
 			uint64_t m_LastBandwidthUpdateTime;		
 
+    /** which router families to trust for first hops */
+    std::vector<std::string> m_TrustedFamilies;
+    mutable std::mutex m_FamilyMutex;
+    
 		public:
 
 			// for HTTP only
diff --git a/Tunnel.cpp b/Tunnel.cpp
index 5da18542..fd971628 100644
--- a/Tunnel.cpp
+++ b/Tunnel.cpp
@@ -443,7 +443,7 @@ namespace tunnel
 				if (msg)
 				{	
 					uint32_t prevTunnelID = 0, tunnelID = 0;
-					std::shared_ptr<TunnelBase> prevTunnel; 
+					std::shared_ptr<TunnelBase> prevTunnel;
 					do
 					{
 						std::shared_ptr<TunnelBase> tunnel;
@@ -458,7 +458,7 @@ namespace tunnel
 									tunnel = prevTunnel;
 								else if (prevTunnel)
 									prevTunnel->FlushTunnelDataMsgs (); 
-						
+								
 								if (!tunnel)
 									tunnel = GetTunnel (tunnelID);
 								if (tunnel)
@@ -468,8 +468,9 @@ namespace tunnel
 									else // tunnel gateway assumed
 										HandleTunnelGatewayMsg (tunnel, msg);
 								}
-								else		
-									LogPrint (eLogWarning, "Tunnel: tunnel with id ", tunnelID, " not found");
+								else
+									LogPrint (eLogWarning, "Tunnel: tunnel not found, tunnelID=", tunnelID, " previousTunnelID=", prevTunnelID, " type=", (int)typeID);
+
 								break;
 							}	
 							case eI2NPVariableTunnelBuild:		
diff --git a/Tunnel.h b/Tunnel.h
index 5bc8b195..0d35b682 100644
--- a/Tunnel.h
+++ b/Tunnel.h
@@ -224,7 +224,7 @@ namespace tunnel
 			std::list<std::shared_ptr<TunnelPool>> m_Pools;
 			std::shared_ptr<TunnelPool> m_ExploratoryPool;
 			i2p::util::Queue<std::shared_ptr<I2NPMessage> > m_Queue;
-
+      
 			// some stats
 			int m_NumSuccesiveTunnelCreations, m_NumFailedTunnelCreations;
 
diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp
index 1bc8a937..e2a0843e 100644
--- a/TunnelEndpoint.cpp
+++ b/TunnelEndpoint.cpp
@@ -205,7 +205,7 @@ namespace tunnel
 			if (it->second.fragmentNum == msg.nextFragmentNum)
 			{
 				LogPrint (eLogWarning, "TunnelMessage: Out-of-sequence fragment ", (int)it->second.fragmentNum, " of message ", msgID, " found");
-				auto size = it->second.data->GetLength ();
+				size_t size = it->second.data->GetLength ();
 				if (msg.data->len + size > msg.data->maxLen)
 				{
 					LogPrint (eLogWarning, "TunnelMessage: Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough");
@@ -235,7 +235,7 @@ namespace tunnel
 			LogPrint (eLogInfo, "TunnelMessage: message expired");
 			return;
 		}	
-		auto typeID = msg.data->GetTypeID ();
+		uint8_t typeID = msg.data->GetTypeID ();
 		LogPrint (eLogDebug, "TunnelMessage: handle fragment of ", msg.data->GetLength (), " bytes, msg type ", (int)typeID);
 		// catch RI or reply with new list of routers	
 		if ((IsRouterInfoMsg (msg.data) || typeID == eI2NPDatabaseSearchReply) &&
diff --git a/TunnelGateway.cpp b/TunnelGateway.cpp
index 3383010b..d701e24b 100644
--- a/TunnelGateway.cpp
+++ b/TunnelGateway.cpp
@@ -49,7 +49,7 @@ namespace tunnel
 
 		// create fragments
 		std::shared_ptr<I2NPMessage> msg = block.data;
-		auto fullMsgLen = diLen + msg->GetLength () + 2; // delivery instructions + payload + 2 bytes length
+		size_t fullMsgLen = diLen + msg->GetLength () + 2; // delivery instructions + payload + 2 bytes length
 		if (fullMsgLen <= m_RemainingSize)
 		{
 			// message fits. First and last fragment
@@ -66,10 +66,10 @@ namespace tunnel
 		{
 			if (!messageCreated) // check if we should complete previous message
 			{	
-				auto numFollowOnFragments = fullMsgLen / TUNNEL_DATA_MAX_PAYLOAD_SIZE;
+				size_t numFollowOnFragments = fullMsgLen / TUNNEL_DATA_MAX_PAYLOAD_SIZE;
 				// length of bytes don't fit full tunnel message
 				// every follow-on fragment adds 7 bytes
-				auto nonFit = (fullMsgLen + numFollowOnFragments*7) % TUNNEL_DATA_MAX_PAYLOAD_SIZE; 
+				size_t nonFit = (fullMsgLen + numFollowOnFragments*7) % TUNNEL_DATA_MAX_PAYLOAD_SIZE; 
 				if (!nonFit || nonFit > m_RemainingSize)
 				{
 					CompleteCurrentTunnelDataMessage ();
diff --git a/TunnelPool.cpp b/TunnelPool.cpp
index 7024e1a1..515e0f5d 100644
--- a/TunnelPool.cpp
+++ b/TunnelPool.cpp
@@ -322,7 +322,7 @@ namespace tunnel
 			i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop);
 
 		if (!hop || hop->GetProfile ()->IsBad ())
-			hop = i2p::data::netdb.GetRandomRouter ();
+			hop = i2p::data::netdb.GetRandomRouter (prevHop);
 		return hop;	
 	}	
 
@@ -330,20 +330,17 @@ namespace tunnel
 	{
 		if (m_ExplicitPeers) return SelectExplicitPeers (peers, isInbound);
 		int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops;
-		if (numHops <= 0) return true; // peers is empty 
-		auto prevHop = i2p::context.GetSharedRouterInfo ();			
-		if (i2p::transport::transports.GetNumPeers () > 25)
+		if (numHops <= 0) return true;
+		auto prevHop	= i2p::context.GetSharedRouterInfo();
+		if(i2p::transport::transports.RoutesRestricted())
 		{
-			auto r = i2p::transport::transports.GetRandomPeer ();
-			if (r && !r->GetProfile ()->IsBad ())
-			{
-				prevHop = r;
-				peers.push_back (r->GetRouterIdentity ());
-				numHops--;
-			}
+			/** if routes are restricted prepend trusted first hop */
+			auto hop = i2p::transport::transports.GetRestrictedPeer();
+			if(!hop) return false;
+			peers.push_back(hop->GetRouterIdentity());
+			prevHop = hop;
 		}
-		
-		for (int i = 0; i < numHops; i++)
+		for(int i = 0; i < numHops; i++ )
 		{
 			auto hop = SelectNextHop (prevHop);
 			if (!hop)
@@ -353,7 +350,7 @@ namespace tunnel
 			}	
 			prevHop = hop;
 			peers.push_back (hop->GetRouterIdentity ());
-		}		
+		}
 		return true;
 	}	
 	
diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt
index 3f5f599f..edde2e06 100644
--- a/build/CMakeLists.txt
+++ b/build/CMakeLists.txt
@@ -12,6 +12,7 @@ option(WITH_STATIC    "Static build" OFF)
 option(WITH_UPNP "Include support for UPnP client" OFF)
 option(WITH_PCH "Use precompiled header" OFF)
 option(WITH_GUI "Include GUI (currently MS Windows only)" ON)
+option(WITH_MESHNET "Build for cjdns test network" OFF)
 
 # paths
 set ( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules" )
@@ -90,6 +91,10 @@ set (DAEMON_SRC
   "${CMAKE_SOURCE_DIR}/UPnP.cpp"
 )
 
+if (WITH_MESHNET)
+  add_definitions(-DMESHNET)
+endif ()
+
 if (WITH_UPNP)
   add_definitions(-DUSE_UPNP)
   if (NOT MSVC AND NOT MSYS)
@@ -296,6 +301,14 @@ link_directories(${CMAKE_CURRENT_BINARY_DIR}/zlib/lib ${ZLIB_ROOT}/lib)
 # load includes
 include_directories( SYSTEM ${Boost_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR} )
 
+
+# warn if for meshnet
+if (WITH_MESHNET)
+  message(STATUS "Building for testnet")
+  message(WARNING "This build will NOT work on mainline i2p")  
+endif()
+
+
 # show summary
 message(STATUS "---------------------------------------")
 message(STATUS "Build type         : ${CMAKE_BUILD_TYPE}")
@@ -311,6 +324,7 @@ message(STATUS "  BINARY           : ${WITH_BINARY}")
 message(STATUS "  STATIC BUILD     : ${WITH_STATIC}")
 message(STATUS "  UPnP             : ${WITH_UPNP}")
 message(STATUS "  PCH              : ${WITH_PCH}")
+message(STATUS "  MESHNET          : ${WITH_MESHNET}")
 message(STATUS "---------------------------------------")
 
 #Handle paths nicely
diff --git a/util.cpp b/util.cpp
index 5230f55f..909040c8 100644
--- a/util.cpp
+++ b/util.cpp
@@ -413,7 +413,52 @@ namespace net
         return GetMTUUnix(localAddress, fallback);
 #endif
         return fallback;
-    }	
+    }
+  
+	const boost::asio::ip::address GetInterfaceAddress(const std::string & ifname, bool ipv6)
+	{
+#ifdef WIN32
+		LogPrint(eLogError, "NetIface: cannot get address by interface name, not implemented on WIN32");
+		return boost::asio::ip::from_string("127.0.0.1");
+#else
+		int af = (ipv6 ? AF_INET6 : AF_INET);
+		ifaddrs * addrs = nullptr;
+		if(getifaddrs(&addrs) == 0)
+		{
+			// got ifaddrs
+			ifaddrs * cur = addrs;
+			while(cur)
+			{
+				std::string cur_ifname(cur->ifa_name);
+				if (cur_ifname == ifname && cur->ifa_addr && cur->ifa_addr->sa_family == af)
+				{
+					// match
+          char * addr = new char[INET6_ADDRSTRLEN];
+          bzero(addr, INET6_ADDRSTRLEN);
+          if(af == AF_INET)
+            inet_ntop(af, &((sockaddr_in *)cur->ifa_addr)->sin_addr, addr, INET6_ADDRSTRLEN);
+          else
+            inet_ntop(af, &((sockaddr_in6 *)cur->ifa_addr)->sin6_addr, addr, INET6_ADDRSTRLEN);
+          freeifaddrs(addrs);
+          std::string cur_ifaddr(addr);
+					return boost::asio::ip::address::from_string(cur_ifaddr);
+				}
+				cur = cur->ifa_next;
+			}
+		}
+		if(addrs) freeifaddrs(addrs);
+		std::string fallback;
+		if(ipv6) {
+			fallback = "::";
+			LogPrint(eLogWarning, "NetIface: cannot find ipv6 address for interface ", ifname);
+		} else {
+			fallback = "127.0.0.1";
+			LogPrint(eLogWarning, "NetIface: cannot find ipv4 address for interface ", ifname);
+		}
+		return boost::asio::ip::address::from_string(fallback);
+			
+#endif
+	}
 } 
 
 } // util
diff --git a/util.h b/util.h
index 9f797158..7c393e02 100644
--- a/util.h
+++ b/util.h
@@ -66,6 +66,7 @@ namespace util
 	namespace net
 	{
 		int GetMTU (const boost::asio::ip::address& localAddress);
+		const boost::asio::ip::address GetInterfaceAddress(const std::string & ifname, bool ipv6=false);
 	}
 }
 }
diff --git a/version.h b/version.h
index 46d170db..54fc295f 100644
--- a/version.h
+++ b/version.h
@@ -12,7 +12,12 @@
 #define I2PD_VERSION_PATCH 0
 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO)
 #define VERSION I2PD_VERSION
+
+#ifdef MESHNET
+#define I2PD_NET_ID 3
+#else
 #define I2PD_NET_ID 2
+#endif
 
 #define I2P_VERSION_MAJOR 0
 #define I2P_VERSION_MINOR 9