diff --git a/Makefile b/Makefile
index 460b16e4..6d56ec7d 100644
--- a/Makefile
+++ b/Makefile
@@ -30,12 +30,12 @@ ifneq (, $(findstring darwin, $(SYS)))
 	else
 		include Makefile.osx
 	endif
+else ifneq (, $(findstring linux, $(SYS))$(findstring gnu, $(SYS)))
+	DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp
+	include Makefile.linux
 else ifneq (, $(findstring freebsd, $(SYS))$(findstring openbsd, $(SYS)))
 	DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp
 	include Makefile.bsd
-else ifneq (, $(findstring linux, $(SYS)))
-	DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp
-	include Makefile.linux
 else ifneq (, $(findstring mingw, $(SYS))$(findstring cygwin, $(SYS)))
 	DAEMON_SRC += Win32/DaemonWin32.cpp Win32/Win32Service.cpp Win32/Win32App.cpp
 	include Makefile.mingw
diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt
index 4f875b33..fc9ca417 100644
--- a/build/CMakeLists.txt
+++ b/build/CMakeLists.txt
@@ -94,13 +94,17 @@ endif()
 
 add_library(libi2pd ${LIBI2PD_SRC})
 set_target_properties(libi2pd PROPERTIES PREFIX "")
-install(TARGETS libi2pd
-  EXPORT libi2pd
-  ARCHIVE DESTINATION lib
-  COMPONENT Libraries)
+
+if (WITH_LIBRARY)
+  install(TARGETS libi2pd
+    EXPORT libi2pd
+    ARCHIVE DESTINATION lib
+    LIBRARY DESTINATION lib
+    COMPONENT Libraries)
 # TODO Make libi2pd available to 3rd party projects via CMake as imported target
 # FIXME This pulls stdafx
 # install(EXPORT libi2pd DESTINATION ${CMAKE_INSTALL_LIBDIR})
+endif()
 
 set (CLIENT_SRC
   "${LIBI2PD_CLIENT_SRC_DIR}/AddressBook.cpp"
@@ -119,7 +123,17 @@ set (CLIENT_SRC
 if(WITH_WEBSOCKETS)
   list (APPEND CLIENT_SRC "${LIBI2PD_CLIENT_SRC_DIR}/Websocket.cpp")
 endif ()
-add_library(i2pdclient ${CLIENT_SRC})
+
+add_library(libi2pdclient ${CLIENT_SRC})
+set_target_properties(libi2pdclient PROPERTIES PREFIX "")
+
+if (WITH_LIBRARY)
+  install(TARGETS libi2pdclient
+    EXPORT libi2pdclient
+    ARCHIVE DESTINATION lib
+    LIBRARY DESTINATION lib
+    COMPONENT Libraries)
+endif()
 
 set(DAEMON_SRC_DIR ../daemon)
 
@@ -303,7 +317,7 @@ if (WITH_PCH)
       WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
       )
     target_compile_options(libi2pd PRIVATE /FIstdafx.h /Yustdafx.h /Zm155 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$<CONFIG>/stdafx.pch")
-    target_compile_options(i2pdclient PRIVATE /FIstdafx.h /Yustdafx.h /Zm155 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$<CONFIG>/stdafx.pch")
+    target_compile_options(libi2pdclient PRIVATE /FIstdafx.h /Yustdafx.h /Zm155 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$<CONFIG>/stdafx.pch")
   else()
     string(TOUPPER ${CMAKE_BUILD_TYPE} BTU)
     get_directory_property(DEFS DEFINITIONS)
@@ -312,12 +326,12 @@ if (WITH_PCH)
       COMMAND ${CMAKE_CXX_COMPILER} ${FLAGS} -c ${CMAKE_CURRENT_SOURCE_DIR}/../libi2pd/stdafx.h -o ${CMAKE_BINARY_DIR}/stdafx.h.gch
     )
     target_compile_options(libi2pd PRIVATE -include libi2pd/stdafx.h)
-    target_compile_options(i2pdclient PRIVATE -include libi2pd/stdafx.h)
+    target_compile_options(libi2pdclient PRIVATE -include libi2pd/stdafx.h)
   endif()
   target_link_libraries(libi2pd stdafx)
 endif()
 
-target_link_libraries(i2pdclient libi2pd)
+target_link_libraries(libi2pdclient libi2pd)
 
 find_package ( Boost COMPONENTS system filesystem program_options date_time REQUIRED )
 if(NOT DEFINED Boost_INCLUDE_DIRS)
@@ -450,7 +464,7 @@ if (WITH_BINARY)
   if (WITH_STATIC)
     set(DL_LIB ${CMAKE_DL_LIBS})
   endif()
-  target_link_libraries( "${PROJECT_NAME}" libi2pd i2pdclient ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${MINGW_EXTRA} ${DL_LIB} ${CMAKE_REQUIRED_LIBRARIES})
+  target_link_libraries( "${PROJECT_NAME}" libi2pd libi2pdclient ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${MINGW_EXTRA} ${DL_LIB} ${CMAKE_REQUIRED_LIBRARIES})
 
   install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Runtime)
   set (APPS "\${CMAKE_INSTALL_PREFIX}/bin/${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX}")
diff --git a/contrib/i2pd.service b/contrib/i2pd.service
index 5ed31d41..9af96c37 100644
--- a/contrib/i2pd.service
+++ b/contrib/i2pd.service
@@ -8,6 +8,8 @@ User=i2pd
 Group=i2pd
 RuntimeDirectory=i2pd
 RuntimeDirectoryMode=0700
+LogsDirectory=i2pd
+LogsDirectoryMode=0700
 Type=simple
 ExecStart=/usr/sbin/i2pd --conf=/etc/i2pd/i2pd.conf --tunconf=/etc/i2pd/tunnels.conf --pidfile=/var/run/i2pd/i2pd.pid --logfile=/var/log/i2pd/i2pd.log --daemon --service
 ExecReload=/bin/kill -HUP $MAINPID
diff --git a/daemon/I2PControl.cpp b/daemon/I2PControl.cpp
index 494ea026..fcff78cd 100644
--- a/daemon/I2PControl.cpp
+++ b/daemon/I2PControl.cpp
@@ -65,6 +65,7 @@ namespace client
 		m_MethodHandlers["RouterInfo"]     = &I2PControlService::RouterInfoHandler;
 		m_MethodHandlers["RouterManager"]  = &I2PControlService::RouterManagerHandler;
 		m_MethodHandlers["NetworkSetting"] = &I2PControlService::NetworkSettingHandler;
+		m_MethodHandlers["ClientServicesInfo"]     = &I2PControlService::ClientServicesInfoHandler;
 
 		// I2PControl
 		m_I2PControlHandlers["i2pcontrol.password"] = &I2PControlService::PasswordHandler;
@@ -92,6 +93,14 @@ namespace client
 		// NetworkSetting
 		m_NetworkSettingHandlers["i2p.router.net.bw.in"]  = &I2PControlService::InboundBandwidthLimit;
 		m_NetworkSettingHandlers["i2p.router.net.bw.out"] = &I2PControlService::OutboundBandwidthLimit;
+
+		// ClientServicesInfo
+		m_ClientServicesInfoHandlers["I2PTunnel"] = &I2PControlService::I2PTunnelInfoHandler;
+		m_ClientServicesInfoHandlers["HTTPProxy"] = &I2PControlService::HTTPProxyInfoHandler;
+		m_ClientServicesInfoHandlers["SOCKS"] = &I2PControlService::SOCKSInfoHandler;
+		m_ClientServicesInfoHandlers["SAM"] = &I2PControlService::SAMInfoHandler;
+		m_ClientServicesInfoHandlers["BOB"] = &I2PControlService::BOBInfoHandler;
+		m_ClientServicesInfoHandlers["I2CP"] = &I2PControlService::I2CPInfoHandler;
 	}
 
 	I2PControlService::~I2PControlService ()
@@ -289,6 +298,13 @@ namespace client
 		ss << "\"" << name << "\":" << std::fixed << std::setprecision(2) << value;
 	}
 
