Browse Source

Merge pull request #271 from EinMByte/master

Benchmarking, webui and windows build
pull/273/head
EinMByte 9 years ago
parent
commit
af66f335c9
  1. 2
      .gitignore
  2. 45
      CMakeLists.txt
  3. 12
      README.md
  4. 14
      benchmark/CMakeLists.txt
  5. 83
      benchmark/main.cpp
  6. 6
      client/ClientContext.cpp
  7. 2
      client/ClientContext.h
  8. 15
      client/Daemon.cpp
  9. 2
      client/DaemonWin32.cpp
  10. 1130
      client/HTTPServer.cpp
  11. 141
      client/HTTPServer.h
  12. 2
      client/Win32Service.cpp
  13. 12
      client/i2p.cpp
  14. 230
      client/i2pcontrol/I2PControl.cpp
  15. 132
      client/i2pcontrol/I2PControl.h
  16. 2
      client/i2pcontrol/I2PControlServer.cpp
  17. 2
      client/i2pcontrol/I2PControlServer.h
  18. 31
      contrib/certificates/router/orignal_at_mail.i2p.crt
  19. 1
      core/CMakeLists.txt
  20. 246
      core/util/HTTP.cpp
  21. 104
      core/util/HTTP.h
  22. 74
      core/util/util.cpp
  23. 21
      core/util/util.h
  24. 29
      doc/BUILDING.md
  25. 11
      doc/BUILD_NOTES.md
  26. 1
      doc/COMMANDLINE.md
  27. 113
      tests/Utility.cpp
  28. 24
      webui/404.html
  29. 76
      webui/css/main.css
  30. 6
      webui/footer.html
  31. 27
      webui/help.html
  32. 101
      webui/index.html
  33. 102
      webui/javascript/I2PControl.js
  34. 20
      webui/menu.html
  35. 59
      webui/netdb.html
  36. 87
      webui/tunnels.html

2
.gitignore vendored

@ -13,8 +13,10 @@ build/CMakeFiles/*
build/tests build/tests
build/client build/client
build/core build/core
build/benchmark
build/i2pd build/i2pd
build/i2pd-tests build/i2pd-tests
build/i2pd-benchmark
*.cmake *.cmake
*.a *.a
*.o *.o

45
CMakeLists.txt

@ -9,6 +9,9 @@ option(WITH_BINARY "Build binary" ON)
option(WITH_STATIC "Static build" OFF) option(WITH_STATIC "Static build" OFF)
option(WITH_UPNP "Include support for UPnP client" OFF) option(WITH_UPNP "Include support for UPnP client" OFF)
option(WITH_TESTS "Build unit tests" OFF) option(WITH_TESTS "Build unit tests" OFF)
option(WITH_BENCHMARK "Build benchmarking code" OFF)
option(WITH_OPTIMIZE "Optimization flags" OFF)
option(I2PD_DATA_PATH "The path to the i2pd data folder")
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/build/cmake_modules") set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/build/cmake_modules")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
@ -80,6 +83,9 @@ if(WITH_AESNI)
add_definitions( "-maes -DAESNI") add_definitions( "-maes -DAESNI")
endif() endif()
if(WITH_OPTIMIZE AND (NOT MSVC))
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
endif()
# Libraries # Libraries
# TODO: once CMake 3.1+ becomes mainstream, see e.g. # TODO: once CMake 3.1+ becomes mainstream, see e.g.
@ -93,17 +99,8 @@ endif()
if(WITH_STATIC) if(WITH_STATIC)
set(Boost_USE_STATIC_LIBS ON) set(Boost_USE_STATIC_LIBS ON)
set(Boost_USE_STATIC_RUNTIME ON) set(Boost_USE_STATIC_RUNTIME OFF)
if(WIN32) if(NOT WIN32)
# http://www.cmake.org/Wiki/CMake_FAQ#Dynamic_Replace
# Note that you might need to rebuild Crypto++
foreach(flag_var CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
if(${flag_var} MATCHES "/MD")
string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
endif(${flag_var} MATCHES "/MD")
endforeach(flag_var)
else()
set(CMAKE_FIND_LIBRARY_SUFFIXES .a) set(CMAKE_FIND_LIBRARY_SUFFIXES .a)
endif() endif()
@ -120,10 +117,10 @@ elseif(NOT WIN32)
# No need in -fPIC overhead for binary if not interested in library # No need in -fPIC overhead for binary if not interested in library
# HINT: revert c266cff CMakeLists.txt: compilation speed up # HINT: revert c266cff CMakeLists.txt: compilation speed up
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
else() # Not a static build
add_definitions(-DBOOST_ALL_DYN_LINK)
endif() endif()
add_definitions(-DBOOST_ALL_DYN_LINK)
find_package( find_package(
Boost COMPONENTS Boost COMPONENTS
system filesystem regex program_options date_time thread chrono REQUIRED system filesystem regex program_options date_time thread chrono REQUIRED
@ -148,6 +145,18 @@ include_directories(
"core/" "core/"
) )
if(I2PD_DATA_PATH)
set(I2PD_DATA_DIR ${I2PD_DATA_PATH})
# Using custom path, make sure the code knows about this
add_definitions(-DI2PD_CUSTOM_DATA_PATH="${I2PD_DATA_PATH}")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
set(I2PD_DATA_DIR "$ENV{APPDATA}\\i2pd")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
set(I2PD_DATA_DIR "$ENV{HOME}/Library/Application Support/i2pd")
else()
set(I2PD_DATA_DIR "$ENV{HOME}/.i2pd")
endif()
# Show summary # Show summary
message(STATUS "---------------------------------------") message(STATUS "---------------------------------------")
message(STATUS "Build type : ${CMAKE_BUILD_TYPE}") message(STATUS "Build type : ${CMAKE_BUILD_TYPE}")
@ -155,6 +164,7 @@ message(STATUS "Compiler vendor : ${CMAKE_CXX_COMPILER_ID}")
message(STATUS "Compiler version : ${CMAKE_CXX_COMPILER_VERSION}") message(STATUS "Compiler version : ${CMAKE_CXX_COMPILER_VERSION}")
message(STATUS "Compiler path : ${CMAKE_CXX_COMPILER}") message(STATUS "Compiler path : ${CMAKE_CXX_COMPILER}")
message(STATUS "Install prefix: : ${CMAKE_INSTALL_PREFIX}") message(STATUS "Install prefix: : ${CMAKE_INSTALL_PREFIX}")
message(STATUS "I2PD data directory: ${I2PD_DATA_DIR}")
message(STATUS "Options:") message(STATUS "Options:")
message(STATUS " AESNI : ${WITH_AESNI}") message(STATUS " AESNI : ${WITH_AESNI}")
message(STATUS " HARDENING : ${WITH_HARDENING}") message(STATUS " HARDENING : ${WITH_HARDENING}")
@ -163,6 +173,8 @@ message(STATUS " BINARY : ${WITH_BINARY}")
message(STATUS " STATIC BUILD : ${WITH_STATIC}") message(STATUS " STATIC BUILD : ${WITH_STATIC}")
message(STATUS " UPnP : ${WITH_UPNP}") message(STATUS " UPnP : ${WITH_UPNP}")
message(STATUS " TESTS : ${WITH_TESTS}") message(STATUS " TESTS : ${WITH_TESTS}")
message(STATUS " BENCHMARKING : ${WITH_BENCHMARK}")
message(STATUS " OPTIMIZATION : ${WITH_OPTIMIZE}")
message(STATUS "---------------------------------------") message(STATUS "---------------------------------------")
# Handle paths nicely # Handle paths nicely
@ -171,6 +183,13 @@ include(GNUInstallDirs)
set(CORE_NAME "${PROJECT_NAME}-core") set(CORE_NAME "${PROJECT_NAME}-core")
set(CLIENT_NAME "${PROJECT_NAME}-client") set(CLIENT_NAME "${PROJECT_NAME}-client")
set(TESTS_NAME "${PROJECT_NAME}-tests") set(TESTS_NAME "${PROJECT_NAME}-tests")
set(BENCHMARK_NAME "${PROJECT_NAME}-benchmark")
add_subdirectory(core) add_subdirectory(core)
add_subdirectory(client) add_subdirectory(client)
add_subdirectory(tests) add_subdirectory(tests)
add_subdirectory(benchmark)
if(WITH_BINARY)
file(MAKE_DIRECTORY "${I2PD_DATA_DIR}/webui")
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/webui" DESTINATION "${I2PD_DATA_DIR}")
endif()

12
README.md

@ -15,13 +15,7 @@ http://download.i2p.io/purplei2p/i2pd/releases/
Build Statuses Build Statuses
--------------- ---------------
- Linux x64 - Maintaince - Linux x64 - Maintenance
- Linux ARM - Maintaince - Linux ARM - Maintenance
- Mac OS X - Maintaince - Mac OS X - Maintenance
- Microsoft VC13 - To be added - Microsoft VC13 - To be added
License
-------
This project is licensed under the BSD 3-clause license, which can be found in the doc directory.

14
benchmark/CMakeLists.txt

@ -0,0 +1,14 @@
set(BENCHMARK_SRC
"main.cpp"
)
if(WITH_BENCHMARK)
add_executable(${BENCHMARK_NAME} ${BENCHMARK_SRC})
target_link_libraries(
${BENCHMARK_NAME} ${CORE_NAME} ${DL_LIB} ${Boost_LIBRARIES} ${CRYPTO++_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT}
)
install(TARGETS
${BENCHMARK_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
endif()

83
benchmark/main.cpp

@ -0,0 +1,83 @@
#include "crypto/Signature.h"
#include <cryptopp/osrng.h>
#include <iostream>
#include <chrono>
#include <functional>
typedef std::function<void(CryptoPP::RandomNumberGenerator&, uint8_t*, uint8_t*)> KeyGenerator;
template<class Verifier, class Signer>
void benchmark(std::size_t count, std::size_t public_key_size, std::size_t private_key_size,
std::size_t signature_size, KeyGenerator generator)
{
typedef std::chrono::time_point<std::chrono::high_resolution_clock> TimePoint;
CryptoPP::AutoSeededRandomPool rng;
uint8_t private_key[private_key_size] = {};
uint8_t public_key[public_key_size] = {};
generator(rng, private_key, public_key);
Verifier verifier(public_key);
Signer signer(private_key);
uint8_t message[512] = {};
uint8_t output[signature_size] = {};
std::chrono::nanoseconds sign_duration(0);
std::chrono::nanoseconds verify_duration(0);
for(std::size_t i = 0; i < count; ++i) {
rng.GenerateBlock(message, 512);
TimePoint begin1 = std::chrono::high_resolution_clock::now();
signer.Sign(rng, message, 512, output);
TimePoint end1 = std::chrono::high_resolution_clock::now();
sign_duration += std::chrono::duration_cast<std::chrono::nanoseconds>(end1 - begin1);
TimePoint begin2 = std::chrono::high_resolution_clock::now();
verifier.Verify(message, 512, output);
TimePoint end2 = std::chrono::high_resolution_clock::now();
verify_duration += std::chrono::duration_cast<std::chrono::nanoseconds>(end2 - begin2);
}
std::cout << "Conducted " << count << " experiments." << std::endl;
std::cout << "Total sign time: " << std::chrono::duration_cast<std::chrono::milliseconds>(sign_duration).count() << std::endl;
std::cout << "Total verify time: " << std::chrono::duration_cast<std::chrono::milliseconds>(verify_duration).count() << std::endl;
}
int main()
{
using namespace i2p::crypto;
std::cout << "--------DSA---------" << std::endl;
benchmark<DSAVerifier, DSASigner>(
1000, DSA_PUBLIC_KEY_LENGTH,
DSA_PRIVATE_KEY_LENGTH, DSA_SIGNATURE_LENGTH,
&CreateDSARandomKeys
);
std::cout << "-----ECDSAP256------" << std::endl;
benchmark<ECDSAP256Verifier, ECDSAP256Signer>(
1000, ECDSAP256_KEY_LENGTH,
ECDSAP256_KEY_LENGTH, 64,
&CreateECDSAP256RandomKeys
);
std::cout << "-----ECDSAP384------" << std::endl;
benchmark<ECDSAP384Verifier, ECDSAP384Signer>(
1000, ECDSAP384_KEY_LENGTH,
ECDSAP384_KEY_LENGTH, 64,
&CreateECDSAP384RandomKeys
);
std::cout << "-----ECDSAP521------" << std::endl;
benchmark<ECDSAP521Verifier, ECDSAP521Signer>(
1000, ECDSAP521_KEY_LENGTH,
ECDSAP521_KEY_LENGTH, 64,
&CreateECDSAP521RandomKeys
);
std::cout << "-----EDDSA25519-----" << std::endl;
benchmark<EDDSA25519Verifier, EDDSA25519Signer>(
1000, EDDSA25519_PUBLIC_KEY_LENGTH,
EDDSA25519_PRIVATE_KEY_LENGTH, 64,
&CreateEDDSARandomKeys
);
}

6
client/ClientContext.cpp

@ -114,10 +114,12 @@ namespace client
// I2P Control // I2P Control
int i2pcontrolPort = i2p::util::config::GetArg("-i2pcontrolport", 0); int i2pcontrolPort = i2p::util::config::GetArg("-i2pcontrolport", 0);
if(i2pcontrolPort) { if(i2pcontrolPort) {
m_I2PControlService = new I2PControlService( m_I2PControlService = new i2pcontrol::I2PControlService(
i2p::util::config::GetArg("-i2pcontroladdress", "127.0.0.1"), i2p::util::config::GetArg("-i2pcontroladdress", "127.0.0.1"),
i2pcontrolPort, i2pcontrolPort,
i2p::util::config::GetArg("-i2pcontrolpassword", I2P_CONTROL_DEFAULT_PASSWORD) i2p::util::config::GetArg(
"-i2pcontrolpassword", i2pcontrol::constants::DEFAULT_PASSWORD
)
); );
m_I2PControlService->Start(); m_I2PControlService->Start();
LogPrint("I2PControl started"); LogPrint("I2PControl started");

2
client/ClientContext.h

@ -72,7 +72,7 @@ namespace client
std::map<i2p::data::IdentHash, std::unique_ptr<I2PServerTunnel> > m_ServerTunnels; // destination->tunnel std::map<i2p::data::IdentHash, std::unique_ptr<I2PServerTunnel> > m_ServerTunnels; // destination->tunnel
SAMBridge * m_SamBridge; SAMBridge * m_SamBridge;
BOBCommandChannel * m_BOBCommandChannel; BOBCommandChannel * m_BOBCommandChannel;
I2PControlService * m_I2PControlService; i2pcontrol::I2PControlService * m_I2PControlService;
public: public:
// for HTTP // for HTTP

15
client/Daemon.cpp

@ -56,7 +56,19 @@ namespace i2p
LogPrint("\n\n\n\ni2pd starting\n"); LogPrint("\n\n\n\ni2pd starting\n");
LogPrint("Version ", VERSION); LogPrint("Version ", VERSION);
LogPrint("data directory: ", i2p::util::filesystem::GetDataDir().string()); LogPrint("data directory: ", i2p::util::filesystem::GetDataDir().string());
i2p::util::filesystem::ReadConfigFile(i2p::util::config::mapArgs, i2p::util::config::mapMultiArgs); i2p::util::filesystem::ReadConfigFile(
i2p::util::config::mapArgs, i2p::util::config::mapMultiArgs
);
if(i2p::util::config::HasArg("-install")) {
try {
i2p::util::filesystem::InstallFiles();
LogPrint("Succesfully installed all files.");
} catch(const std::runtime_error& e) {
LogPrint(eLogError, "Failed to install: ", e.what());
return false;
}
}
isDaemon = i2p::util::config::GetArg("-daemon", 0); isDaemon = i2p::util::config::GetArg("-daemon", 0);
isLogging = i2p::util::config::GetArg("-log", 1); isLogging = i2p::util::config::GetArg("-log", 1);
@ -104,7 +116,6 @@ namespace i2p
else else
StartLog (""); // write to stdout StartLog (""); // write to stdout
} }
d.httpServer = new i2p::util::HTTPServer( d.httpServer = new i2p::util::HTTPServer(
i2p::util::config::GetArg("-httpaddress", "127.0.0.1"), i2p::util::config::GetArg("-httpaddress", "127.0.0.1"),
i2p::util::config::GetArg("-httpport", 7070) i2p::util::config::GetArg("-httpport", 7070)

2
client/DaemonWin32.cpp

@ -4,7 +4,7 @@
#ifdef _WIN32 #ifdef _WIN32
#include "./Win32/Win32Service.h" #include "Win32Service.h"
namespace i2p namespace i2p
{ {

1130
client/HTTPServer.cpp

File diff suppressed because it is too large Load Diff

141
client/HTTPServer.h

@ -6,119 +6,80 @@
#include <memory> #include <memory>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/array.hpp> #include <boost/array.hpp>
#include "i2pcontrol/I2PControl.h"
#include "util/HTTP.h"
namespace i2p {
namespace util {
const size_t HTTP_CONNECTION_BUFFER_SIZE = 8192;
const int HTTP_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds
class HTTPConnection: public std::enable_shared_from_this<HTTPConnection> {
public:
HTTPConnection(boost::asio::ip::tcp::socket* socket,
std::shared_ptr<i2p::client::i2pcontrol::I2PControlSession> session);
namespace i2p
{
namespace util
{
const size_t HTTP_CONNECTION_BUFFER_SIZE = 8192;
const int HTTP_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds
class HTTPConnection: public std::enable_shared_from_this<HTTPConnection>
{
protected:
struct header
{
std::string name;
std::string value;
};
struct request
{
std::string method;
std::string uri;
std::string host;
int port;
int http_version_major;
int http_version_minor;
std::vector<header> headers;
};
struct reply
{
std::vector<header> headers;
std::string content;
std::vector<boost::asio::const_buffer> to_buffers (int status);
};
public:
HTTPConnection (boost::asio::ip::tcp::socket * socket):
m_Socket (socket), m_Timer (socket->get_io_service ()),
m_BufferLen (0) {};
~HTTPConnection() { delete m_Socket; } ~HTTPConnection() { delete m_Socket; }
void Receive (); void Receive();
private: private:
void Terminate (); void Terminate();
void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleReceive(const boost::system::error_code& ecode, std::size_t bytes_transferred);
void RunRequest();
void HandleWriteReply(const boost::system::error_code& ecode); void HandleWriteReply(const boost::system::error_code& ecode);
void SendReply (const std::string& content, int status = 200); void SendReply();
void HandleRequest (const std::string& address);
void HandleCommand (const std::string& command, std::stringstream& s);
void ShowTransports (std::stringstream& s);
void ShowTunnels (std::stringstream& s);
void ShowTransitTunnels (std::stringstream& s);
void ShowLocalDestinations (std::stringstream& s);
void ShowLocalDestination (const std::string& b32, std::stringstream& s);
void ShowSAMSessions (std::stringstream& s);
void ShowSAMSession (const std::string& id, std::stringstream& s);
void StartAcceptingTunnels (std::stringstream& s);
void StopAcceptingTunnels (std::stringstream& s);
void FillContent (std::stringstream& s);
std::string ExtractAddress ();
void ExtractParams (const std::string& str, std::map<std::string, std::string>& params);
protected:
boost::asio::ip::tcp::socket * m_Socket;
boost::asio::deadline_timer m_Timer;
char m_Buffer[HTTP_CONNECTION_BUFFER_SIZE + 1], m_StreamBuffer[HTTP_CONNECTION_BUFFER_SIZE + 1];
size_t m_BufferLen;
request m_Request;
reply m_Reply;
protected: void Send404Reply();
virtual void RunRequest (); /*
* @throw std::runtime_error when the file is not accessible
*/
std::string GetFileContents(const std::string& filename, bool preprocess) const;
public: void HandleRequest();
void HandleI2PControlRequest();
void ExtractParams(const std::string& str, std::map<std::string, std::string>& params);
static const std::string itoopieImage; bool isAllowed(const std::string& address) const;
static const std::string itoopieFavicon; private:
}; boost::asio::ip::tcp::socket* m_Socket;
char m_Buffer[HTTP_CONNECTION_BUFFER_SIZE + 1];
size_t m_BufferLen;
util::http::Request m_Request;
util::http::Response m_Reply;
std::shared_ptr<i2p::client::i2pcontrol::I2PControlSession> m_Session;
};
class HTTPServer class HTTPServer {
{ public:
public:
HTTPServer (const std::string& address, int port); HTTPServer(const std::string& address, int port);
virtual ~HTTPServer (); virtual ~HTTPServer();
void Start (); void Start();
void Stop (); void Stop();
private: private:
void Run (); void Run();
void Accept (); void Accept();
void HandleAccept(const boost::system::error_code& ecode); void HandleAccept(const boost::system::error_code& ecode);
private: private:
std::thread * m_Thread; std::thread * m_Thread;
boost::asio::io_service m_Service; boost::asio::io_service m_Service;
boost::asio::io_service::work m_Work; boost::asio::io_service::work m_Work;
boost::asio::ip::tcp::acceptor m_Acceptor; boost::asio::ip::tcp::acceptor m_Acceptor;
boost::asio::ip::tcp::socket * m_NewSocket; boost::asio::ip::tcp::socket * m_NewSocket;
std::shared_ptr<i2p::client::i2pcontrol::I2PControlSession> m_Session;
protected: protected:
virtual void CreateConnection(boost::asio::ip::tcp::socket * m_NewSocket); void CreateConnection(boost::asio::ip::tcp::socket* m_NewSocket);
}; };
} }
} }