+	void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, const boost::property_tree::ptree& value) const
+	{
+		std::ostringstream buf;
+		boost::property_tree::write_json (buf, value, false);
+		ss << "\"" << name << "\":" << buf.str();
+	}
+
 	void I2PControlService::SendResponse (std::shared_ptr<ssl_socket> socket,
 		std::shared_ptr<I2PControlBuffer> buf, std::ostringstream& response, bool isHtml)
 	{
@@ -457,6 +473,7 @@ namespace client
 		InsertParam (results, "i2p.router.net.total.sent.bytes",     (double)i2p::transport::transports.GetTotalSentBytes ());
 	}
 
+
 // RouterManager
 
 	void I2PControlService::RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results)
@@ -586,5 +603,178 @@ namespace client
 		}
 		EVP_PKEY_free (pkey);
 	}
+
+// ClientServicesInfo
+
+	void I2PControlService::ClientServicesInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results)
+	{
+		for (auto it = params.begin (); it != params.end (); it++)
+		{
+			LogPrint (eLogDebug, "I2PControl: ClientServicesInfo request: ", it->first);
+			auto it1 = m_ClientServicesInfoHandlers.find (it->first);
+			if (it1 != m_ClientServicesInfoHandlers.end ())
+			{
+				if (it != params.begin ()) results << ",";
+				(this->*(it1->second))(results);
+			}
+			else
+				LogPrint (eLogError, "I2PControl: ClientServicesInfo unknown request ", it->first);
+		}
+	}
+
+	void I2PControlService::I2PTunnelInfoHandler (std::ostringstream& results)
+	{
+		boost::property_tree::ptree pt;
+		boost::property_tree::ptree client_tunnels, server_tunnels;
+
+		for (auto& it: i2p::client::context.GetClientTunnels ())
+		{
+			auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
+			boost::property_tree::ptree ct;
+			ct.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident));
+			client_tunnels.add_child(it.second->GetName (), ct);
+		}
+
+		auto& serverTunnels = i2p::client::context.GetServerTunnels ();
+		if (!serverTunnels.empty ()) {
+			for (auto& it: serverTunnels)
+			{
+				auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
+				boost::property_tree::ptree st;
+				st.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident));
+				st.put("port", it.second->GetLocalPort ());
+				server_tunnels.add_child(it.second->GetName (), st);
+			}
+		}
+
+		auto& clientForwards = i2p::client::context.GetClientForwards ();
+		if (!clientForwards.empty ())
+		{
+			for (auto& it: clientForwards)
+			{
+				auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
+				boost::property_tree::ptree ct;
+				ct.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident));
+				client_tunnels.add_child(it.second->GetName (), ct);
+			}
+		}
+
+		auto& serverForwards = i2p::client::context.GetServerForwards ();
+		if (!serverForwards.empty ())
+		{
+			for (auto& it: serverForwards)
+			{
+				auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
+				boost::property_tree::ptree st;
+				st.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident));
+				server_tunnels.add_child(it.second->GetName (), st);
+			}
+		}
+
+		pt.add_child("client", client_tunnels);
+		pt.add_child("server", server_tunnels);
+
+		InsertParam (results, "I2PTunnel", pt);
+	}
+
+	void I2PControlService::HTTPProxyInfoHandler (std::ostringstream& results)
+	{
+		boost::property_tree::ptree pt;
+
+		auto httpProxy = i2p::client::context.GetHttpProxy ();
+		if (httpProxy)
+		{
+			auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash();
+			pt.put("enabled", true);
+			pt.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident));
+		}
+		else
+			pt.put("enabled", false);
+
+		InsertParam (results, "HTTPProxy", pt);
+	}
+
+	void I2PControlService::SOCKSInfoHandler (std::ostringstream& results)
+	{
+		boost::property_tree::ptree pt;
+
+		auto socksProxy = i2p::client::context.GetSocksProxy ();
+		if (socksProxy)
+		{
+			auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash();
+			pt.put("enabled", true);
+			pt.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident));
+		}
+		else
+			pt.put("enabled", false);
+
+		InsertParam (results, "SOCKS", pt);
+	}
+
+	void I2PControlService::SAMInfoHandler (std::ostringstream& results)
+	{
+		boost::property_tree::ptree pt;
+		auto sam = i2p::client::context.GetSAMBridge ();
+		if (sam)
+		{
+			pt.put("enabled", true);
+			boost::property_tree::ptree sam_sessions;
+			for (auto& it: sam->GetSessions ())
+			{
+				boost::property_tree::ptree sam_session, sam_session_sockets;
+				auto& name = it.second->localDestination->GetNickname ();
+				auto& ident = it.second->localDestination->GetIdentHash();
+				sam_session.put("name", name);
+				sam_session.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident));
+
+				for (const auto& socket: it.second->ListSockets())
+				{
+					boost::property_tree::ptree stream;
+					stream.put("type", socket->GetSocketType ());
+					stream.put("peer", socket->GetSocket ().remote_endpoint());
+
+					sam_session_sockets.push_back(std::make_pair("", stream));
+				}
+				sam_session.add_child("sockets", sam_session_sockets);
+				sam_sessions.add_child(it.first, sam_session);
+			}
+
+			pt.add_child("sessions", sam_sessions);
+		}
+		else
+			pt.put("enabled", false);
+
+		InsertParam (results, "SAM", pt);
+	}
+
+	void I2PControlService::BOBInfoHandler (std::ostringstream& results)
+	{
+		boost::property_tree::ptree pt;
+		auto bob = i2p::client::context.GetBOBCommandChannel ();
+		if (bob)
+		{
+			/* TODO more info */
+			pt.put("enabled", true);
+		}
+		else
+			pt.put("enabled", false);
+
+		InsertParam (results, "BOB", pt);
+	}
+
+	void I2PControlService::I2CPInfoHandler (std::ostringstream& results)
+	{
+		boost::property_tree::ptree pt;
+		auto i2cp = i2p::client::context.GetI2CPServer ();
+		if (i2cp)
+		{
+			/* TODO more info */
+			pt.put("enabled", true);
+		}
+		else
+			pt.put("enabled", false);
+
+		InsertParam (results, "I2CP", pt);
+	}
 }
 }
diff --git a/daemon/I2PControl.h b/daemon/I2PControl.h
index a7ed1eab..3233ad12 100644
--- a/daemon/I2PControl.h
+++ b/daemon/I2PControl.h
@@ -57,6 +57,7 @@ namespace client
 			void InsertParam (std::ostringstream& ss, const std::string& name, int value) const;
 			void InsertParam (std::ostringstream& ss, const std::string& name, double value) const;
 			void InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value) const;
+			void InsertParam (std::ostringstream& ss, const std::string& name, const boost::property_tree::ptree& value) const;
 
 			// methods
 			typedef void (I2PControlService::*MethodHandler)(const boost::property_tree::ptree& params, std::ostringstream& results);
@@ -67,6 +68,7 @@ namespace client
 			void RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results);
 			void RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results);
 			void NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results);
+			void ClientServicesInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results);
 
 			// I2PControl
 			typedef void (I2PControlService::*I2PControlRequestHandler)(const std::string& value);
@@ -98,6 +100,15 @@ namespace client
 			void InboundBandwidthLimit  (const std::string& value, std::ostringstream& results);
 			void OutboundBandwidthLimit (const std::string& value, std::ostringstream& results);
 
+			// ClientServicesInfo
+			typedef void (I2PControlService::*ClientServicesInfoRequestHandler)(std::ostringstream& results);
+			void I2PTunnelInfoHandler (std::ostringstream& results);
+			void HTTPProxyInfoHandler (std::ostringstream& results);
+			void SOCKSInfoHandler (std::ostringstream& results);
+			void SAMInfoHandler (std::ostringstream& results);
+			void BOBInfoHandler (std::ostringstream& results);
+			void I2CPInfoHandler (std::ostringstream& results);
+
 		private:
 
 			std::string m_Password;
@@ -115,6 +126,7 @@ namespace client
 			std::map<std::string, RouterInfoRequestHandler> m_RouterInfoHandlers;
 			std::map<std::string, RouterManagerRequestHandler> m_RouterManagerHandlers;
 			std::map<std::string, NetworkSettingRequestHandler> m_NetworkSettingHandlers;
+			std::map<std::string, ClientServicesInfoRequestHandler> m_ClientServicesInfoHandlers;
 	};
 }
 }
diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp
index d7d5adf6..5ba3334d 100644
--- a/libi2pd/Crypto.cpp
+++ b/libi2pd/Crypto.cpp
@@ -373,7 +373,7 @@ namespace crypto
 	}
 
 // ECIES
-	void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx)
+	void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding)
 	{
 		BN_CTX_start (ctx);
 		BIGNUM * q = BN_CTX_get (ctx);
@@ -386,10 +386,19 @@ namespace crypto
 		EC_POINT_mul (curve, p, k, nullptr, nullptr, ctx);
 		BIGNUM * x = BN_CTX_get (ctx), * y = BN_CTX_get (ctx);
 		EC_POINT_get_affine_coordinates_GFp (curve, p, x, y, nullptr);
-		encrypted[0] = 0;
-		bn2buf (x, encrypted + 1, len);
-		bn2buf (y, encrypted + 1 + len, len);
-		RAND_bytes (encrypted + 1 + 2*len, 256 - 2*len);
+		if (zeroPadding)
+		{
+			encrypted[0] = 0;
+			bn2buf (x, encrypted + 1, len);
+			bn2buf (y, encrypted + 1 + len, len);
+			RAND_bytes (encrypted + 1 + 2*len, 256 - 2*len);
+		}	
+		else
+		{
+			bn2buf (x, encrypted, len);
+			bn2buf (y, encrypted + len, len);
+			RAND_bytes (encrypted + 2*len, 256 - 2*len);
+		}
 		// ecryption key and iv
 		EC_POINT_mul (curve, p, nullptr, key, k, ctx);
 		EC_POINT_get_affine_coordinates_GFp (curve, p, x, y, nullptr);
@@ -403,16 +412,21 @@ namespace crypto
 		memcpy (m+33, data, 222);
 		SHA256 (m+33, 222, m+1);
 		// encrypt
-		encrypted[257] = 0;
 		CBCEncryption encryption;
 		encryption.SetKey (shared);
 		encryption.SetIV (iv);
-		encryption.Encrypt (m, 256, encrypted + 258);
+		if (zeroPadding)
+		{
+			encrypted[257] = 0;
+			encryption.Encrypt (m, 256, encrypted + 258);
+		}
+		else
+			encryption.Encrypt (m, 256, encrypted + 256);
 		EC_POINT_free (p);
 		BN_CTX_end (ctx);
 	}
 