2
client/Win32Service.cpp

@ -8,7 +8,7 @@
#include <windows.h> #include <windows.h>
#include "Daemon.h" #include "Daemon.h"
#include "Log.h" #include "util/Log.h"
I2PService *I2PService::s_service = NULL; I2PService *I2PService::s_service = NULL;

12
client/i2p.cpp

@ -3,13 +3,13 @@
#include "Daemon.h" #include "Daemon.h"
#include "Reseed.h" #include "Reseed.h"
int main( int argc, char* argv[] ) int main(int argc, char* argv[])
{ {
Daemon.init(argc, argv); if(!Daemon.init(argc, argv))
if (Daemon.start()) return EXIT_FAILURE;
{
while (Daemon.running) if(Daemon.start()) {
{ while (Daemon.running) {
//TODO Meeh: Find something better to do here. //TODO Meeh: Find something better to do here.
std::this_thread::sleep_for (std::chrono::seconds(1)); std::this_thread::sleep_for (std::chrono::seconds(1));
} }

230
client/i2pcontrol/I2PControl.cpp

@ -11,6 +11,7 @@
#include <cryptopp/filters.h> #include <cryptopp/filters.h>
#include <boost/property_tree/json_parser.hpp> #include <boost/property_tree/json_parser.hpp>
#include "util/util.h"
#include "util/Log.h" #include "util/Log.h"
#include "util/Timestamp.h" #include "util/Timestamp.h"
#include "transport/Transports.h" #include "transport/Transports.h"
@ -21,6 +22,65 @@
namespace i2p { namespace i2p {
namespace client { namespace client {
namespace i2pcontrol {
JsonObject::JsonObject(const std::string& value)
: children(), value("\"" + value + "\"")
{
}
JsonObject::JsonObject(int value)
: children(), value(std::to_string(value))
{
}
JsonObject::JsonObject(double v)
: children(), value()
{
std::ostringstream oss;
oss << std::fixed << std::setprecision(2) << v;
value = oss.str();
}
JsonObject& JsonObject::operator[](const std::string& key)
{
return children[key];
}
std::string JsonObject::toString() const
{
if(children.empty())
return value;
std::ostringstream oss;
oss << '{';
for(auto it = children.begin(); it != children.end(); ++it) {
if(it != children.begin())
oss << ',';
oss << '"' << it->first << "\":" << it->second.toString();
}
oss << '}';
return oss.str();
}
JsonObject tunnelToJsonObject(i2p::tunnel::Tunnel* tunnel)
{
JsonObject obj;
std::stringstream ss;
tunnel->GetTunnelConfig()->Print(ss); // TODO: use a JsonObject
obj["layout"] = JsonObject(ss.str());
const auto state = tunnel->GetState();
if(state == i2p::tunnel::eTunnelStateFailed)
obj["state"] = JsonObject("failed");
else if(state == i2p::tunnel::eTunnelStateExpiring)
obj["state"] = JsonObject("expiring");
return obj;
}
I2PControlSession::Response::Response(const std::string& version) I2PControlSession::Response::Response(const std::string& version)
: id(), version(version), error(ErrorCode::None), parameters() : id(), version(version), error(ErrorCode::None), parameters()
@ -90,6 +150,11 @@ void I2PControlSession::Response::setParam(const std::string& param, double valu
parameters[param] = oss.str(); parameters[param] = oss.str();
} }
void I2PControlSession::Response::setParam(const std::string& param, const JsonObject& value)
{
parameters[param] = value.toString();
}
void I2PControlSession::Response::setError(ErrorCode code) void I2PControlSession::Response::setError(ErrorCode code)
{ {
error = code; error = code;
@ -104,28 +169,35 @@ I2PControlSession::I2PControlSession(boost::asio::io_service& ios, const std::st
: password(pass), tokens(), tokensMutex(), : password(pass), tokens(), tokensMutex(),
service(ios), shutdownTimer(ios), expireTokensTimer(ios) service(ios), shutdownTimer(ios), expireTokensTimer(ios)
{ {
using namespace i2p::client::i2pcontrol::constants;
// Method handlers // Method handlers
methodHandlers[I2P_CONTROL_METHOD_AUTHENTICATE] = &I2PControlSession::handleAuthenticate; methodHandlers[METHOD_AUTHENTICATE] = &I2PControlSession::handleAuthenticate;
methodHandlers[I2P_CONTROL_METHOD_ECHO] = &I2PControlSession::handleEcho; methodHandlers[METHOD_ECHO] = &I2PControlSession::handleEcho;
methodHandlers[I2P_CONTROL_METHOD_I2PCONTROL] = &I2PControlSession::handleI2PControl; methodHandlers[METHOD_I2PCONTROL] = &I2PControlSession::handleI2PControl;
methodHandlers[I2P_CONTROL_METHOD_ROUTER_INFO] = &I2PControlSession::handleRouterInfo; methodHandlers[METHOD_ROUTER_INFO] = &I2PControlSession::handleRouterInfo;
methodHandlers[I2P_CONTROL_METHOD_ROUTER_MANAGER] = &I2PControlSession::handleRouterManager; methodHandlers[METHOD_ROUTER_MANAGER] = &I2PControlSession::handleRouterManager;
methodHandlers[I2P_CONTROL_METHOD_NETWORK_SETTING] = &I2PControlSession::handleNetworkSetting; methodHandlers[METHOD_NETWORK_SETTING] = &I2PControlSession::handleNetworkSetting;
// RouterInfo handlers // RouterInfo handlers
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_UPTIME] = &I2PControlSession::handleUptime; routerInfoHandlers[ROUTER_INFO_UPTIME] = &I2PControlSession::handleUptime;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_VERSION] = &I2PControlSession::handleVersion; routerInfoHandlers[ROUTER_INFO_VERSION] = &I2PControlSession::handleVersion;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_STATUS] = &I2PControlSession::handleStatus; routerInfoHandlers[ROUTER_INFO_STATUS] = &I2PControlSession::handleStatus;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS]= &I2PControlSession::handleNetDbKnownPeers; routerInfoHandlers[ROUTER_INFO_DATAPATH] = &I2PControlSession::handleDatapath;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS] = &I2PControlSession::handleNetDbActivePeers; routerInfoHandlers[ROUTER_INFO_NETDB_KNOWNPEERS]= &I2PControlSession::handleNetDbKnownPeers;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_NET_STATUS] = &I2PControlSession::handleNetStatus; routerInfoHandlers[ROUTER_INFO_NETDB_ACTIVEPEERS] = &I2PControlSession::handleNetDbActivePeers;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING] = &I2PControlSession::handleTunnelsParticipating; routerInfoHandlers[ROUTER_INFO_NETDB_LEASESETS] = &I2PControlSession::handleNetDbLeaseSets;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_BW_IB_1S] = &I2PControlSession::handleInBandwidth1S; routerInfoHandlers[ROUTER_INFO_NETDB_FLOODFILLS] = &I2PControlSession::handleNetDbFloodfills;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_BW_OB_1S] = &I2PControlSession::handleOutBandwidth1S; routerInfoHandlers[ROUTER_INFO_NET_STATUS] = &I2PControlSession::handleNetStatus;
routerInfoHandlers[ROUTER_INFO_TUNNELS_PARTICIPATING] = &I2PControlSession::handleTunnelsParticipating;
routerInfoHandlers[ROUTER_INFO_TUNNELS_CREATION_SUCCESS] = &I2PControlSession::handleTunnelsCreationSuccess;
routerInfoHandlers[ROUTER_INFO_TUNNELS_IN_LIST] = &I2PControlSession::handleTunnelsInList;
routerInfoHandlers[ROUTER_INFO_TUNNELS_OUT_LIST] = &I2PControlSession::handleTunnelsOutList;
routerInfoHandlers[ROUTER_INFO_BW_IB_1S] = &I2PControlSession::handleInBandwidth1S;
routerInfoHandlers[ROUTER_INFO_BW_OB_1S] = &I2PControlSession::handleOutBandwidth1S;
// RouterManager handlers // RouterManager handlers
routerManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN] = &I2PControlSession::handleShutdown; routerManagerHandlers[ROUTER_MANAGER_SHUTDOWN] = &I2PControlSession::handleShutdown;
routerManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL] = &I2PControlSession::handleShutdownGraceful; routerManagerHandlers[ROUTER_MANAGER_SHUTDOWN_GRACEFUL] = &I2PControlSession::handleShutdownGraceful;
routerManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_RESEED] = &I2PControlSession::handleReseed; routerManagerHandlers[ROUTER_MANAGER_RESEED] = &I2PControlSession::handleReseed;
} }
void I2PControlSession::start() void I2PControlSession::start()
@ -147,9 +219,9 @@ I2PControlSession::Response I2PControlSession::handleRequest(std::stringstream&
Response response; Response response;
try { try {
response.setId(pt.get<std::string>(I2P_CONTROL_PROPERTY_ID)); response.setId(pt.get<std::string>(constants::PROPERTY_ID));
std::string method = pt.get<std::string>(I2P_CONTROL_PROPERTY_METHOD); std::string method = pt.get<std::string>(constants::PROPERTY_METHOD);
auto it = methodHandlers.find(method); auto it = methodHandlers.find(method);
if(it == methodHandlers.end()) { // Not found if(it == methodHandlers.end()) { // Not found
LogPrint(eLogWarning, "Unknown I2PControl method ", method); LogPrint(eLogWarning, "Unknown I2PControl method ", method);
@ -157,8 +229,8 @@ I2PControlSession::Response I2PControlSession::handleRequest(std::stringstream&
return response; return response;
} }
PropertyTree params = pt.get_child(I2P_CONTROL_PROPERTY_PARAMS); PropertyTree params = pt.get_child(constants::PROPERTY_PARAMS);
if(method != I2P_CONTROL_METHOD_AUTHENTICATE && !authenticate(params, response)) { if(method != constants::METHOD_AUTHENTICATE && !authenticate(params, response)) {
LogPrint(eLogWarning, "I2PControl invalid token presented"); LogPrint(eLogWarning, "I2PControl invalid token presented");
return response; return response;
} }
@ -177,14 +249,14 @@ I2PControlSession::Response I2PControlSession::handleRequest(std::stringstream&
bool I2PControlSession::authenticate(const PropertyTree& pt, Response& response) bool I2PControlSession::authenticate(const PropertyTree& pt, Response& response)
{ {
try { try {
std::string token = pt.get<std::string>(I2P_CONTROL_PARAM_TOKEN); std::string token = pt.get<std::string>(constants::PARAM_TOKEN);
std::lock_guard<std::mutex> lock(tokensMutex); std::lock_guard<std::mutex> lock(tokensMutex);
auto it = tokens.find(token); auto it = tokens.find(token);
if(it == tokens.end()) { if(it == tokens.end()) {
response.setError(ErrorCode::NonexistentToken); response.setError(ErrorCode::NonexistentToken);
return false; return false;
} else if(util::GetSecondsSinceEpoch() - it->second > I2P_CONTROL_TOKEN_LIFETIME) { } else if(util::GetSecondsSinceEpoch() - it->second > constants::TOKEN_LIFETIME) {
response.setError(ErrorCode::ExpiredToken); response.setError(ErrorCode::ExpiredToken);
return false; return false;
} }
@ -199,12 +271,12 @@ bool I2PControlSession::authenticate(const PropertyTree& pt, Response& response)
std::string I2PControlSession::generateToken() const std::string I2PControlSession::generateToken() const
{ {
byte random_data[I2P_CONTROL_TOKEN_SIZE] = {}; byte random_data[constants::TOKEN_SIZE] = {};
CryptoPP::AutoSeededRandomPool rng; CryptoPP::AutoSeededRandomPool rng;
rng.GenerateBlock(random_data, I2P_CONTROL_TOKEN_SIZE); rng.GenerateBlock(random_data, constants::TOKEN_SIZE);
std::string token; std::string token;
CryptoPP::StringSource ss( CryptoPP::StringSource ss(
random_data, I2P_CONTROL_TOKEN_SIZE, true, random_data, constants::TOKEN_SIZE, true,
new CryptoPP::HexEncoder(new CryptoPP::StringSink(token)) new CryptoPP::HexEncoder(new CryptoPP::StringSink(token))
); );
return token; return token;
@ -212,8 +284,8 @@ std::string I2PControlSession::generateToken() const
void I2PControlSession::handleAuthenticate(const PropertyTree& pt, Response& response) void I2PControlSession::handleAuthenticate(const PropertyTree& pt, Response& response)
{ {
const int api = pt.get<int>(I2P_CONTROL_PARAM_API); const int api = pt.get<int>(constants::PARAM_API);
const std::string given_pass = pt.get<std::string>(I2P_CONTROL_PARAM_PASSWORD); const std::string given_pass = pt.get<std::string>(constants::PARAM_PASSWORD);
LogPrint(eLogDebug, "I2PControl Authenticate API = ", api, " Password = ", given_pass); LogPrint(eLogDebug, "I2PControl Authenticate API = ", api, " Password = ", given_pass);
if(given_pass != password) { if(given_pass != password) {
LogPrint( LogPrint(
@ -224,8 +296,8 @@ void I2PControlSession::handleAuthenticate(const PropertyTree& pt, Response& res
return; return;
} }
const std::string token = generateToken(); const std::string token = generateToken();
response.setParam(I2P_CONTROL_PARAM_API, api); response.setParam(constants::PARAM_API, api);
response.setParam(I2P_CONTROL_PARAM_TOKEN, token); response.setParam(constants::PARAM_TOKEN, token);
std::lock_guard<std::mutex> lock(tokensMutex); std::lock_guard<std::mutex> lock(tokensMutex);
tokens.insert(std::make_pair(token, util::GetSecondsSinceEpoch())); tokens.insert(std::make_pair(token, util::GetSecondsSinceEpoch()));
@ -233,9 +305,9 @@ void I2PControlSession::handleAuthenticate(const PropertyTree& pt, Response& res
void I2PControlSession::handleEcho(const PropertyTree& pt, Response& response) void I2PControlSession::handleEcho(const PropertyTree& pt, Response& response)
{ {
const std::string echo = pt.get<std::string>(I2P_CONTROL_PARAM_ECHO); const std::string echo = pt.get<std::string>(constants::PARAM_ECHO);
LogPrint(eLogDebug, "I2PControl Echo Echo = ", echo); LogPrint(eLogDebug, "I2PControl Echo Echo = ", echo);
response.setParam(I2P_CONTROL_PARAM_RESULT, echo); response.setParam(constants::PARAM_RESULT, echo);
} }
void I2PControlSession::handleI2PControl(const PropertyTree&, Response&) void I2PControlSession::handleI2PControl(const PropertyTree&, Response&)
@ -249,7 +321,7 @@ void I2PControlSession::handleRouterInfo(const PropertyTree& pt, Response& respo
{ {
LogPrint(eLogDebug, "I2PControl RouterInfo"); LogPrint(eLogDebug, "I2PControl RouterInfo");
for(const auto& pair : pt) { for(const auto& pair : pt) {
if(pair.first == I2P_CONTROL_PARAM_TOKEN) if(pair.first == constants::PARAM_TOKEN)
continue; continue;
LogPrint(eLogDebug, pair.first); LogPrint(eLogDebug, pair.first);
auto it = routerInfoHandlers.find(pair.first); auto it = routerInfoHandlers.find(pair.first);
@ -266,7 +338,7 @@ void I2PControlSession::handleRouterManager(const PropertyTree& pt, Response& re
{ {
LogPrint(eLogDebug, "I2PControl RouterManager"); LogPrint(eLogDebug, "I2PControl RouterManager");
for(const auto& pair : pt) { for(const auto& pair : pt) {
if(pair.first == I2P_CONTROL_PARAM_TOKEN) if(pair.first == constants::PARAM_TOKEN)
continue; continue;
LogPrint(eLogDebug, pair.first); LogPrint(eLogDebug, pair.first);
auto it = routerManagerHandlers.find(pair.first); auto it = routerManagerHandlers.find(pair.first);
@ -286,53 +358,112 @@ void I2PControlSession::handleNetworkSetting(const PropertyTree&, Response&)
void I2PControlSession::handleUptime(Response& response) void I2PControlSession::handleUptime(Response& response)
{ {
response.setParam(I2P_CONTROL_ROUTER_INFO_UPTIME, (int)i2p::context.GetUptime()*1000); response.setParam(constants::ROUTER_INFO_UPTIME, (int)i2p::context.GetUptime()*1000);
} }
void I2PControlSession::handleVersion(Response& response) void I2PControlSession::handleVersion(Response& response)
{ {
response.setParam(I2P_CONTROL_ROUTER_INFO_VERSION, VERSION); response.setParam(constants::ROUTER_INFO_VERSION, VERSION);
} }
void I2PControlSession::handleStatus(Response& response) void I2PControlSession::handleStatus(Response& response)
{ {
response.setParam(I2P_CONTROL_ROUTER_INFO_STATUS, "???"); // TODO: response.setParam(constants::ROUTER_INFO_STATUS, "???"); // TODO:
}
void I2PControlSession::handleDatapath(Response& response)
{
response.setParam(
constants::ROUTER_INFO_DATAPATH,
i2p::util::filesystem::GetDefaultDataDir().string()
);
} }
void I2PControlSession::handleNetDbKnownPeers(Response& response) void I2PControlSession::handleNetDbKnownPeers(Response& response)
{ {
response.setParam( response.setParam(
I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS, i2p::data::netdb.GetNumRouters() constants::ROUTER_INFO_NETDB_KNOWNPEERS, i2p::data::netdb.GetNumRouters()
); );
} }
void I2PControlSession::handleNetDbActivePeers(Response& response) void I2PControlSession::handleNetDbActivePeers(Response& response)
{ {
response.setParam( response.setParam(
I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS, constants::ROUTER_INFO_NETDB_ACTIVEPEERS,
(int)i2p::transport::transports.GetPeers().size() (int)i2p::transport::transports.GetPeers().size()
); );
} }
void I2PControlSession::handleNetDbFloodfills(Response& response)
{
response.setParam(
constants::ROUTER_INFO_NETDB_FLOODFILLS,
(int)i2p::data::netdb.GetNumFloodfills()
);
}
void I2PControlSession::handleNetDbLeaseSets(Response& response)
{
response.setParam(
constants::ROUTER_INFO_NETDB_LEASESETS,
(int)i2p::data::netdb.GetNumLeaseSets()
);
}
void I2PControlSession::handleNetStatus(Response& response) void I2PControlSession::handleNetStatus(Response& response)
{ {
response.setParam( response.setParam(
I2P_CONTROL_ROUTER_INFO_NET_STATUS, (int)i2p::context.GetStatus() constants::ROUTER_INFO_NET_STATUS, (int)i2p::context.GetStatus()
); );
} }
void I2PControlSession::handleTunnelsParticipating(Response& response) void I2PControlSession::handleTunnelsParticipating(Response& response)
{ {
response.setParam( response.setParam(
I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING, constants::ROUTER_INFO_TUNNELS_PARTICIPATING,
(int)i2p::tunnel::tunnels.GetTransitTunnels().size() (int)i2p::tunnel::tunnels.GetTransitTunnels().size()
); );
} }
void I2PControlSession::handleTunnelsCreationSuccess(Response& response)
{
response.setParam(
constants::ROUTER_INFO_TUNNELS_CREATION_SUCCESS,
i2p::tunnel::tunnels.GetTunnelCreationSuccessRate()
);
}
void I2PControlSession::handleTunnelsInList(Response& response)
{
JsonObject list;
for(auto pair : i2p::tunnel::tunnels.GetInboundTunnels()) {
const std::string id = std::to_string(pair.first);
list[id] = tunnelToJsonObject(pair.second.get());
list[id]["bytes"] = JsonObject(
static_cast<int>(pair.second->GetNumReceivedBytes())
);
}
response.setParam(constants::ROUTER_INFO_TUNNELS_IN_LIST, list);
}
void I2PControlSession::handleTunnelsOutList(Response& response)
{
JsonObject list;
for(auto tunnel : i2p::tunnel::tunnels.GetOutboundTunnels()) {
const std::string id = std::to_string(tunnel->GetTunnelID());
list[id] = tunnelToJsonObject(tunnel.get());
list[id]["bytes"] = JsonObject(
static_cast<int>(tunnel->GetNumSentBytes())
);
}
response.setParam(constants::ROUTER_INFO_TUNNELS_OUT_LIST, list);
}
void I2PControlSession::handleInBandwidth1S(Response& response) void I2PControlSession::handleInBandwidth1S(Response& response)
{ {
response.setParam( response.setParam(
I2P_CONTROL_ROUTER_INFO_BW_IB_1S, constants::ROUTER_INFO_BW_IB_1S,
(double)i2p::transport::transports.GetInBandwidth() (double)i2p::transport::transports.GetInBandwidth()
); );
} }
@ -340,7 +471,7 @@ void I2PControlSession::handleInBandwidth1S(Response& response)
void I2PControlSession::handleOutBandwidth1S(Response& response) void I2PControlSession::handleOutBandwidth1S(Response& response)
{ {
response.setParam( response.setParam(
I2P_CONTROL_ROUTER_INFO_BW_OB_1S, constants::ROUTER_INFO_BW_OB_1S,
(double)i2p::transport::transports.GetOutBandwidth() (double)i2p::transport::transports.GetOutBandwidth()
); );
} }
@ -348,7 +479,7 @@ void I2PControlSession::handleOutBandwidth1S(Response& response)
void I2PControlSession::handleShutdown(Response& response) void I2PControlSession::handleShutdown(Response& response)
{ {
LogPrint(eLogInfo, "Shutdown requested"); LogPrint(eLogInfo, "Shutdown requested");
response.setParam(I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN, ""); response.setParam(constants::ROUTER_MANAGER_SHUTDOWN, "");
// 1 second to make sure response has been sent // 1 second to make sure response has been sent
shutdownTimer.expires_from_now(boost::posix_time::seconds(1)); shutdownTimer.expires_from_now(boost::posix_time::seconds(1));
shutdownTimer.async_wait([](const boost::system::error_code&) { shutdownTimer.async_wait([](const boost::system::error_code&) {
@ -361,7 +492,7 @@ void I2PControlSession::handleShutdownGraceful(Response& response)
i2p::context.SetAcceptsTunnels(false); i2p::context.SetAcceptsTunnels(false);
int timeout = i2p::tunnel::tunnels.GetTransitTunnelsExpirationTimeout(); int timeout = i2p::tunnel::tunnels.GetTransitTunnelsExpirationTimeout();
LogPrint(eLogInfo, "Graceful shutdown requested. Will shutdown after ", timeout, " seconds"); LogPrint(eLogInfo, "Graceful shutdown requested. Will shutdown after ", timeout, " seconds");
response.setParam(I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL, ""); response.setParam(constants::ROUTER_MANAGER_SHUTDOWN_GRACEFUL, "");
shutdownTimer.expires_from_now(boost::posix_time::seconds(timeout + 1)); shutdownTimer.expires_from_now(boost::posix_time::seconds(timeout + 1));
shutdownTimer.async_wait([](const boost::system::error_code&) { shutdownTimer.async_wait([](const boost::system::error_code&) {
Daemon.running = 0; Daemon.running = 0;
@ -371,7 +502,7 @@ void I2PControlSession::handleShutdownGraceful(Response& response)
void I2PControlSession::handleReseed(Response& response) void I2PControlSession::handleReseed(Response& response)
{ {
LogPrint(eLogInfo, "Reseed requested"); LogPrint(eLogInfo, "Reseed requested");
response.setParam(I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN, ""); response.setParam(constants::ROUTER_MANAGER_SHUTDOWN, "");
i2p::data::netdb.Reseed(); i2p::data::netdb.Reseed();
} }
@ -385,7 +516,7 @@ void I2PControlSession::expireTokens(const boost::system::error_code& error)
const uint64_t now = util::GetSecondsSinceEpoch(); const uint64_t now = util::GetSecondsSinceEpoch();
std::lock_guard<std::mutex> lock(tokensMutex); std::lock_guard<std::mutex> lock(tokensMutex);
for(auto it = tokens.begin(); it != tokens.end(); ) { for(auto it = tokens.begin(); it != tokens.end(); ) {
if(now - it->second > I2P_CONTROL_TOKEN_LIFETIME) if(now - it->second > constants::TOKEN_LIFETIME)
it = tokens.erase(it); it = tokens.erase(it);
else else
++it; ++it;
@ -394,7 +525,7 @@ void I2PControlSession::expireTokens(const boost::system::error_code& error)
void I2PControlSession::startExpireTokensJob() void I2PControlSession::startExpireTokensJob()
{ {
expireTokensTimer.expires_from_now(boost::posix_time::seconds(I2P_CONTROL_TOKEN_LIFETIME)); expireTokensTimer.expires_from_now(boost::posix_time::seconds(constants::TOKEN_LIFETIME));
expireTokensTimer.async_wait(std::bind( expireTokensTimer.async_wait(std::bind(
&I2PControlSession::expireTokens, shared_from_this(), std::placeholders::_1 &I2PControlSession::expireTokens, shared_from_this(), std::placeholders::_1
)); ));
@ -402,3 +533,4 @@ void I2PControlSession::startExpireTokensJob()
} }
} }
}

132
client/i2pcontrol/I2PControl.h

@ -9,52 +9,94 @@
#include <boost/asio.hpp> #include <boost/asio.hpp>
namespace i2p { namespace i2p {
// Forward declaration
namespace tunnel { class Tunnel; }
namespace client { namespace client {
namespace i2pcontrol {
const char I2P_CONTROL_DEFAULT_PASSWORD[] = "itoopie"; namespace constants {
const uint64_t I2P_CONTROL_TOKEN_LIFETIME = 600; // Token lifetime in seconds
const std::size_t I2P_CONTROL_TOKEN_SIZE = 8; // Token size in bytes
const char I2P_CONTROL_PROPERTY_ID[] = "id"; const char DEFAULT_PASSWORD[] = "itoopie";
const char I2P_CONTROL_PROPERTY_METHOD[] = "method"; const uint64_t TOKEN_LIFETIME = 600; // Token lifetime in seconds
const char I2P_CONTROL_PROPERTY_PARAMS[] = "params"; const std::size_t TOKEN_SIZE = 8; // Token size in bytes
const char I2P_CONTROL_PROPERTY_RESULT[] = "result";
const char PROPERTY_ID[] = "id";
const char PROPERTY_METHOD[] = "method";
const char PROPERTY_PARAMS[] = "params";
const char PROPERTY_RESULT[] = "result";
// methods // methods
const char I2P_CONTROL_METHOD_AUTHENTICATE[] = "Authenticate"; const char METHOD_AUTHENTICATE[] = "Authenticate";
const char I2P_CONTROL_METHOD_ECHO[] = "Echo"; const char METHOD_ECHO[] = "Echo";
const char I2P_CONTROL_METHOD_I2PCONTROL[] = "I2PControl"; const char METHOD_I2PCONTROL[] = "I2PControl";
const char I2P_CONTROL_METHOD_ROUTER_INFO[] = "RouterInfo"; const char METHOD_ROUTER_INFO[] = "RouterInfo";
const char I2P_CONTROL_METHOD_ROUTER_MANAGER[] = "RouterManager"; const char METHOD_ROUTER_MANAGER[] = "RouterManager";
const char I2P_CONTROL_METHOD_NETWORK_SETTING[] = "NetworkSetting"; const char METHOD_NETWORK_SETTING[] = "NetworkSetting";
// params // params
const char I2P_CONTROL_PARAM_API[] = "API"; const char PARAM_API[] = "API";
const char I2P_CONTROL_PARAM_PASSWORD[] = "Password"; const char PARAM_PASSWORD[] = "Password";
const char I2P_CONTROL_PARAM_TOKEN[] = "Token"; const char PARAM_TOKEN[] = "Token";
const char I2P_CONTROL_PARAM_ECHO[] = "Echo"; const char PARAM_ECHO[] = "Echo";
const char I2P_CONTROL_PARAM_RESULT[] = "Result"; const char PARAM_RESULT[] = "Result";
// I2PControl // I2PControl
const char I2P_CONTROL_I2PCONTROL_ADDRESS[] = "i2pcontrol.address"; const char I2PCONTROL_ADDRESS[] = "i2pcontrol.address";
const char I2P_CONTROL_I2PCONTROL_PASSWORD[] = "i2pcontrol.password"; const char I2PCONTROL_PASSWORD[] = "i2pcontrol.password";
const char I2P_CONTROL_I2PCONTROL_PORT[] = "i2pcontrol.port"; const char I2PCONTROL_PORT[] = "i2pcontrol.port";
// RouterInfo requests // RouterInfo requests
const char I2P_CONTROL_ROUTER_INFO_UPTIME[] = "i2p.router.uptime"; const char ROUTER_INFO_UPTIME[] = "i2p.router.uptime";
const char I2P_CONTROL_ROUTER_INFO_VERSION[] = "i2p.router.version"; const char ROUTER_INFO_VERSION[] = "i2p.router.version";
const char I2P_CONTROL_ROUTER_INFO_STATUS[] = "i2p.router.status"; const char ROUTER_INFO_STATUS[] = "i2p.router.status";
const char I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS[] = "i2p.router.netdb.knownpeers"; const char ROUTER_INFO_DATAPATH[] = "i2p.router.datapath";
const char I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS[] = "i2p.router.netdb.activepeers"; const char ROUTER_INFO_NETDB_KNOWNPEERS[] = "i2p.router.netdb.knownpeers";
const char I2P_CONTROL_ROUTER_INFO_NET_STATUS[] = "i2p.router.net.status"; const char ROUTER_INFO_NETDB_ACTIVEPEERS[] = "i2p.router.netdb.activepeers";
const char I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING[] = "i2p.router.net.tunnels.participating"; const char ROUTER_INFO_NETDB_FLOODFILLS[] = "i2p.router.netdb.floodfills";
const char I2P_CONTROL_ROUTER_INFO_BW_IB_1S[] = "i2p.router.net.bw.inbound.1s"; const char ROUTER_INFO_NETDB_LEASESETS[] = "i2p.router.netdb.leasesets";
const char I2P_CONTROL_ROUTER_INFO_BW_OB_1S[] = "i2p.router.net.bw.outbound.1s"; const char ROUTER_INFO_NET_STATUS[] = "i2p.router.net.status";
const char ROUTER_INFO_TUNNELS_PARTICIPATING[] = "i2p.router.net.tunnels.participating";
// TODO: Probably better to use the standard GetRate instead
const char ROUTER_INFO_TUNNELS_CREATION_SUCCESS[] = "i2p.router.net.tunnels.creationsuccessrate";
const char ROUTER_INFO_TUNNELS_IN_LIST[] = "i2p.router.net.tunnels.inbound.list";
const char ROUTER_INFO_TUNNELS_OUT_LIST[] = "i2p.router.net.tunnels.outbound.list";
const char ROUTER_INFO_BW_IB_1S[] = "i2p.router.net.bw.inbound.1s";
const char ROUTER_INFO_BW_OB_1S[] = "i2p.router.net.bw.outbound.1s";
// RouterManager requests // RouterManager requests
const char I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN[] = "Shutdown"; const char ROUTER_MANAGER_SHUTDOWN[] = "Shutdown";
const char I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL[] = "ShutdownGraceful"; const char ROUTER_MANAGER_SHUTDOWN_GRACEFUL[] = "ShutdownGraceful";
const char I2P_CONTROL_ROUTER_MANAGER_RESEED[] = "Reseed"; const char ROUTER_MANAGER_RESEED[] = "Reseed";
} // constants
/**
* Represents a Json object, provides functionality to convert to string.
*/
class JsonObject {
public:
JsonObject() = default;
JsonObject(const std::string& value);
JsonObject(int value);
JsonObject(double value);
JsonObject& operator[](const std::string& key);
std::string toString() const;
private:
std::map<std::string, JsonObject> children;
std::string value;
};
JsonObject tunnelToJsonObject(i2p::tunnel::Tunnel* tunnel);
/** /**
* "Null" I2P control implementation, does not do actual networking. * "Null" I2P control implementation, does not do actual networking.
@ -97,9 +139,22 @@ public:
* @todo escape quotes * @todo escape quotes
*/ */
void setParam(const std::string& param, const std::string& value); void setParam(const std::string& param, const std::string& value);
/**
* Set an ouptut parameter to a specified integer.
*/
void setParam(const std::string& param, int value); void setParam(const std::string& param, int value);
/**
* Set an ouptut parameter to a specified double.
*/
void setParam(const std::string& param, double value); void setParam(const std::string& param, double value);
/**
* Set an ouptut parameter to a specified Json object.
*/
void setParam(const std::string& param, const JsonObject& value);
void setError(ErrorCode code); void setError(ErrorCode code);
void setId(const std::string& identifier); void setId(const std::string& identifier);
@ -113,7 +168,7 @@ public:
* the lifetime of this I2PControlSession. * the lifetime of this I2PControlSession.
*/ */
I2PControlSession(boost::asio::io_service& ios, I2PControlSession(boost::asio::io_service& ios,
const std::string& pass = I2P_CONTROL_DEFAULT_PASSWORD); const std::string& pass = constants::DEFAULT_PASSWORD);
/** /**
* Starts the I2PControlSession. * Starts the I2PControlSession.
@ -172,10 +227,18 @@ private:
void handleUptime(Response& response); void handleUptime(Response& response);
void handleVersion(Response& response); void handleVersion(Response& response);
void handleStatus(Response& response); void handleStatus(Response& response);
void handleDatapath(Response& response);
void handleNetDbKnownPeers(Response& response); void handleNetDbKnownPeers(Response& response);
void handleNetDbActivePeers(Response& response); void handleNetDbActivePeers(Response& response);
void handleNetDbFloodfills(Response& response);
void handleNetDbLeaseSets(Response& response);
void handleNetStatus(Response& response); void handleNetStatus(Response& response);
void handleTunnelsParticipating(Response& response); void handleTunnelsParticipating(Response& response);
void handleTunnelsCreationSuccess(Response& response);
void handleTunnelsInList(Response& response);
void handleTunnelsOutList(Response& response);
void handleInBandwidth1S(Response& response); void handleInBandwidth1S(Response& response);
void handleOutBandwidth1S(Response& response); void handleOutBandwidth1S(Response& response);
@ -198,6 +261,7 @@ private:
boost::asio::deadline_timer expireTokensTimer; boost::asio::deadline_timer expireTokensTimer;
}; };
}
} }
} }

2
client/i2pcontrol/I2PControlServer.cpp

@ -8,6 +8,7 @@
namespace i2p { namespace i2p {
namespace client { namespace client {
namespace i2pcontrol {
I2PControlService::I2PControlService(const std::string& address, int port, const std::string& pass) I2PControlService::I2PControlService(const std::string& address, int port, const std::string& pass)
: m_Session(std::make_shared<I2PControlSession>(m_Service, pass)), : m_Session(std::make_shared<I2PControlSession>(m_Service, pass)),
@ -168,3 +169,4 @@ void I2PControlService::HandleResponseSent(const boost::system::error_code& ecod
} }
} }
}

2
client/i2pcontrol/I2PControlServer.h

@ -12,6 +12,7 @@
namespace i2p { namespace i2p {
namespace client { namespace client {
namespace i2pcontrol {
const size_t I2P_CONTROL_MAX_REQUEST_SIZE = 1024; const size_t I2P_CONTROL_MAX_REQUEST_SIZE = 1024;
typedef std::array<char, I2P_CONTROL_MAX_REQUEST_SIZE> I2PControlBuffer; typedef std::array<char, I2P_CONTROL_MAX_REQUEST_SIZE> I2PControlBuffer;
@ -51,6 +52,7 @@ private:
}; };
} }
} }
}
#endif #endif

31
contrib/certificates/router/orignal_at_mail.i2p.crt

@ -1,31 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIFVDCCAzwCCQC2r1XWYtqtAzANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQGEwJY
WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMRMwEQYDVQQKDApQdXJwbGUgSTJQ
MQ0wCwYDVQQLDARJMlBEMR8wHQYJKoZIhvcNAQkBFhBvcmlnbmFsQG1haWwuaTJw
MB4XDTE1MDIyMjEzNTgxOFoXDTI1MDIxOTEzNTgxOFowbDELMAkGA1UEBhMCWFgx
CzAJBgNVBAgMAlhYMQswCQYDVQQHDAJYWDETMBEGA1UECgwKUHVycGxlIEkyUDEN
MAsGA1UECwwESTJQRDEfMB0GCSqGSIb3DQEJARYQb3JpZ25hbEBtYWlsLmkycDCC
AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALp3D/gdvFjrMm+IE8tHZCWE
hQ6Pp0CCgCGDBC3WQFLqR98bqVPl4UwRG/MKY/LY7Woai06JNmGcpfw0LMoNnHxT
bvKtDRe/8kQdhdLHhgIkWKSbMvTAl7uUdV6FzsPgDR0x7scoFVWEhkF0wfmzGF2V
yr/WCBQejFPu69z03m5tRQ8Xjp2txWV45RawUmFu50bgbZvLCSLfTkIvxmfJzgPN
pJ3sPa/g7TBZl2uEiAu4uaEKvTuuzStOWCGgFaHYFVlTfFXTvmhFMqHfaidtzrlu
H35WGrmIWTDl6uGPC5QkSppvkj73rDj5aEyPzWMz5DN3YeECoVSchN+OJJCM6m7+
rLFYXghVEp2h+T9O1GBRfcHlQ2E3CrWWvxhmK8dfteJmd501dyNX2paeuIg/aPFO
54/8m2r11uyF29hgY8VWLdXtqvwhKuK36PCzofEwDp9QQX8GRsEV4pZTrn4bDhGo
kb9BF7TZTqtL3uyiRmIyBXrNNiYlA1Xm4fyKRtxl0mrPaUXdgdnCt3KxOAJ8WM2B
7L/kk9U8C/nexHbMxIZfTap49XcUg5dxSO9kOBosIOcCUms8sAzBPDV2tWAByhYF
jI/Tutbd3F0+fvcmTcIFOlGbOxKgO2SfwXjv/44g/3LMK6IAMFB9UOc8KhnnJP0f
uAHvMXn1ahRs4pM1VizLAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAIOxdaXT+wfu
nv/+1hy5T4TlRMNNsuj79ROcy6Mp+JwMG50HjTc0qTlXh8C7nHybDJn4v7DA+Nyn
RxT0J5I+Gqn+Na9TaC9mLeX/lwe8/KomyhBWxjrsyWj1V6v/cLO924S2rtcfzMDm
l3SFh9YHM1KF/R9N1XYBwtMzr3bupWDnE1yycYp1F4sMLr5SMzMQ0svQpQEM2/y5
kly8+eUzryhm+ag9x1686uEG5gxhQ1eHQoZEaClHUOsV+28+d5If7cqcYx9Hf5Tt
CiVjJQzdxBF+6GeiJtKxnLtevqlkbyIJt6Cm9/7YIy/ovRGF2AKSYN6oCwmZQ6i1
8nRnFq5zE7O94m+GXconWZxy0wVqA6472HThMi7S+Tk/eLYen2ilGY+KCb9a0FH5
5MOuWSoJZ8/HfW2VeQmL8EjhWm5F2ybg28wgXK4BOGR3jQi03Fsc+AFidnWxSKo0
aiJoPgOsfyu8/fnCcAi07kSmjzUKIWskApgcpGQLNXHFK9mtg7+VA8esRnfLlKtP
tJf+nNAPY1sqHfGBzh7WWGWal5RGHF5nEm3ta3oiFF5sMKCJ6C87zVwFkEcRytGC
xOGmiG1O1RPrO5NG7rZUaQ4y1OKl2Y1H+nGONzZ3mvoAOvxEq6JtUnU2kZscpPlk
fpeOSDoGBYJGbIpzDreBDhxaZrwGq36k
-----END CERTIFICATE-----

1
core/CMakeLists.txt

@ -11,6 +11,7 @@ set(CORE_SRC
"util/base64.cpp" "util/base64.cpp"
"util/util.cpp" "util/util.cpp"
"util/Log.cpp" "util/Log.cpp"
"util/HTTP.cpp"
"tunnel/TransitTunnel.cpp" "tunnel/TransitTunnel.cpp"
"tunnel/Tunnel.cpp" "tunnel/Tunnel.cpp"
"tunnel/TunnelGateway.cpp" "tunnel/TunnelGateway.cpp"

246
core/util/HTTP.cpp

@ -0,0 +1,246 @@
#include "HTTP.h"
#include <boost/algorithm/string.hpp>
#include <iostream>
#include <regex>
#include <fstream>
#include <boost/filesystem.hpp>
#include "Log.h"
namespace i2p {
namespace util {
namespace http {
void Request::parseRequestLine(const std::string& line)
{
std::stringstream ss(line);
ss >> method;
ss >> uri;
}
void Request::parseHeaderLine(const std::string& line)
{
const std::size_t pos = line.find_first_of(':');
headers[boost::trim_copy(line.substr(0, pos))] = boost::trim_copy(line.substr(pos + 1));
}
void Request::parseHeader(std::stringstream& ss)
{
std::string line;
while(std::getline(ss, line) && !boost::trim_copy(line).empty())
parseHeaderLine(line);
has_header = boost::trim_copy(line).empty();
if(!has_header)
header_part = line;
else
header_part = "";
}
void Request::setIsComplete()
{
auto it = headers.find("Content-Length");
if(it == headers.end()) {
// If Content-Length is not set, assume there is no more content
// TODO: Support chunked transfer, or explictly reject it
is_complete = true;
return;
}
const std::size_t length = std::stoi(it->second);
is_complete = content.size() >= length;
}
Request::Request(const std::string& data)
{
if(!data.empty())
has_data = true;
std::stringstream ss(data);
std::string line;
std::getline(ss, line);
// Assume the request line is always passed in one go
parseRequestLine(line);
parseHeader(ss);
if(has_header && ss) {
const std::string current = ss.str();
content = current.substr(ss.tellg());
}
if(has_header)
setIsComplete();
}
std::string Request::getMethod() const
{
return method;
}
std::string Request::getUri() const
{
return uri;
}
std::string Request::getHost() const
{
return host;
}
int Request::getPort() const
{
return port;
}
std::string Request::getHeader(const std::string& name) const
{
return headers.at(name);
}
std::string Request::getContent() const
{
return content;
}
bool Request::hasData() const
{
return has_data;
}
bool Request::isComplete() const
{
return is_complete;
}
void Request::clear()
{
has_data = false;
has_header = false;
is_complete = false;
}
void Request::update(const std::string& data)
{
std::stringstream ss(header_part + data);
if(!has_header)
parseHeader(ss);
if(has_header && ss) {
const std::string current = ss.str();
content += current.substr(ss.tellg());
}
if(has_header)
setIsComplete();
}
Response::Response(int status, const std::string& content)
: status(status), content(content), headers()
{
}
void Response::setHeader(const std::string& name, const std::string& value)
{
headers[name] = value;
}
std::string Response::toString() const
{
std::stringstream ss;
ss << "HTTP/1.1 " << status << ' ' << getStatusMessage() << "\r\n";
for(auto& pair : headers)
ss << pair.first << ": " << pair.second << "\r\n";
ss << "\r\n" << content;
return ss.str();
}
std::string Response::getStatusMessage() const
{
switch(status) {
case 105:
return "Name Not Resolved";
case 200:
return "OK";
case 400:
return "Bad Request";
case 404:
return "Not Found";
case 408:
return "Request Timeout";
case 500:
return "Internal Server Error";
case 502:
return "Not Implemented";
case 504:
return "Gateway Timeout";
default:
return std::string();
}
}
void Response::setContentLength()
{
setHeader("Content-Length", std::to_string(content.size()));
}
std::string preprocessContent(const std::string& content, const std::string& path)
{
const boost::filesystem::path directory(path); // Given path is assumed to be clean
static const std::regex re(
"<\\!\\-\\-\\s*#include\\s+virtual\\s*\\=\\s*\"([^\"]*)\"\\s*\\-\\->"
);
boost::system::error_code e;
std::string result;
std::smatch match;
auto it = content.begin();
while(std::regex_search(it, content.end(), match, re)) {
const auto last = it;
std::advance(it, match.position());
result.append(last, it);
std::advance(it, match.length());
// Read the contents of the included file
std::ifstream ifs(
boost::filesystem::canonical(directory / std::string(match[1]), e).string(),
std::ios_base::in | std::ios_base::binary
);
if(e || !ifs)
continue;
std::string data;
ifs.seekg(0, ifs.end);
data.resize(ifs.tellg());
ifs.seekg(0, ifs.beg);
ifs.read(&data[0], data.size());
result += data;
}
// Append all of the remaining content
result.append(it, content.end());
return result;
}
std::string getMimeType(const std::string& filename)
{
const std::string ext = filename.substr(filename.find_last_of("."));
if(ext == ".css")
return "text/css";
else if(ext == ".js")
return "text/javascript";
else if(ext == ".html" || ext == ".htm")
return "text/html";
else
return "application/octet-stream";
}
}
}
}

104
core/util/HTTP.h

@ -0,0 +1,104 @@
#ifndef _HTTP_H__
#define _HTTP_H__
#include <string>
#include <map>
#include <sstream>
namespace i2p {
namespace util {
namespace http {
class Request {
void parseRequestLine(const std::string& line);
void parseHeaderLine(const std::string& line);
void parseHeader(std::stringstream& ss);
void setIsComplete();
public:
Request() = default;
Request(const std::string& data);
std::string getMethod() const;
std::string getUri() const;
std::string getHost() const;
int getPort() const;
/**
* @throw std::out_of_range if no such header exists
*/
std::string getHeader(const std::string& name) const;
std::string getContent() const;
bool hasData() const;
bool isComplete() const;
void clear();
void update(const std::string& data);
private:
std::string header_part;
std::string method;
std::string uri;
std::string host;
std::string content;
int port;
std::map<std::string, std::string> headers;
bool has_data;
bool has_header;
bool is_complete;
};
class Response {
public:
Response() = default;
Response(int status, const std::string& content = "");
/**
* @note overrides existing header values with the same name
*/
void setHeader(const std::string& name, const std::string& value);
std::string toString() const;
/**
* @return the message associated with the satus of this response, or the
* empty string if the status number is invalid
*/
std::string getStatusMessage() const;
void setContentLength();
private:
int status;
std::string content;
std::map<std::string, std::string> headers;
};
/**
* Handle server side includes.
*/
std::string preprocessContent(const std::string& content, const std::string& path);
/**
* @return the MIME type based on the extension of the given filename
*/
std::string getMimeType(const std::string& filename);
}
}
}
#endif // _HTTP_H__

74
core/util/util.cpp

@ -18,7 +18,7 @@
#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__OpenBSD__) #if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__OpenBSD__)
#include <sys/types.h> #include <sys/types.h>
#include <ifaddrs.h> #include <ifaddrs.h>
#elif defined(WIN32) #elif defined(_WIN32)
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
@ -79,7 +79,7 @@ namespace config {
strKey = strKey.substr(0, has_data); strKey = strKey.substr(0, has_data);
} }
#ifdef WIN32 #ifdef _WIN32
boost::to_lower(strKey); boost::to_lower(strKey);
if(boost::algorithm::starts_with(strKey, "/")) if(boost::algorithm::starts_with(strKey, "/"))
strKey = "-" + strKey.substr(1); strKey = "-" + strKey.substr(1);
@ -125,6 +125,11 @@ namespace config {
return nDefault; return nDefault;
} }
bool HasArg(const std::string& strArg)
{
return mapArgs.count(strArg);
}
} }
namespace filesystem namespace filesystem
@ -193,28 +198,34 @@ namespace filesystem
return pathTunnelsConfigFile; return pathTunnelsConfigFile;
} }
boost::filesystem::path GetWebuiDataDir()
{
return GetDataDir() / "webui";
}
boost::filesystem::path GetDefaultDataDir() boost::filesystem::path GetDefaultDataDir()
{ {
// Custom path, or default path:
// Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd // Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd
// Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd // Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd
// Mac: ~/Library/Application Support/i2pd // Mac: ~/Library/Application Support/i2pd
// Unix: ~/.i2pd or /var/lib/i2pd is system=1 // Unix: ~/.i2pd
#ifdef I2PD_CUSTOM_DATA_PATH
#ifdef WIN32 return boost::filesystem::path(std::string(I2PD_CUSTOM_DATA_PATH));
#else
#ifdef _WIN32
// Windows // Windows
char localAppData[MAX_PATH]; char localAppData[MAX_PATH];
SHGetFolderPath(NULL, CSIDL_APPDATA, 0, NULL, localAppData); SHGetFolderPath(NULL, CSIDL_APPDATA, 0, NULL, localAppData);
return boost::filesystem::path(std::string(localAppData) + "\\" + appName); return boost::filesystem::path(std::string(localAppData) + "\\" + appName);
#else #else
if(i2p::util::config::GetArg("-service", 0)) // use system folder
return boost::filesystem::path(std::string ("/var/lib/") + appName);
boost::filesystem::path pathRet; boost::filesystem::path pathRet;
char* pszHome = getenv("HOME"); char* pszHome = getenv("HOME");
if(pszHome == NULL || strlen(pszHome) == 0) if(pszHome == NULL || strlen(pszHome) == 0)
pathRet = boost::filesystem::path("/"); pathRet = boost::filesystem::path("/");
else else
pathRet = boost::filesystem::path(pszHome); pathRet = boost::filesystem::path(pszHome);
#ifdef MAC_OSX #ifdef __APPLE__
// Mac // Mac
pathRet /= "Library/Application Support"; pathRet /= "Library/Application Support";
boost::filesystem::create_directory(pathRet); boost::filesystem::create_directory(pathRet);
@ -223,6 +234,7 @@ namespace filesystem
// Unix // Unix
return pathRet / (std::string (".") + appName); return pathRet / (std::string (".") + appName);
#endif #endif
#endif
#endif #endif
} }
@ -251,6 +263,46 @@ namespace filesystem
{ {
return GetDataDir () / "certificates"; return GetDataDir () / "certificates";
} }
void InstallFiles()
{
namespace bfs = boost::filesystem;
boost::system::error_code e;
const bfs::path source = bfs::canonical(
config::GetArg("-install", "webui"), e
);
const bfs::path destination = GetWebuiDataDir();
if(e || !bfs::is_directory(source))
throw std::runtime_error("Given directory is invalid or does not exist");
// TODO: check that destination is not in source
try {
CopyDir(source, destination);
} catch(...) {
throw std::runtime_error("Could not copy webui folder to i2pd folder.");
}
}
void CopyDir(const boost::filesystem::path& src, const boost::filesystem::path& dest)
{
namespace bfs = boost::filesystem;
bfs::create_directory(dest);
for(bfs::directory_iterator file(src); file != bfs::directory_iterator(); ++file) {
const bfs::path current(file->path());
if(bfs::is_directory(current))
CopyDir(current, dest / current.filename());
else
bfs::copy_file(
current, dest / current.filename(),
bfs::copy_option::overwrite_if_exists
);
}
}
} }
namespace http namespace http
@ -515,7 +567,7 @@ namespace net {
return mtu; return mtu;
} }
#elif defined(WIN32) #elif defined(_WIN32)
int GetMTUWindowsIpv4(sockaddr_in inputAddress, int fallback) int GetMTUWindowsIpv4(sockaddr_in inputAddress, int fallback)
{ {
ULONG outBufLen = 0; ULONG outBufLen = 0;
@ -556,7 +608,7 @@ namespace net {
LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr;
sockaddr_in* localInterfaceAddress = (sockaddr_in*) lpAddr; sockaddr_in* localInterfaceAddress = (sockaddr_in*) lpAddr;
if(localInterfaceAddress->sin_addr.S_un.S_addr == inputAddress.sin_addr.S_un.S_addr) { if(localInterfaceAddress->sin_addr.S_un.S_addr == inputAddress.sin_addr.S_un.S_addr) {
result = pAddresses->Mtu; auto result = pAddresses->Mtu;
FREE(pAddresses); FREE(pAddresses);
return result; return result;
} }
@ -618,7 +670,7 @@ namespace net {
found_address = true; found_address = true;
} }
} if (found_address) { } if (found_address) {
result = pAddresses->Mtu; auto result = pAddresses->Mtu;
FREE(pAddresses); FREE(pAddresses);
pAddresses = nullptr; pAddresses = nullptr;
return result; return result;

21
core/util/util.h

@ -41,6 +41,11 @@ namespace util
* @param nDefault the default value to be returned * @param nDefault the default value to be returned
*/ */
const char* GetCharArg(const std::string& strArg, const std::string& nDefault); const char* GetCharArg(const std::string& strArg, const std::string& nDefault);
/**
* @return true if the argument is set, false otherwise
*/
bool HasArg(const std::string& strArg);
} }
namespace filesystem namespace filesystem
@ -80,6 +85,10 @@ namespace util
*/ */
boost::filesystem::path GetDefaultDataDir(); boost::filesystem::path GetDefaultDataDir();
/**
* @return the default directory for webui data
*/
boost::filesystem::path GetWebuiDataDir();
/** /**
* Read a configuration file and store its contents in the given maps. * Read a configuration file and store its contents in the given maps.
@ -91,6 +100,18 @@ namespace util
* @return the path of the certificates directory * @return the path of the certificates directory
*/ */
boost::filesystem::path GetCertificatesDir(); boost::filesystem::path GetCertificatesDir();
/**
* Installs the webui files.
* @throw std::runtime_error when installation fails
*/
void InstallFiles();
/**
* Copies all files and directories in src to dest.
* @warning overrides existing files
*/
void CopyDir(const boost::filesystem::path& src, const boost::filesystem::path& dest);
} }
namespace http namespace http

29
doc/BUILDING.md

@ -15,7 +15,22 @@ The client should now reseed by itself.
By default, the web console is located at http://localhost:7070/. By default, the web console is located at http://localhost:7070/.
For a list of cmake options, see build/BUILD_NOTES.md For a list of cmake options, see BUILD_NOTES.md
Installing the webui
====================
If you build from source the webui files will automatically be copied to your
i2pd data path.
In some cases (such as when using binaries), you may have to manually install the
webui.
For this, run:
$ ./i2pd --install=/path/to/webui
Or, if the current directory contains a folder named "webui":
$ ./i2pd --install
Building Unit Tests Building Unit Tests
=================== ===================
@ -28,3 +43,15 @@ On Ubuntu/Debian based
To build the tests, run To build the tests, run
$ cmake .. -DWITH_TESTS=ON $ cmake .. -DWITH_TESTS=ON
CMake Options
============
Available cmake options:
* CMAKE_BUILD_TYPE -- build profile (Debug/Release)
* WITH_AESNI -- AES-NI support (ON/OFF)
* WITH_HARDENING -- enable hardening features (ON/OFF) (gcc only)
* WITH_TESTS -- build tests (ON/OFF)
* WITH_BENCHMARK -- build bechmarking code (ON/OFF)
* WITH_OPTIMIZE -- enable optimization flags (ON/OFF) (not for MSVC)
* I2PD_DATA_DIR -- directory where i2pd will store data

11
doc/BUILD_NOTES.md

@ -1,12 +1,3 @@
Build notes
===========
Available cmake options:
* CMAKE_BUILD_TYPE -- build profile (Debug/Release)
* WITH_AESNI -- AES-NI support (ON/OFF)
* WITH_HARDENING -- enable hardening features (ON/OFF) (gcc only)
Debian Debian
------ ------
@ -25,7 +16,7 @@ Optional packages:
FreeBSD FreeBSD
------- -------
Branch 9.X has gcc v4.2, that knows nothing about required c++11 standart. Branch 9.X has gcc v4.2, that knows nothing about required c++11 standard.
Required ports: Required ports:

1
doc/COMMANDLINE.md

@ -35,3 +35,4 @@ Cmdline options
* --conf= - Config file (default: ~/.i2pd/i2p.conf or /var/lib/i2pd/i2p.conf) * --conf= - Config file (default: ~/.i2pd/i2p.conf or /var/lib/i2pd/i2p.conf)
This parameter will be silently ignored if the specified config file does not exist. This parameter will be silently ignored if the specified config file does not exist.
Options specified on the command line take precedence over those in the config file. Options specified on the command line take precedence over those in the config file.
* --install= - Installs the webui files, see BUILDING.md for details.

113
tests/Utility.cpp

@ -1,5 +1,6 @@
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
#include "util/util.h" #include "util/util.h"
#include "util/HTTP.h"
BOOST_AUTO_TEST_SUITE(UtilityTests) BOOST_AUTO_TEST_SUITE(UtilityTests)
@ -88,4 +89,116 @@ BOOST_AUTO_TEST_CASE(ParseUrlPassword)
BOOST_CHECK_EQUAL(url("").pass_, ""); BOOST_CHECK_EQUAL(url("").pass_, "");
} }
BOOST_AUTO_TEST_CASE(ParseHTTPRequestNoHeaders)
{
Request req1("GET /index.html HTTP/1.1");
Request req2("POST / HTTP/1.0\r\n");
BOOST_CHECK_EQUAL(req1.getMethod(), "GET");
BOOST_CHECK_EQUAL(req1.getUri(), "/index.html");
BOOST_CHECK_EQUAL(req2.getMethod(), "POST");
BOOST_CHECK_EQUAL(req2.getUri(), "/");
}
BOOST_AUTO_TEST_CASE(ParseHTTPRequestWithHeaders)
{
Request req1(
"GET /index.html HTTP/1.1\r\n"
"Host: localhost\r\n"
);
Request req2(
"POST / HTTP/1.1\r\n"
"Host: localhost:123 \r\n"
);
BOOST_CHECK_EQUAL(req1.getHeader("Host"), "localhost");
BOOST_CHECK_EQUAL(req2.getHeader("Host"), "localhost:123");
}
BOOST_AUTO_TEST_CASE(ParseHTTPRequestWithContent)
{
Request req1(
"GET /index.html HTTP/1.1\r\n"
"Host: localhost\r\n\r\n"
"Random content."
);
Request req2(
"GET /index.html HTTP/1.0\r\n\r\n"
"Random content.\r\nTest content."
);
BOOST_CHECK_EQUAL(req1.getContent(), "Random content.");
BOOST_CHECK_EQUAL(req2.getContent(), "Random content.\r\nTest content.");
}
BOOST_AUTO_TEST_CASE(ParseHTTPRequestWithPartialHeaders)
{
Request req(
"GET /index.html HTTP/1.1\r\n"
"Host: local"
);
BOOST_CHECK(req.hasData());
BOOST_CHECK(!req.isComplete());
BOOST_CHECK_EQUAL(req.getMethod(), "GET");
req.update("host\r\n");
BOOST_CHECK(req.isComplete());
BOOST_CHECK_EQUAL(req.getHeader("Host"), "localhost");
req.clear();
BOOST_CHECK(!req.hasData());
}
BOOST_AUTO_TEST_CASE(ParseHTTPRequestHeadersFirst)
{
Request req(
"GET /index.html HTTP/1.1\r\n"
"Content-Length: 5\r\n"
"Host: localhost\r\n\r\n"
);
BOOST_CHECK_EQUAL(req.getMethod(), "GET");
BOOST_CHECK_EQUAL(req.getHeader("Content-Length"), "5");
BOOST_CHECK_EQUAL(req.getHeader("Host"), "localhost");
BOOST_CHECK(!req.isComplete());
req.update("ab");
BOOST_CHECK(!req.isComplete());
req.update("cde");
BOOST_CHECK(req.isComplete());
BOOST_CHECK_EQUAL(req.getContent(), "abcde");
}
BOOST_AUTO_TEST_CASE(HTTPResponseStatusMessage)
{
BOOST_CHECK_EQUAL(Response(0).getStatusMessage(), "");
BOOST_CHECK_EQUAL(Response(105).getStatusMessage(), "Name Not Resolved");
BOOST_CHECK_EQUAL(Response(200).getStatusMessage(), "OK");
BOOST_CHECK_EQUAL(Response(400).getStatusMessage(), "Bad Request");
BOOST_CHECK_EQUAL(Response(404).getStatusMessage(), "Not Found");
BOOST_CHECK_EQUAL(Response(408).getStatusMessage(), "Request Timeout");
BOOST_CHECK_EQUAL(Response(500).getStatusMessage(), "Internal Server Error");
BOOST_CHECK_EQUAL(Response(502).getStatusMessage(), "Not Implemented");
BOOST_CHECK_EQUAL(Response(504).getStatusMessage(), "Gateway Timeout");
}
BOOST_AUTO_TEST_CASE(WriteHTTPResponse)
{
Response rsp(200);
rsp.setHeader("Connection", "close");
BOOST_CHECK_EQUAL(
rsp.toString(),
"HTTP/1.1 200 OK\r\n"
"Connection: close\r\n\r\n"
);
}
BOOST_AUTO_TEST_CASE(WriteHTTPResponseWithContent)
{
Response rsp(200, "Test content.");
rsp.setHeader("Connection", "close");
BOOST_CHECK_EQUAL(
rsp.toString(),
"HTTP/1.1 200 OK\r\n"
"Connection: close\r\n\r\n"
"Test content."
);
}
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()

24
webui/404.html

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>404 - Page not found</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<div class="header">
<h1>404 - Page not found</h1>
</div>
<div class="content">
<h2 class="content-subhead">The page you were looking for could not be found.</h2>
</div>
<!--#include virtual="menu.html" -->
<!--#include virtual="footer.html" -->
</body>
</html>

76
webui/css/main.css

@ -0,0 +1,76 @@
body {
font-family: sans-serif;
margin: 0px;
padding: 0px;
}
.header {
margin: 0px 150px;
text-align: center;
padding: 2.5em 2em 0px;
border-bottom: 1px solid #EEE;
}
h1 {
color: #333;
font-weight: 300;
}
h2 {
color: #ccc;
font-weight: 300;
}
.content {
margin: 0px 150px auto;
padding: 0px 2em;
line-height: 1.6em;
}
.content-subhead {
margin: 50px 0px 20px;
font-weight: 300;
color: #888;
}
#menu {
width: 150px;
position: fixed;
top: 0px;
bottom: 0px;
z-index: 1000;
background: #191818 none repeat scroll 0% 0%;
overflow-y: auto;
display: block;
margin: 0px;
padding: 0px;
}
.menu-heading {
background: #1f8dd6;
display: block;
padding: 10px;
color: white;
font-variant: small-caps;
font-size: 23px;
font-weight: bold;
}
.menu-list {
list-style: none;
margin: 0;
padding: 0;
}
.menu-item {
padding: 0;
margin: 0;
padding: 10px;
}
.menu-link {
text-decoration: none;
color: #ccc;
font-weight: bold;
font-size: 15px;
}

6
webui/footer.html

@ -0,0 +1,6 @@
<div id="main">
<noscript>
<div class="header"><h1>Please enable JavaScript!</h1></div>
</noscript>
</div>

27
webui/help.html

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Purple I2P 0.10.0 Webconsole</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<div class="header">
<h1>I2P help</h1>
</div>
<div class="content">
<h2 class="content-subhead">Need help? Join us at IRC: #i2pd-dev at irc.freenode.net</h2>
<h2 class="content-subhead">
<a href="https://github.com/PurpleI2P/i2pd">i2pd at GitHub</a>
</h2>
<h2 class="content-subhead"><a href="https://geti2p.net/en/">I2P Project</a> </h2>
</div>
<!--#include virtual="menu.html" -->
<!--#include virtual="footer.html" -->
</body>
</html>

101
webui/index.html

@ -0,0 +1,101 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Purple I2P 0.10.0 Webconsole</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/main.css">
<script type="text/javascript" src="javascript/I2PControl.js"></script>
<script type="text/javascript">
function updateRouterInfo(result, session) {
if(session.error) {
alert("Error: " + session.error["message"]);
return;
}
I2PControl.updateDocument({
"version" : result["i2p.router.version"],
"status" : I2PControl.statusToString(result["i2p.router.net.status"]),
"uptime" : I2PControl.msToString(result["i2p.router.uptime"]),
"knownpeers" : result["i2p.router.netdb.knownpeers"],
"activepeers" : result["i2p.router.netdb.activepeers"],
"tunnels-participating" : result["i2p.router.net.tunnels.participating"],
"tunnels-successrate" : result["i2p.router.net.tunnels.creationsuccessrate"] + "&#37",
"bw-in" : result["i2p.router.net.bw.inbound.1s"],
"bw-out" : result["i2p.router.net.bw.outbound.1s"]
});
window.setTimeout(function() { requestRouterInfo(session); }, 5000);
}
function requestRouterInfo(session) {
session.request("RouterInfo", {
"i2p.router.version" : "",
"i2p.router.net.status" : "",
"i2p.router.uptime" : "",
"i2p.router.netdb.knownpeers" : "",
"i2p.router.netdb.activepeers" : "",
"i2p.router.net.tunnels.participating" : "",
"i2p.router.net.tunnels.creationsuccessrate" : "",
"i2p.router.net.bw.inbound.1s" : "",
"i2p.router.net.bw.outbound.1s" : ""
}, updateRouterInfo);
}
window.onload = function() {
var session = new I2PControl.Session("itoopie");
session.start(function() { requestRouterInfo(session); });
document.getElementById("shutdown").onclick = function() {
session.request("RouterManager", {"Shutdown" : ""}, function() {});
this.disabled = true;
};
document.getElementById("shutdown-graceful").onclick = function() {
session.request("RouterManager", {"ShutdownGraceful" : ""}, function() {});
this.disabled = true;
};
document.getElementById("reseed").onclick = function() {
session.request("RouterManager", {"Reseed" : ""}, function() {});
this.disabled = true;
};
};
</script>
</head>
<body>
<div class="header">
<h1>i2pd router console</h1>
<h2>Version: <span id="version"></span>, uptime: <span id="uptime"></span></h2>
<h2>Network status: <span id="status"></span></h2>
<p>
<button id="shutdown-graceful">graceful shutdown</button>
<button id="shutdown">force shutdown</button>
<button id="restart" disabled>restart</button>
<button id="reseed">reseed</button>
</p>
</div>
<div class="content">
<h2 class="content-subhead">
Tunnels participating: <span id="tunnels-participating"></span>
</h2>
<h2 class="content-subhead">
Tunnel create success rate: <span id="tunnels-successrate"></span>
</h2>
<h2 class="content-subhead">
Active peers: <span id="activepeers"></span>
</h2>
<h2 class="content-subhead">
Known peers: <span id="knownpeers"></span>
</h2>
<h2 class="content-subhead">
Bandwidth:
in <span id="bw-in"></span> Bps /
out <span id="bw-out"></span> Bps
</h2>
</div>
<!--#include virtual="menu.html" -->
<!--#include virtual="footer.html" -->
</body>
</html>

102
webui/javascript/I2PControl.js

@ -0,0 +1,102 @@
var I2PControl = I2PControl || {}
I2PControl.Session = function(password) {
this.token = "";
this.ready = false;
this.error = false;
this.password = password;
};
I2PControl.Session.prototype = {
request : function(method, params, handler) {
var request = new XMLHttpRequest();
request.open("POST", "", true);
request.setRequestHeader('Content-Type', 'application/json');
var self = this;
request.onreadystatechange = function() {
if(this.readyState == 4 && this.status == "200" && this.responseText != "") {
var data = JSON.parse(this.responseText);
if(data.hasOwnProperty("error")) {
if(data["error"]["code"] == -32003 || data["error"]["code"] == -32004) {
// Get a new token and resend the request
self.start(function() {
self.request(method, params, handler);
});
return;
}
// Cannot fix the error, report it
self.error = data["error"];
}
handler(data.result, self);
}
};
if(this.token != "")
params["Token"] = this.token;
var rpc = {
"id" : 0,
"method" : method ,
"params" : params,
"jsonrpc": "2.0"
}
request.send(JSON.stringify(rpc));
},
start : function(onReady) {
var self = this;
var handleAuthenticate = function(result) {
self.token = result["Token"];
self.ready = true;
onReady();
};
this.request(
"Authenticate",
{"API" : 1, "Password" : this.password},
handleAuthenticate
);
},
};
I2PControl.statusToString = function(status) {
switch(status) {
case 0: return "OK";
case 1: return "TESTING";
case 2: return "FIREWALLED";
case 3: return "HIDDEN";
case 4: return "WARN_FIREWALLED_AND_FAST";
case 5: return "WARN_FIREWALLED_AND_FLOODFILL";
case 6: return "WARN_FIREWALLED_WITH_INBOUND_TCP";
case 7: return "WARN_FIREWALLED_WITH_UDP_DISABLED";
case 8: return "ERROR_I2CP";
case 9: return "ERROR_CLOCK_SKEW";
case 10: return "ERROR_PRIVATE_TCP_ADDRESS";
case 11: return "ERROR_SYMMETRIC_NAT";
case 12: return "ERROR_UDP_PORT_IN_USE";
case 13: return "ERROR_NO_ACTIVE_PEERS_CHECK_CONNECTION_AND_FIREWALL";
case 14: return "ERROR_UDP_DISABLED_AND_TCP_UNSET";
default: return "UNKNOWN";
}
};
I2PControl.msToString = function(mseconds) {
var seconds = mseconds / 1000;
var numdays = Math.floor(seconds / 86400);
var numhours = Math.floor((seconds % 86400) / 3600);
var numminutes = Math.floor(((seconds % 86400) % 3600) / 60);
var numseconds = ((seconds % 86400) % 3600) % 60;
return numdays + "d " + numhours + "h " + numminutes + "m " + numseconds + "s";
}
I2PControl.updateDocument = function(values) {
for(id in values) {
if(!values.hasOwnProperty(id))
continue;
document.getElementById(id).innerHTML = values[id];
}
};

20
webui/menu.html

@ -0,0 +1,20 @@
<div id="menu">
<span class="menu-heading">i2pd</span>
<ul class="menu-list">
<li class="menu-item">
<a href="index.html" class="menu-link">Home</a>
</li>
<li class="menu-item">
<a href="netdb.html" class="menu-link">Network Database</a>
</li>
<li class="menu-item">
<a href="tunnels.html" class="menu-link">Tunnels</a>
</li>
<li class="menu-item">
<a href="config.html" class="menu-link">Configure</a>
</li>
<li class="menu-item">
<a href="help.html" class="menu-link">Help</a>
</li>
</ul>
</div>

59
webui/netdb.html

@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Purple I2P 0.10.0 Webconsole</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/main.css">
<script type="text/javascript" src="javascript/I2PControl.js"></script>
<script type="text/javascript">
function updateNetDbInfo(result, session) {
if(session.error) {
alert("Error: " + session.error["message"]);
return;
}
I2PControl.updateDocument({
"knownpeers" : result["i2p.router.netdb.knownpeers"],
"activepeers" : result["i2p.router.netdb.activepeers"],
"floodfills" : result["i2p.router.netdb.floodfills"],
"leasesets" : result["i2p.router.netdb.leasesets"],
});
window.setTimeout(function() { requestNetDbInfo(session); }, 10000);
}
function requestNetDbInfo(session) {
session.request("RouterInfo", {
"i2p.router.netdb.knownpeers" : "",
"i2p.router.netdb.activepeers" : "",
"i2p.router.netdb.floodfills" : "",
"i2p.router.netdb.leasesets" : "",
}, updateNetDbInfo);
}
window.onload = function() {
var session = new I2PControl.Session("itoopie");
session.start(function() { requestNetDbInfo(session); });
};
</script>
</head>
<body>
<div class="header">
<h1>i2pd router console</h1>
<h2>Network Database Information</h2>
</div>
<div class="content">
<h2 class="content-subhead">Active peers: <span id="activepeers"></span></h2>
<h2 class="content-subhead">Known peers: <span id="knownpeers"></span></h2>
<h2 class="content-subhead">Floodfills: <span id="floodfills"></span></h2>
</br>
<h2 class="content-subhead">LeaseSets: <span id="leasesets"></span></h2>
</div>
<!--#include virtual="menu.html" -->
<!--#include virtual="footer.html" -->
</body>
</html>

87
webui/tunnels.html

@ -0,0 +1,87 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Purple I2P 0.10.0 Webconsole</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/main.css">
<script type="text/javascript" src="javascript/I2PControl.js"></script>
<script type="text/javascript">
function buildTable(direction, result) {
var table = document.getElementById(direction + "-tunnels").getElementsByTagName("tbody")[0];
var cmd = "i2p.router.net.tunnels." + direction + ".list";
for(id in result[cmd]) {
if(!result[cmd].hasOwnProperty(id))
continue;
var tunnel = result[cmd][id];
var row = table.insertRow(table.rows.length);
row.insertCell(0).appendChild(document.createTextNode(id));
row.insertCell(1).appendChild(document.createTextNode(tunnel["state"] ? tunnel["state"] : "running"));
row.insertCell(2).appendChild(document.createTextNode(tunnel["layout"]));
}
}
function updateTunnelInfo(result, session) {
if(session.error) {
alert("Error: " + session.error["message"]);
return;
}
buildTable("inbound", result);
buildTable("outbound", result);
}
function requestTunnelInfo(session) {
session.request("RouterInfo", {
"i2p.router.net.tunnels.inbound.list" : "",
"i2p.router.net.tunnels.outbound.list" : "",
}, updateTunnelInfo);
}
window.onload = function() {
var session = new I2PControl.Session("itoopie");
session.start(function() { requestTunnelInfo(session); });
};
</script>
</head>
<body>
<div class="header">
<h1>i2pd router console</h1>
<h2>Tunnel Information</h2>
</div>
<div class="content">
<h2 class="content-subhead">Inbound Tunnels</h2>
<table id="inbound-tunnels">
<thead>
<th>Tunnel ID</th>
<th>Status</th>
<th>Overview</th>
</thead>
<tbody>
</tbody>
</table>
<h2 class="content-subhead">Outbound Tunnels</h2>
<table id="outbound-tunnels">
<thead>
<th>Tunnel ID</th>
<th>Status</th>
<th>Overview</th>
</thead>
<tbody>
</tbody>
</table>
</div>
<!--#include virtual="menu.html" -->
<!--#include virtual="footer.html" -->
</body>
</html>
Loading…
Cancel
Save