-	bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx)
+	bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding)
 	{
 		bool ret = true;
 		BN_CTX_start (ctx);
@@ -421,8 +435,16 @@ namespace crypto
 		int len = BN_num_bytes (q);
 		// point for shared secret
 		BIGNUM * x = BN_CTX_get (ctx), * y = BN_CTX_get (ctx);
-		BN_bin2bn (encrypted + 1, len, x);
-		BN_bin2bn (encrypted + 1 + len, len, y);
+		if (zeroPadding)
+		{
+			BN_bin2bn (encrypted + 1, len, x);
+			BN_bin2bn (encrypted + 1 + len, len, y);
+		}
+		else
+		{
+			BN_bin2bn (encrypted, len, x);
+			BN_bin2bn (encrypted + len, len, y);
+		}
 		auto p = EC_POINT_new (curve);
 		if (EC_POINT_set_affine_coordinates_GFp (curve, p, x, y, nullptr))
 		{
@@ -439,7 +461,10 @@ namespace crypto
 			CBCDecryption decryption;
 			decryption.SetKey (shared);
 			decryption.SetIV (iv);
-			decryption.Decrypt (encrypted + 258, 256, m);
+			if (zeroPadding)	
+				decryption.Decrypt (encrypted + 258, 256, m);
+			else
+				decryption.Decrypt (encrypted + 256, 256, m);	
 			// verify and copy
 			uint8_t hash[32];
 			SHA256 (m + 33, 222, hash);
diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h
index b833ff19..6e4ddb3d 100644
--- a/libi2pd/Crypto.h
+++ b/libi2pd/Crypto.h
@@ -54,8 +54,8 @@ namespace crypto
 	void GenerateElGamalKeyPair (uint8_t * priv, uint8_t * pub);
 
 	// ECIES
-	void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx); // 222 bytes data, 514 bytes encrypted
-	bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx);
+	void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding = false); // 222 bytes data, 514 bytes encrypted with zeropadding, 512 without
+	bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding = false);
 	void GenerateECIESKeyPair (const EC_GROUP * curve, BIGNUM *& priv, EC_POINT *& pub);
 
 	// HMAC
diff --git a/libi2pd/CryptoKey.cpp b/libi2pd/CryptoKey.cpp
index 4be230f7..711d4ce6 100644
--- a/libi2pd/CryptoKey.cpp
+++ b/libi2pd/CryptoKey.cpp
@@ -12,9 +12,9 @@ namespace crypto
 		memcpy (m_PublicKey, pub, 256);
 	}
 
-	void ElGamalEncryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx)
+	void ElGamalEncryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding)
 	{
-		ElGamalEncrypt (m_PublicKey, data, encrypted, ctx, true);
+		ElGamalEncrypt (m_PublicKey, data, encrypted, ctx, zeroPadding);
 	}
 
 	ElGamalDecryptor::ElGamalDecryptor (const uint8_t * priv)
@@ -22,9 +22,9 @@ namespace crypto
 		memcpy (m_PrivateKey, priv, 256);
 	}
 
-	bool ElGamalDecryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx)
+	bool ElGamalDecryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding)
 	{
-		return ElGamalDecrypt (m_PrivateKey, encrypted, data, ctx, true);
+		return ElGamalDecrypt (m_PrivateKey, encrypted, data, ctx, zeroPadding);
 	}
 
 	ECIESP256Encryptor::ECIESP256Encryptor (const uint8_t * pub)
@@ -44,10 +44,10 @@ namespace crypto
 		if (m_PublicKey) EC_POINT_free (m_PublicKey);
 	}
 
-	void ECIESP256Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx)
+	void ECIESP256Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding)
 	{
 		if (m_Curve && m_PublicKey)
-			ECIESEncrypt (m_Curve, m_PublicKey, data, encrypted, ctx);
+			ECIESEncrypt (m_Curve, m_PublicKey, data, encrypted, ctx, zeroPadding);
 	}
 
 	ECIESP256Decryptor::ECIESP256Decryptor (const uint8_t * priv)
@@ -62,10 +62,10 @@ namespace crypto
 		if (m_PrivateKey) BN_free (m_PrivateKey);
 	}
 
-	bool ECIESP256Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx)
+	bool ECIESP256Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding)
 	{
 		if (m_Curve && m_PrivateKey)
-			return ECIESDecrypt (m_Curve, m_PrivateKey, encrypted, data, ctx);
+			return ECIESDecrypt (m_Curve, m_PrivateKey, encrypted, data, ctx, zeroPadding);
 		return false;
 	}
 
@@ -104,10 +104,10 @@ namespace crypto
 		if (m_PublicKey) EC_POINT_free (m_PublicKey);
 	}
 
-	void ECIESGOSTR3410Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx)
+	void ECIESGOSTR3410Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding)
 	{
 		if (m_PublicKey)
-			ECIESEncrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PublicKey, data, encrypted, ctx);
+			ECIESEncrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PublicKey, data, encrypted, ctx, zeroPadding);
 	}
 
 	ECIESGOSTR3410Decryptor::ECIESGOSTR3410Decryptor (const uint8_t * priv)
@@ -120,10 +120,10 @@ namespace crypto
 		if (m_PrivateKey) BN_free (m_PrivateKey);
 	}
 
-	bool ECIESGOSTR3410Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx)
+	bool ECIESGOSTR3410Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding)
 	{
 		if (m_PrivateKey)
-			return ECIESDecrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PrivateKey, encrypted, data, ctx);
+			return ECIESDecrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PrivateKey, encrypted, data, ctx, zeroPadding);
 		return false;
 	}
 
diff --git a/libi2pd/CryptoKey.h b/libi2pd/CryptoKey.h
index ece86eb0..0dff7584 100644
--- a/libi2pd/CryptoKey.h
+++ b/libi2pd/CryptoKey.h
@@ -13,7 +13,7 @@ namespace crypto
 		public:
 
 			virtual ~CryptoKeyEncryptor () {};
-			virtual void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) = 0; // 222 bytes data, 512 bytes encrypted
+			virtual void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) = 0; // 222 bytes data, 512/514 bytes encrypted
 	};
 
 	class CryptoKeyDecryptor
@@ -21,7 +21,7 @@ namespace crypto
 		public:
 
 			virtual ~CryptoKeyDecryptor () {};
-			virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) = 0; // 512 bytes encrypted, 222 bytes data
+			virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) = 0; // 512/514 bytes encrypted, 222 bytes data
 	};
 
 // ElGamal
@@ -30,7 +30,7 @@ namespace crypto
 		public:
 
 			ElGamalEncryptor (const uint8_t * pub);
-			void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx);
+			void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding);
 
 		private:
 
@@ -42,7 +42,7 @@ namespace crypto
 		public:
 
 			ElGamalDecryptor (const uint8_t * priv);
-			bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx);
+			bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding);
 
 		private:
 
@@ -57,7 +57,7 @@ namespace crypto
 
 			ECIESP256Encryptor (const uint8_t * pub);
 			~ECIESP256Encryptor ();
-			void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx);
+			void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding);
 
 		private:
 
@@ -72,7 +72,7 @@ namespace crypto
 
 			ECIESP256Decryptor (const uint8_t * priv);
 			~ECIESP256Decryptor ();
-			bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx);
+			bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding);
 
 		private:
 
@@ -90,7 +90,7 @@ namespace crypto
 
 			ECIESGOSTR3410Encryptor (const uint8_t * pub);
 			~ECIESGOSTR3410Encryptor ();
-			void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx);
+			void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding);
 
 		private:
 
@@ -104,7 +104,7 @@ namespace crypto
 
 			ECIESGOSTR3410Decryptor (const uint8_t * priv);
 			~ECIESGOSTR3410Decryptor ();
-			bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx);
+			bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding);
 
 		private:
 
diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp
index 33eff029..b7c2ee32 100644
--- a/libi2pd/Destination.cpp
+++ b/libi2pd/Destination.cpp
@@ -169,6 +169,46 @@ namespace client
 			return false;
 	}
 
+	bool LeaseSetDestination::Reconfigure(std::map<std::string, std::string> params)
+	{
+		
+		auto itr = params.find("i2cp.dontPublishLeaseSet");
+		if (itr != params.end())
+		{
+			m_IsPublic = itr->second != "true";
+		}
+		
+		int inLen, outLen, inQuant, outQuant, numTags, minLatency, maxLatency;
+		std::map<std::string, int&> intOpts = {
+			{I2CP_PARAM_INBOUND_TUNNEL_LENGTH, inLen},
+			{I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, outLen},
+			{I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, inQuant},
+			{I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, outQuant},
+			{I2CP_PARAM_TAGS_TO_SEND, numTags},
+			{I2CP_PARAM_MIN_TUNNEL_LATENCY, minLatency},
+			{I2CP_PARAM_MAX_TUNNEL_LATENCY, maxLatency}
+		};
+
+		auto pool = GetTunnelPool();
+		inLen = pool->GetNumInboundHops();
+		outLen = pool->GetNumOutboundHops();
+		inQuant = pool->GetNumInboundTunnels();
+		outQuant = pool->GetNumOutboundTunnels();
+		minLatency = 0;
+		maxLatency = 0;
+		
+		for (auto & opt : intOpts)
+		{
+			itr = params.find(opt.first);
+			if(itr != params.end())
+			{
+				opt.second = std::stoi(itr->second);
+			}
+		}
+		pool->RequireLatency(minLatency, maxLatency);
+		return pool->Reconfigure(inLen, outLen, inQuant, outQuant);
+	}
+	
 	std::shared_ptr<const i2p::data::LeaseSet> LeaseSetDestination::FindLeaseSet (const i2p::data::IdentHash& ident)
 	{
 		std::shared_ptr<i2p::data::LeaseSet> remoteLS;
@@ -241,8 +281,12 @@ namespace client
 		i2p::garlic::GarlicDestination::SetLeaseSetUpdated ();
 		if (m_IsPublic)
 		{
-			m_PublishVerificationTimer.cancel ();
-			Publish ();
+			auto s = shared_from_this ();
+			m_Service.post ([s](void)
+			{
+				s->m_PublishVerificationTimer.cancel ();
+				s->Publish ();
+			});	
 		}
 	}
 
@@ -984,7 +1028,7 @@ namespace client
 	bool ClientDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const
 	{
 		if (m_Decryptor)
-			return m_Decryptor->Decrypt (encrypted, data, ctx);
+			return m_Decryptor->Decrypt (encrypted, data, ctx, true);
 		else
 			LogPrint (eLogError, "Destinations: decryptor is not set");
 		return false;
diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h
index 6f37e768..3f261bc9 100644
--- a/libi2pd/Destination.h
+++ b/libi2pd/Destination.h
@@ -96,6 +96,10 @@ namespace client
 
 			virtual bool Start ();
 			virtual bool Stop ();
+
+			/** i2cp reconfigure */
+			virtual bool Reconfigure(std::map<std::string, std::string> i2cpOpts);
+		
 			bool IsRunning () const { return m_IsRunning; };
 			boost::asio::io_service& GetService () { return m_Service; };
 			std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () { return m_Pool; };
diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp
index 9bb7dfd1..c91bfdb3 100644
--- a/libi2pd/I2NPProtocol.cpp
+++ b/libi2pd/I2NPProtocol.cpp
@@ -327,7 +327,7 @@ namespace i2p
 			{
 				LogPrint (eLogDebug, "I2NP: Build request record ", i, " is ours");
 				BN_CTX * ctx = BN_CTX_new ();
-				i2p::crypto::ElGamalDecrypt (i2p::context.GetPrivateKeys ().GetPrivateKey () , record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText, ctx);
+				i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText, ctx);
 				BN_CTX_free (ctx);
 				// replace record to reply
 				if (i2p::context.AcceptsTunnels () &&
diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp
index f2355930..8b6063fc 100644
--- a/libi2pd/LeaseSet.cpp
+++ b/libi2pd/LeaseSet.cpp
@@ -212,7 +212,7 @@ namespace data
 	{
 		auto encryptor = m_Identity->CreateEncryptor (m_EncryptionKey);
 		if (encryptor)
-			encryptor->Encrypt (data, encrypted, ctx);
+			encryptor->Encrypt (data, encrypted, ctx, true);
 	}
 
 	LocalLeaseSet::LocalLeaseSet (std::shared_ptr<const IdentityEx> identity, const uint8_t * encryptionPublicKey, std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels):
diff --git a/libi2pd/NTCPSession.cpp b/libi2pd/NTCPSession.cpp
index de4306e6..a297ad6a 100644
--- a/libi2pd/NTCPSession.cpp
+++ b/libi2pd/NTCPSession.cpp
@@ -184,7 +184,7 @@ namespace transport
 			}
 			// TODO: check for number of pending keys
 			auto work = new NTCPWork{shared_from_this()};
-			m_Server.Work(work->session, [work]() -> std::function<void(void)> {
+			m_Server.Work(work->session, [work, this]() -> std::function<void(void)> {
 					if (!work->session->m_DHKeysPair)
 						work->session->m_DHKeysPair = transports.GetNextDHKeysPair ();
 					work->session->CreateAESKey (work->session->m_Establisher->phase1.pubKey);
@@ -250,7 +250,7 @@ namespace transport
 		else
 		{
 			auto work = new NTCPWork{shared_from_this()};
-			m_Server.Work(work->session, [work]() -> std::function<void(void)> {
+			m_Server.Work(work->session, [work, this]() -> std::function<void(void)> {
 				work->session->CreateAESKey (work->session->m_Establisher->phase2.pubKey);
 				return std::bind(&NTCPSession::HandlePhase2, work->session, work);
 			});
diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp
index 38fe3224..a82ace85 100644
--- a/libi2pd/RouterContext.cpp
+++ b/libi2pd/RouterContext.cpp
@@ -34,11 +34,7 @@ namespace i2p
 
 	void RouterContext::CreateNewRouter ()
 	{
-#if defined(__x86_64__) || defined(__i386__) || defined(_MSC_VER)
 		m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519);
-#else
-		m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_DSA_SHA1);
-#endif
 		SaveKeys ();
 		NewRouterInfo ();
 	}
@@ -482,6 +478,11 @@ namespace i2p
 
 	bool RouterContext::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const
 	{
-		return m_Decryptor ? m_Decryptor->Decrypt (encrypted, data, ctx) : false;
+		return m_Decryptor ? m_Decryptor->Decrypt (encrypted, data, ctx, true) : false;
+	}
+
+	bool RouterContext::DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const
+	{
+		return m_Decryptor ? m_Decryptor->Decrypt (encrypted, data, ctx, false) : false;
 	}
 }
diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h
index ef23af25..4bd324f5 100644
--- a/libi2pd/RouterContext.h
+++ b/libi2pd/RouterContext.h
@@ -61,6 +61,7 @@ namespace i2p
 			void SetError (RouterError error) { m_Status = eRouterStatusError; m_Error = error; };
 			int GetNetID () const { return m_NetID; };
 			void SetNetID (int netID) { m_NetID = netID; };
+			bool DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const;
 
 			void UpdatePort (int port); // called from Daemon
 			void UpdateAddress (const boost::asio::ip::address& host);	// called from SSU or Daemon
diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp
index e9c2a384..e3f4d2d4 100644
--- a/libi2pd/RouterInfo.cpp
+++ b/libi2pd/RouterInfo.cpp
@@ -840,7 +840,7 @@ namespace data
 	{
 		auto encryptor = m_RouterIdentity->CreateEncryptor (nullptr);
 		if (encryptor)
-			encryptor->Encrypt (data, encrypted, ctx);
+			encryptor->Encrypt (data, encrypted, ctx, true);
 	}
 }
 }
diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp
index e2c12b83..c7e1b1b4 100644
--- a/libi2pd/Tunnel.cpp
+++ b/libi2pd/Tunnel.cpp
@@ -670,10 +670,13 @@ namespace tunnel
 					{
 						if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
 						{
-							tunnel->SetIsRecreated ();
 							auto pool = tunnel->GetTunnelPool ();
-							if (pool)
+							// let it die if the tunnel pool has been reconfigured and this is old
+							if (pool && tunnel->GetNumHops() == pool->GetNumOutboundHops())
+							{
+								tunnel->SetIsRecreated ();
 								pool->RecreateOutboundTunnel (tunnel);
+							}
 						}
 						if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
 							tunnel->SetState (eTunnelStateExpiring);
@@ -721,10 +724,13 @@ namespace tunnel
 					{
 						if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
 						{
-							tunnel->SetIsRecreated ();
 							auto pool = tunnel->GetTunnelPool ();
-							if (pool)
+							// let it die if the tunnel pool was reconfigured and has different number of hops
+							if (pool && tunnel->GetNumHops() == pool->GetNumInboundHops())
+							{
+								tunnel->SetIsRecreated ();
 								pool->RecreateInboundTunnel (tunnel);
+							}
 						}
 
 						if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h
index 38beccaa..3244faad 100644
--- a/libi2pd/Tunnel.h
+++ b/libi2pd/Tunnel.h
@@ -105,6 +105,7 @@ namespace tunnel
 			bool IsFailed () const { return m_State == eTunnelStateFailed; };
 			bool IsRecreated () const { return m_IsRecreated; };
 			void SetIsRecreated () { m_IsRecreated = true; };
+			int GetNumHops () const { return m_Hops.size (); };
 			virtual bool IsInbound() const = 0;
 
 			std::shared_ptr<TunnelPool> GetTunnelPool () const { return m_Pool; };
diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h
index 7267fc30..48e66f2e 100644
--- a/libi2pd/TunnelConfig.h
+++ b/libi2pd/TunnelConfig.h
@@ -5,7 +5,6 @@
 #include <sstream>
 #include <vector>
 #include <memory>
-#include "Crypto.h"
 #include "Identity.h"
 #include "RouterContext.h"
 #include "Timestamp.h"
@@ -35,6 +34,7 @@ namespace tunnel
 			RAND_bytes (replyKey, 32);
 			RAND_bytes (replyIV, 16);
 			RAND_bytes ((uint8_t *)&tunnelID, 4);
+			if (!tunnelID) tunnelID = 1; // tunnelID can't be zero
 			isGateway = true;
 			isEndpoint = true;
 			ident = r;
@@ -50,6 +50,7 @@ namespace tunnel
 			nextIdent = ident;
 			isEndpoint = false;
 			RAND_bytes ((uint8_t *)&nextTunnelID, 4);
+			if (!nextTunnelID) nextTunnelID = 1; // tunnelID can't be zero
 		}
 
 		void SetReplyHop (uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent)
@@ -101,7 +102,9 @@ namespace tunnel
 			htobe32buf (clearText + BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetHoursSinceEpoch ());
 			htobe32buf (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID);
 			RAND_bytes (clearText + BUILD_REQUEST_RECORD_PADDING_OFFSET, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - BUILD_REQUEST_RECORD_PADDING_OFFSET);
-			i2p::crypto::ElGamalEncrypt (ident->GetEncryptionPublicKey (), clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx);
+			auto encryptor = ident->CreateEncryptor (nullptr);
+			if (encryptor)
+				encryptor->Encrypt (clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx, false);
 			memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16);
 		}
 	};
diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp
index 52736fa0..4f740a09 100644
--- a/libi2pd/TunnelPool.cpp
+++ b/libi2pd/TunnelPool.cpp
@@ -69,6 +69,18 @@ namespace tunnel
 		m_Tests.clear ();
 	}
 
+	bool TunnelPool::Reconfigure(int inHops, int outHops, int inQuant, int outQuant)	{
+		if( inHops >= 0 && outHops >= 0 && inQuant > 0 && outQuant > 0)
+		{
+			m_NumInboundHops = inHops;
+			m_NumOutboundHops = outHops;
+			m_NumInboundTunnels = inQuant;
+			m_NumOutboundTunnels = outQuant;
+			return true;
+		}
+		return false;
+	}
+	
 	void TunnelPool::TunnelCreated (std::shared_ptr<InboundTunnel> createdTunnel)
 	{
 		if (!m_IsActive) return;
@@ -479,11 +491,17 @@ namespace tunnel
 			outboundTunnel = tunnels.GetNextOutboundTunnel ();
 		LogPrint (eLogDebug, "Tunnels: Re-creating destination inbound tunnel...");
 		std::shared_ptr<TunnelConfig> config;
-		if (m_NumInboundHops > 0) config = std::make_shared<TunnelConfig>(tunnel->GetPeers ());
-		auto newTunnel = tunnels.CreateInboundTunnel (config, outboundTunnel);
-		newTunnel->SetTunnelPool (shared_from_this());
-		if (newTunnel->IsEstablished ()) // zero hops
-			TunnelCreated (newTunnel);
+		if (m_NumInboundHops > 0 && tunnel->GetPeers().size())
+		{
+			config = std::make_shared<TunnelConfig>(tunnel->GetPeers ());
+		}
+		if (m_NumInboundHops == 0 || config)
+		{
+			auto newTunnel = tunnels.CreateInboundTunnel (config, outboundTunnel);
+			newTunnel->SetTunnelPool (shared_from_this());
+			if (newTunnel->IsEstablished ()) // zero hops
+				TunnelCreated (newTunnel);
+		}
 	}
 
 	void TunnelPool::CreateOutboundTunnel ()
@@ -521,12 +539,17 @@ namespace tunnel
 		{
 			LogPrint (eLogDebug, "Tunnels: Re-creating destination outbound tunnel...");
 			std::shared_ptr<TunnelConfig> config;
-			if (m_NumOutboundHops > 0)
+			if (m_NumOutboundHops > 0 && tunnel->GetPeers().size())
+			{
 				config = std::make_shared<TunnelConfig>(tunnel->GetPeers (), inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ());
-			auto newTunnel = tunnels.CreateOutboundTunnel (config);
-			newTunnel->SetTunnelPool (shared_from_this ());
-			if (newTunnel->IsEstablished ()) // zero hops
-				TunnelCreated (newTunnel);
+			}
+			if(m_NumOutboundHops == 0 || config)
+			{
+				auto newTunnel = tunnels.CreateOutboundTunnel (config);
+				newTunnel->SetTunnelPool (shared_from_this ());
+				if (newTunnel->IsEstablished ()) // zero hops
+					TunnelCreated (newTunnel);
+			}
 		}
 		else
 			LogPrint (eLogDebug, "Tunnels: Can't re-create outbound tunnel, no inbound tunnels found");
diff --git a/libi2pd/TunnelPool.h b/libi2pd/TunnelPool.h
index 07c3024e..fc46930c 100644
--- a/libi2pd/TunnelPool.h
+++ b/libi2pd/TunnelPool.h
@@ -78,7 +78,12 @@ namespace tunnel
 
 			int GetNumInboundTunnels () const { return m_NumInboundTunnels; };
 			int GetNumOutboundTunnels () const { return m_NumOutboundTunnels; };
+			int GetNumInboundHops() const { return m_NumInboundHops; };
+			int GetNumOutboundHops() const { return m_NumOutboundHops; };
 
+			/** i2cp reconfigure */
+			bool Reconfigure(int inboundHops, int outboundHops, int inboundQuant, int outboundQuant);
+    
 			void SetCustomPeerSelector(ITunnelPeerSelector * selector);
 			void UnsetCustomPeerSelector();
 			bool HasCustomPeerSelector();
diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp
index 371456ee..b08fded1 100644
--- a/libi2pd_client/I2CP.cpp
+++ b/libi2pd_client/I2CP.cpp
@@ -37,7 +37,7 @@ namespace client
 	bool I2CPDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const
 	{
 		if (m_Decryptor)
-			return m_Decryptor->Decrypt (encrypted, data, ctx);
+			return m_Decryptor->Decrypt (encrypted, data, ctx, true);
 		else
 			LogPrint (eLogError, "I2CP: decryptor is not set");
 		return false;
@@ -416,9 +416,60 @@ namespace client
 
 	void I2CPSession::ReconfigureSessionMessageHandler (const uint8_t * buf, size_t len)
 	{
-		// TODO: implement actual reconfiguration
-		SendSessionStatusMessage (2); // updated
-	}
+		uint8_t status = 3; // rejected
+		if(len > sizeof(uint16_t))
+		{
+			uint16_t sessionID = bufbe16toh(buf);
+			if(sessionID == m_SessionID)
+			{
+				buf += sizeof(uint16_t);
+				const uint8_t * body = buf;
+				i2p::data::IdentityEx ident;
+				if(ident.FromBuffer(buf, len - sizeof(uint16_t)))
+				{
+					if (ident == *m_Destination->GetIdentity())
+					{
+						size_t identsz = ident.GetFullLen();
+						buf += identsz;
+						uint16_t optssize = bufbe16toh(buf);
+						if (optssize <= len - sizeof(uint16_t) - sizeof(uint64_t) - identsz - ident.GetSignatureLen() - sizeof(uint16_t))
+						{
+							buf += sizeof(uint16_t);
+							std::map<std::string, std::string> opts;
+							ExtractMapping(buf, optssize, opts);
+							buf += optssize;
+							//uint64_t date = bufbe64toh(buf);
+							buf += sizeof(uint64_t);
+							const uint8_t * sig = buf;
+							if(ident.Verify(body, len - sizeof(uint16_t) - ident.GetSignatureLen(), sig))
+							{
+								if(m_Destination->Reconfigure(opts))
+								{
+									LogPrint(eLogInfo, "I2CP: reconfigured destination");
+									status = 2; // updated
+								}
+								else
+									LogPrint(eLogWarning, "I2CP: failed to reconfigure destination");
+							}
+							else
+								LogPrint(eLogError, "I2CP: invalid reconfigure message signature");
+						}
+						else
+							LogPrint(eLogError, "I2CP: mapping size missmatch");
+					}
+					else
+						LogPrint(eLogError, "I2CP: destination missmatch");
+				}
+				else
+					LogPrint(eLogError, "I2CP: malfromed destination");
+			}
+			else
+				LogPrint(eLogError, "I2CP: session missmatch");
+		}
+		else
+			LogPrint(eLogError, "I2CP: short message");
+		SendSessionStatusMessage (status); 
+	}	
 
 	void I2CPSession::SendSessionStatusMessage (uint8_t status)
 	{
diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp
index 1f4d04db..05943981 100644
--- a/libi2pd_client/SAM.cpp
+++ b/libi2pd_client/SAM.cpp
@@ -107,6 +107,21 @@ namespace client
 				std::placeholders::_1, std::placeholders::_2));
 	}
 
+	static bool SAMVersionAcceptable(const std::string & ver)
+	{
+		return ver == "3.0" || ver == "3.1";
+	}
+
+	static bool SAMVersionTooLow(const std::string & ver)
+	{
+		return ver.size() && ver[0] < '3';
+	}
+
+	static bool SAMVersionTooHigh(const std::string & ver)
+	{
+		return ver.size() && ver > "3.1";
+	}
+
 	void SAMSocket::HandleHandshakeReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
 	{
 		if (ecode)
@@ -132,19 +147,37 @@ namespace client
 
 			if (!strcmp (m_Buffer, SAM_HANDSHAKE))
 			{
-				std::string version("3.0");
+				std::string maxver("3.1");
+				std::string minver("3.0");
 				// try to find MIN and MAX, 3.0 if not found
 				if (separator)
 				{
 					separator++;
 					std::map<std::string, std::string> params;
 					ExtractParams (separator, params);
-					//auto it = params.find (SAM_PARAM_MAX);
-					// TODO: check MIN as well
-					//if (it != params.end ())
-					//	version = it->second;
+					auto it = params.find (SAM_PARAM_MAX);
+					if (it != params.end ())
+						maxver = it->second;
+					it = params.find(SAM_PARAM_MIN);
+					if (it != params.end ())
+						minver = it->second;
 				}
-				if (version[0] == '3') // we support v3 (3.0 and 3.1) only
+				// version negotiation
+				std::string version;
+				if (SAMVersionAcceptable(maxver))
+				{
+					version = maxver;
+				}
+				else if (SAMVersionAcceptable(minver))
+				{
+					version = minver;
+				}
+				else if (SAMVersionTooLow(minver) && SAMVersionTooHigh(maxver))
+				{
+					version = "3.0";
+				}
+
+				if (SAMVersionAcceptable(version))
 				{
 #ifdef _MSC_VER
 					size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_HANDSHAKE_REPLY, version.c_str ());
@@ -156,7 +189,7 @@ namespace client
 						std::placeholders::_1, std::placeholders::_2));
 				}
 				else
-					SendMessageReply (SAM_HANDSHAKE_I2P_ERROR, strlen (SAM_HANDSHAKE_I2P_ERROR), true);
+					SendMessageReply (SAM_HANDSHAKE_NOVERSION, strlen (SAM_HANDSHAKE_NOVERSION), true);
 			}
 			else
 			{
diff --git a/libi2pd_client/SAM.h b/libi2pd_client/SAM.h
index 5f0ee69b..6ecd14a4 100644
--- a/libi2pd_client/SAM.h
+++ b/libi2pd_client/SAM.h
@@ -23,6 +23,7 @@ namespace client
 	const int SAM_SESSION_READINESS_CHECK_INTERVAL = 20; // in seconds
 	const char SAM_HANDSHAKE[] = "HELLO VERSION";
 	const char SAM_HANDSHAKE_REPLY[] = "HELLO REPLY RESULT=OK VERSION=%s\n";
+	const char SAM_HANDSHAKE_NOVERSION[] = "HELLO REPLY RESULT=NOVERSION\n";
 	const char SAM_HANDSHAKE_I2P_ERROR[] = "HELLO REPLY RESULT=I2P_ERROR\n";
 	const char SAM_SESSION_CREATE[] = "SESSION CREATE";
 	const char SAM_SESSION_CREATE_REPLY_OK[] = "SESSION STATUS RESULT=OK DESTINATION=%s\n";