Merge pull request #271 from EinMByte/master

Benchmarking, webui and windows build
This commit is contained in:
EinMByte 2015-09-27 19:49:45 +02:00
commit af66f335c9
36 changed files with 1830 additions and 1247 deletions

2
.gitignore vendored
View File

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

View File

@ -9,6 +9,9 @@ option(WITH_BINARY "Build binary" ON)
option(WITH_STATIC "Static build" OFF)
option(WITH_UPNP "Include support for UPnP client" 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_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
@ -80,6 +83,9 @@ if(WITH_AESNI)
add_definitions( "-maes -DAESNI")
endif()
if(WITH_OPTIMIZE AND (NOT MSVC))
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
endif()
# Libraries
# TODO: once CMake 3.1+ becomes mainstream, see e.g.
@ -93,17 +99,8 @@ endif()
if(WITH_STATIC)
set(Boost_USE_STATIC_LIBS ON)
set(Boost_USE_STATIC_RUNTIME ON)
if(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(Boost_USE_STATIC_RUNTIME OFF)
if(NOT WIN32)
set(CMAKE_FIND_LIBRARY_SUFFIXES .a)
endif()
@ -120,10 +117,10 @@ elseif(NOT WIN32)
# No need in -fPIC overhead for binary if not interested in library
# HINT: revert c266cff CMakeLists.txt: compilation speed up
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
else() # Not a static build
add_definitions(-DBOOST_ALL_DYN_LINK)
endif()
add_definitions(-DBOOST_ALL_DYN_LINK)
find_package(
Boost COMPONENTS
system filesystem regex program_options date_time thread chrono REQUIRED
@ -148,6 +145,18 @@ include_directories(
"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
message(STATUS "---------------------------------------")
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 path : ${CMAKE_CXX_COMPILER}")
message(STATUS "Install prefix: : ${CMAKE_INSTALL_PREFIX}")
message(STATUS "I2PD data directory: ${I2PD_DATA_DIR}")
message(STATUS "Options:")
message(STATUS " AESNI : ${WITH_AESNI}")
message(STATUS " HARDENING : ${WITH_HARDENING}")
@ -163,6 +173,8 @@ message(STATUS " BINARY : ${WITH_BINARY}")
message(STATUS " STATIC BUILD : ${WITH_STATIC}")
message(STATUS " UPnP : ${WITH_UPNP}")
message(STATUS " TESTS : ${WITH_TESTS}")
message(STATUS " BENCHMARKING : ${WITH_BENCHMARK}")
message(STATUS " OPTIMIZATION : ${WITH_OPTIMIZE}")
message(STATUS "---------------------------------------")
# Handle paths nicely
@ -171,6 +183,13 @@ include(GNUInstallDirs)
set(CORE_NAME "${PROJECT_NAME}-core")
set(CLIENT_NAME "${PROJECT_NAME}-client")
set(TESTS_NAME "${PROJECT_NAME}-tests")
set(BENCHMARK_NAME "${PROJECT_NAME}-benchmark")
add_subdirectory(core)
add_subdirectory(client)
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()

View File

@ -15,13 +15,7 @@ http://download.i2p.io/purplei2p/i2pd/releases/
Build Statuses
---------------
- Linux x64 - Maintaince
- Linux ARM - Maintaince
- Mac OS X - Maintaince
- Linux x64 - Maintenance
- Linux ARM - Maintenance
- Mac OS X - Maintenance
- 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 Normal file
View File

@ -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 Normal file
View File

@ -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
);
}

View File

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

View File

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

View File

@ -56,7 +56,19 @@ namespace i2p
LogPrint("\n\n\n\ni2pd starting\n");
LogPrint("Version ", VERSION);
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);
isLogging = i2p::util::config::GetArg("-log", 1);
@ -104,7 +116,6 @@ namespace i2p
else
StartLog (""); // write to stdout
}
d.httpServer = new i2p::util::HTTPServer(
i2p::util::config::GetArg("-httpaddress", "127.0.0.1"),
i2p::util::config::GetArg("-httpport", 7070)

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -6,119 +6,80 @@
#include <memory>
#include <boost/asio.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>
{
protected:
namespace i2p {
namespace util {
struct header
{
std::string name;
std::string value;
};
const size_t HTTP_CONNECTION_BUFFER_SIZE = 8192;
const int HTTP_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds
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;
};
class HTTPConnection: public std::enable_shared_from_this<HTTPConnection> {
public:
struct reply
{
std::vector<header> headers;
std::string content;
HTTPConnection(boost::asio::ip::tcp::socket* socket,
std::shared_ptr<i2p::client::i2pcontrol::I2PControlSession> session);
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; }
void Receive ();
private:
void Terminate ();
void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleWriteReply(const boost::system::error_code& ecode);
void SendReply (const std::string& content, int status = 200);
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:
~HTTPConnection() { delete m_Socket; }
void Receive();
virtual void RunRequest ();
private:
public:
void Terminate();
void HandleReceive(const boost::system::error_code& ecode, std::size_t bytes_transferred);
void RunRequest();
void HandleWriteReply(const boost::system::error_code& ecode);
void SendReply();
static const std::string itoopieImage;
static const std::string itoopieFavicon;
};
void Send404Reply();
class HTTPServer
{
public:
/*
* @throw std::runtime_error when the file is not accessible
*/
std::string GetFileContents(const std::string& filename, bool preprocess) const;
HTTPServer (const std::string& address, int port);
virtual ~HTTPServer ();
void HandleRequest();
void HandleI2PControlRequest();
void ExtractParams(const std::string& str, std::map<std::string, std::string>& params);
bool isAllowed(const std::string& address) const;
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;
};
void Start ();
void Stop ();
class HTTPServer {
public:
private:
HTTPServer(const std::string& address, int port);
virtual ~HTTPServer();
void Run ();
void Accept ();
void HandleAccept(const boost::system::error_code& ecode);
private:
void Start();
void Stop();
std::thread * m_Thread;
boost::asio::io_service m_Service;
boost::asio::io_service::work m_Work;
boost::asio::ip::tcp::acceptor m_Acceptor;
boost::asio::ip::tcp::socket * m_NewSocket;
private:
protected:
virtual void CreateConnection(boost::asio::ip::tcp::socket * m_NewSocket);
};
void Run();
void Accept();
void HandleAccept(const boost::system::error_code& ecode);
private:
std::thread * m_Thread;
boost::asio::io_service m_Service;
boost::asio::io_service::work m_Work;
boost::asio::ip::tcp::acceptor m_Acceptor;
boost::asio::ip::tcp::socket * m_NewSocket;
std::shared_ptr<i2p::client::i2pcontrol::I2PControlSession> m_Session;
protected:
void CreateConnection(boost::asio::ip::tcp::socket* m_NewSocket);
};
}
}

View File

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

View File

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

View File

@ -11,6 +11,7 @@
#include <cryptopp/filters.h>
#include <boost/property_tree/json_parser.hpp>
#include "util/util.h"
#include "util/Log.h"
#include "util/Timestamp.h"
#include "transport/Transports.h"
@ -21,6 +22,65 @@
namespace i2p {
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)
: 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();
}
void I2PControlSession::Response::setParam(const std::string& param, const JsonObject& value)
{
parameters[param] = value.toString();
}
void I2PControlSession::Response::setError(ErrorCode code)
{
error = code;
@ -104,28 +169,35 @@ I2PControlSession::I2PControlSession(boost::asio::io_service& ios, const std::st
: password(pass), tokens(), tokensMutex(),
service(ios), shutdownTimer(ios), expireTokensTimer(ios)
{
using namespace i2p::client::i2pcontrol::constants;
// Method handlers
methodHandlers[I2P_CONTROL_METHOD_AUTHENTICATE] = &I2PControlSession::handleAuthenticate;
methodHandlers[I2P_CONTROL_METHOD_ECHO] = &I2PControlSession::handleEcho;
methodHandlers[I2P_CONTROL_METHOD_I2PCONTROL] = &I2PControlSession::handleI2PControl;
methodHandlers[I2P_CONTROL_METHOD_ROUTER_INFO] = &I2PControlSession::handleRouterInfo;
methodHandlers[I2P_CONTROL_METHOD_ROUTER_MANAGER] = &I2PControlSession::handleRouterManager;
methodHandlers[I2P_CONTROL_METHOD_NETWORK_SETTING] = &I2PControlSession::handleNetworkSetting;
methodHandlers[METHOD_AUTHENTICATE] = &I2PControlSession::handleAuthenticate;
methodHandlers[METHOD_ECHO] = &I2PControlSession::handleEcho;
methodHandlers[METHOD_I2PCONTROL] = &I2PControlSession::handleI2PControl;
methodHandlers[METHOD_ROUTER_INFO] = &I2PControlSession::handleRouterInfo;
methodHandlers[METHOD_ROUTER_MANAGER] = &I2PControlSession::handleRouterManager;
methodHandlers[METHOD_NETWORK_SETTING] = &I2PControlSession::handleNetworkSetting;
// RouterInfo handlers
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_UPTIME] = &I2PControlSession::handleUptime;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_VERSION] = &I2PControlSession::handleVersion;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_STATUS] = &I2PControlSession::handleStatus;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS]= &I2PControlSession::handleNetDbKnownPeers;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS] = &I2PControlSession::handleNetDbActivePeers;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_NET_STATUS] = &I2PControlSession::handleNetStatus;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING] = &I2PControlSession::handleTunnelsParticipating;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_BW_IB_1S] = &I2PControlSession::handleInBandwidth1S;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_BW_OB_1S] = &I2PControlSession::handleOutBandwidth1S;
routerInfoHandlers[ROUTER_INFO_UPTIME] = &I2PControlSession::handleUptime;
routerInfoHandlers[ROUTER_INFO_VERSION] = &I2PControlSession::handleVersion;
routerInfoHandlers[ROUTER_INFO_STATUS] = &I2PControlSession::handleStatus;
routerInfoHandlers[ROUTER_INFO_DATAPATH] = &I2PControlSession::handleDatapath;
routerInfoHandlers[ROUTER_INFO_NETDB_KNOWNPEERS]= &I2PControlSession::handleNetDbKnownPeers;
routerInfoHandlers[ROUTER_INFO_NETDB_ACTIVEPEERS] = &I2PControlSession::handleNetDbActivePeers;
routerInfoHandlers[ROUTER_INFO_NETDB_LEASESETS] = &I2PControlSession::handleNetDbLeaseSets;
routerInfoHandlers[ROUTER_INFO_NETDB_FLOODFILLS] = &I2PControlSession::handleNetDbFloodfills;
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
routerManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN] = &I2PControlSession::handleShutdown;
routerManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL] = &I2PControlSession::handleShutdownGraceful;
routerManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_RESEED] = &I2PControlSession::handleReseed;
routerManagerHandlers[ROUTER_MANAGER_SHUTDOWN] = &I2PControlSession::handleShutdown;
routerManagerHandlers[ROUTER_MANAGER_SHUTDOWN_GRACEFUL] = &I2PControlSession::handleShutdownGraceful;
routerManagerHandlers[ROUTER_MANAGER_RESEED] = &I2PControlSession::handleReseed;
}
void I2PControlSession::start()
@ -147,9 +219,9 @@ I2PControlSession::Response I2PControlSession::handleRequest(std::stringstream&
Response response;
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);
if(it == methodHandlers.end()) { // Not found
LogPrint(eLogWarning, "Unknown I2PControl method ", method);
@ -157,8 +229,8 @@ I2PControlSession::Response I2PControlSession::handleRequest(std::stringstream&
return response;
}
PropertyTree params = pt.get_child(I2P_CONTROL_PROPERTY_PARAMS);
if(method != I2P_CONTROL_METHOD_AUTHENTICATE && !authenticate(params, response)) {
PropertyTree params = pt.get_child(constants::PROPERTY_PARAMS);
if(method != constants::METHOD_AUTHENTICATE && !authenticate(params, response)) {
LogPrint(eLogWarning, "I2PControl invalid token presented");
return response;
}
@ -177,14 +249,14 @@ I2PControlSession::Response I2PControlSession::handleRequest(std::stringstream&
bool I2PControlSession::authenticate(const PropertyTree& pt, Response& response)
{
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);
auto it = tokens.find(token);
if(it == tokens.end()) {
response.setError(ErrorCode::NonexistentToken);
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);
return false;
}
@ -199,12 +271,12 @@ bool I2PControlSession::authenticate(const PropertyTree& pt, Response& response)
std::string I2PControlSession::generateToken() const
{
byte random_data[I2P_CONTROL_TOKEN_SIZE] = {};
byte random_data[constants::TOKEN_SIZE] = {};
CryptoPP::AutoSeededRandomPool rng;
rng.GenerateBlock(random_data, I2P_CONTROL_TOKEN_SIZE);
rng.GenerateBlock(random_data, constants::TOKEN_SIZE);
std::string token;
CryptoPP::StringSource ss(
random_data, I2P_CONTROL_TOKEN_SIZE, true,
random_data, constants::TOKEN_SIZE, true,
new CryptoPP::HexEncoder(new CryptoPP::StringSink(token))
);
return token;
@ -212,8 +284,8 @@ std::string I2PControlSession::generateToken() const
void I2PControlSession::handleAuthenticate(const PropertyTree& pt, Response& response)
{
const int api = pt.get<int>(I2P_CONTROL_PARAM_API);
const std::string given_pass = pt.get<std::string>(I2P_CONTROL_PARAM_PASSWORD);
const int api = pt.get<int>(constants::PARAM_API);
const std::string given_pass = pt.get<std::string>(constants::PARAM_PASSWORD);
LogPrint(eLogDebug, "I2PControl Authenticate API = ", api, " Password = ", given_pass);
if(given_pass != password) {
LogPrint(
@ -224,8 +296,8 @@ void I2PControlSession::handleAuthenticate(const PropertyTree& pt, Response& res
return;
}
const std::string token = generateToken();
response.setParam(I2P_CONTROL_PARAM_API, api);
response.setParam(I2P_CONTROL_PARAM_TOKEN, token);
response.setParam(constants::PARAM_API, api);
response.setParam(constants::PARAM_TOKEN, token);
std::lock_guard<std::mutex> lock(tokensMutex);
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)
{
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);
response.setParam(I2P_CONTROL_PARAM_RESULT, echo);
response.setParam(constants::PARAM_RESULT, echo);
}
void I2PControlSession::handleI2PControl(const PropertyTree&, Response&)
@ -249,7 +321,7 @@ void I2PControlSession::handleRouterInfo(const PropertyTree& pt, Response& respo
{
LogPrint(eLogDebug, "I2PControl RouterInfo");
for(const auto& pair : pt) {
if(pair.first == I2P_CONTROL_PARAM_TOKEN)
if(pair.first == constants::PARAM_TOKEN)
continue;
LogPrint(eLogDebug, pair.first);
auto it = routerInfoHandlers.find(pair.first);
@ -266,7 +338,7 @@ void I2PControlSession::handleRouterManager(const PropertyTree& pt, Response& re
{
LogPrint(eLogDebug, "I2PControl RouterManager");
for(const auto& pair : pt) {
if(pair.first == I2P_CONTROL_PARAM_TOKEN)
if(pair.first == constants::PARAM_TOKEN)
continue;
LogPrint(eLogDebug, pair.first);
auto it = routerManagerHandlers.find(pair.first);
@ -286,53 +358,112 @@ void I2PControlSession::handleNetworkSetting(const PropertyTree&, 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)
{
response.setParam(I2P_CONTROL_ROUTER_INFO_VERSION, VERSION);
response.setParam(constants::ROUTER_INFO_VERSION, VERSION);
}
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)
{
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)
{
response.setParam(
I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS,
constants::ROUTER_INFO_NETDB_ACTIVEPEERS,
(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)
{
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)
{
response.setParam(
I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING,
constants::ROUTER_INFO_TUNNELS_PARTICIPATING,
(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)
{
response.setParam(
I2P_CONTROL_ROUTER_INFO_BW_IB_1S,
constants::ROUTER_INFO_BW_IB_1S,
(double)i2p::transport::transports.GetInBandwidth()
);
}
@ -340,7 +471,7 @@ void I2PControlSession::handleInBandwidth1S(Response& response)
void I2PControlSession::handleOutBandwidth1S(Response& response)
{
response.setParam(
I2P_CONTROL_ROUTER_INFO_BW_OB_1S,
constants::ROUTER_INFO_BW_OB_1S,
(double)i2p::transport::transports.GetOutBandwidth()
);
}
@ -348,7 +479,7 @@ void I2PControlSession::handleOutBandwidth1S(Response& response)
void I2PControlSession::handleShutdown(Response& response)
{
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
shutdownTimer.expires_from_now(boost::posix_time::seconds(1));
shutdownTimer.async_wait([](const boost::system::error_code&) {
@ -361,7 +492,7 @@ void I2PControlSession::handleShutdownGraceful(Response& response)
i2p::context.SetAcceptsTunnels(false);
int timeout = i2p::tunnel::tunnels.GetTransitTunnelsExpirationTimeout();
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.async_wait([](const boost::system::error_code&) {
Daemon.running = 0;
@ -371,7 +502,7 @@ void I2PControlSession::handleShutdownGraceful(Response& response)
void I2PControlSession::handleReseed(Response& response)
{
LogPrint(eLogInfo, "Reseed requested");
response.setParam(I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN, "");
response.setParam(constants::ROUTER_MANAGER_SHUTDOWN, "");
i2p::data::netdb.Reseed();
}
@ -385,7 +516,7 @@ void I2PControlSession::expireTokens(const boost::system::error_code& error)
const uint64_t now = util::GetSecondsSinceEpoch();
std::lock_guard<std::mutex> lock(tokensMutex);
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);
else
++it;
@ -394,7 +525,7 @@ void I2PControlSession::expireTokens(const boost::system::error_code& error)
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(
&I2PControlSession::expireTokens, shared_from_this(), std::placeholders::_1
));
@ -402,3 +533,4 @@ void I2PControlSession::startExpireTokensJob()
}
}
}

View File

@ -9,52 +9,94 @@
#include <boost/asio.hpp>
namespace i2p {
// Forward declaration
namespace tunnel { class Tunnel; }
namespace client {
namespace i2pcontrol {
const char I2P_CONTROL_DEFAULT_PASSWORD[] = "itoopie";
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
namespace constants {
const char I2P_CONTROL_PROPERTY_ID[] = "id";
const char I2P_CONTROL_PROPERTY_METHOD[] = "method";
const char I2P_CONTROL_PROPERTY_PARAMS[] = "params";
const char I2P_CONTROL_PROPERTY_RESULT[] = "result";
const char DEFAULT_PASSWORD[] = "itoopie";
const uint64_t TOKEN_LIFETIME = 600; // Token lifetime in seconds
const std::size_t TOKEN_SIZE = 8; // Token size in bytes
const char PROPERTY_ID[] = "id";
const char PROPERTY_METHOD[] = "method";
const char PROPERTY_PARAMS[] = "params";
const char PROPERTY_RESULT[] = "result";
// methods
const char I2P_CONTROL_METHOD_AUTHENTICATE[] = "Authenticate";
const char I2P_CONTROL_METHOD_ECHO[] = "Echo";
const char I2P_CONTROL_METHOD_I2PCONTROL[] = "I2PControl";
const char I2P_CONTROL_METHOD_ROUTER_INFO[] = "RouterInfo";
const char I2P_CONTROL_METHOD_ROUTER_MANAGER[] = "RouterManager";
const char I2P_CONTROL_METHOD_NETWORK_SETTING[] = "NetworkSetting";
const char METHOD_AUTHENTICATE[] = "Authenticate";
const char METHOD_ECHO[] = "Echo";
const char METHOD_I2PCONTROL[] = "I2PControl";
const char METHOD_ROUTER_INFO[] = "RouterInfo";
const char METHOD_ROUTER_MANAGER[] = "RouterManager";
const char METHOD_NETWORK_SETTING[] = "NetworkSetting";
// params
const char I2P_CONTROL_PARAM_API[] = "API";
const char I2P_CONTROL_PARAM_PASSWORD[] = "Password";
const char I2P_CONTROL_PARAM_TOKEN[] = "Token";
const char I2P_CONTROL_PARAM_ECHO[] = "Echo";
const char I2P_CONTROL_PARAM_RESULT[] = "Result";
const char PARAM_API[] = "API";
const char PARAM_PASSWORD[] = "Password";
const char PARAM_TOKEN[] = "Token";
const char PARAM_ECHO[] = "Echo";
const char PARAM_RESULT[] = "Result";
// I2PControl
const char I2P_CONTROL_I2PCONTROL_ADDRESS[] = "i2pcontrol.address";
const char I2P_CONTROL_I2PCONTROL_PASSWORD[] = "i2pcontrol.password";
const char I2P_CONTROL_I2PCONTROL_PORT[] = "i2pcontrol.port";
const char I2PCONTROL_ADDRESS[] = "i2pcontrol.address";
const char I2PCONTROL_PASSWORD[] = "i2pcontrol.password";
const char I2PCONTROL_PORT[] = "i2pcontrol.port";
// RouterInfo requests
const char I2P_CONTROL_ROUTER_INFO_UPTIME[] = "i2p.router.uptime";
const char I2P_CONTROL_ROUTER_INFO_VERSION[] = "i2p.router.version";
const char I2P_CONTROL_ROUTER_INFO_STATUS[] = "i2p.router.status";
const char I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS[] = "i2p.router.netdb.knownpeers";
const char I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS[] = "i2p.router.netdb.activepeers";
const char I2P_CONTROL_ROUTER_INFO_NET_STATUS[] = "i2p.router.net.status";
const char I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING[] = "i2p.router.net.tunnels.participating";
const char I2P_CONTROL_ROUTER_INFO_BW_IB_1S[] = "i2p.router.net.bw.inbound.1s";
const char I2P_CONTROL_ROUTER_INFO_BW_OB_1S[] = "i2p.router.net.bw.outbound.1s";
const char ROUTER_INFO_UPTIME[] = "i2p.router.uptime";
const char ROUTER_INFO_VERSION[] = "i2p.router.version";
const char ROUTER_INFO_STATUS[] = "i2p.router.status";
const char ROUTER_INFO_DATAPATH[] = "i2p.router.datapath";
const char ROUTER_INFO_NETDB_KNOWNPEERS[] = "i2p.router.netdb.knownpeers";
const char ROUTER_INFO_NETDB_ACTIVEPEERS[] = "i2p.router.netdb.activepeers";
const char ROUTER_INFO_NETDB_FLOODFILLS[] = "i2p.router.netdb.floodfills";
const char ROUTER_INFO_NETDB_LEASESETS[] = "i2p.router.netdb.leasesets";
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
const char I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN[] = "Shutdown";
const char I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL[] = "ShutdownGraceful";
const char I2P_CONTROL_ROUTER_MANAGER_RESEED[] = "Reseed";
const char ROUTER_MANAGER_SHUTDOWN[] = "Shutdown";
const char ROUTER_MANAGER_SHUTDOWN_GRACEFUL[] = "ShutdownGraceful";
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.
@ -97,9 +139,22 @@ public:
* @todo escape quotes
*/
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);
/**
* Set an ouptut parameter to a specified double.
*/
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 setId(const std::string& identifier);
@ -113,7 +168,7 @@ public:
* the lifetime of this I2PControlSession.
*/
I2PControlSession(boost::asio::io_service& ios,
const std::string& pass = I2P_CONTROL_DEFAULT_PASSWORD);
const std::string& pass = constants::DEFAULT_PASSWORD);
/**
* Starts the I2PControlSession.
@ -172,10 +227,18 @@ private:
void handleUptime(Response& response);
void handleVersion(Response& response);
void handleStatus(Response& response);
void handleDatapath(Response& response);
void handleNetDbKnownPeers(Response& response);
void handleNetDbActivePeers(Response& response);
void handleNetDbFloodfills(Response& response);
void handleNetDbLeaseSets(Response& response);
void handleNetStatus(Response& response);
void handleTunnelsParticipating(Response& response);
void handleTunnelsCreationSuccess(Response& response);
void handleTunnelsInList(Response& response);
void handleTunnelsOutList(Response& response);
void handleInBandwidth1S(Response& response);
void handleOutBandwidth1S(Response& response);
@ -198,6 +261,7 @@ private:
boost::asio::deadline_timer expireTokensTimer;
};
}
}
}

View File

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

View File

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

View File

@ -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-----

View File

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

246
core/util/HTTP.cpp Normal file
View File

@ -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 Normal file
View File

@ -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__

View File

@ -18,7 +18,7 @@
#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__OpenBSD__)
#include <sys/types.h>
#include <ifaddrs.h>
#elif defined(WIN32)
#elif defined(_WIN32)
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
@ -79,7 +79,7 @@ namespace config {
strKey = strKey.substr(0, has_data);
}
#ifdef WIN32
#ifdef _WIN32
boost::to_lower(strKey);
if(boost::algorithm::starts_with(strKey, "/"))
strKey = "-" + strKey.substr(1);
@ -125,6 +125,11 @@ namespace config {
return nDefault;
}
bool HasArg(const std::string& strArg)
{
return mapArgs.count(strArg);
}
}
namespace filesystem
@ -193,28 +198,34 @@ namespace filesystem
return pathTunnelsConfigFile;
}
boost::filesystem::path GetWebuiDataDir()
{
return GetDataDir() / "webui";
}
boost::filesystem::path GetDefaultDataDir()
{
// Custom path, or default path:
// Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd
// Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd
// Mac: ~/Library/Application Support/i2pd
// Unix: ~/.i2pd or /var/lib/i2pd is system=1
#ifdef WIN32
// Unix: ~/.i2pd
#ifdef I2PD_CUSTOM_DATA_PATH
return boost::filesystem::path(std::string(I2PD_CUSTOM_DATA_PATH));
#else
#ifdef _WIN32
// Windows
char localAppData[MAX_PATH];
SHGetFolderPath(NULL, CSIDL_APPDATA, 0, NULL, localAppData);
return boost::filesystem::path(std::string(localAppData) + "\\" + appName);
#else
if(i2p::util::config::GetArg("-service", 0)) // use system folder
return boost::filesystem::path(std::string ("/var/lib/") + appName);
boost::filesystem::path pathRet;
char* pszHome = getenv("HOME");
if(pszHome == NULL || strlen(pszHome) == 0)
pathRet = boost::filesystem::path("/");
pathRet = boost::filesystem::path("/");
else
pathRet = boost::filesystem::path(pszHome);
#ifdef MAC_OSX
#ifdef __APPLE__
// Mac
pathRet /= "Library/Application Support";
boost::filesystem::create_directory(pathRet);
@ -223,6 +234,7 @@ namespace filesystem
// Unix
return pathRet / (std::string (".") + appName);
#endif
#endif
#endif
}
@ -251,6 +263,46 @@ namespace filesystem
{
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
@ -515,7 +567,7 @@ namespace net {
return mtu;
}
#elif defined(WIN32)
#elif defined(_WIN32)
int GetMTUWindowsIpv4(sockaddr_in inputAddress, int fallback)
{
ULONG outBufLen = 0;
@ -556,7 +608,7 @@ namespace net {
LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr;
sockaddr_in* localInterfaceAddress = (sockaddr_in*) lpAddr;
if(localInterfaceAddress->sin_addr.S_un.S_addr == inputAddress.sin_addr.S_un.S_addr) {
result = pAddresses->Mtu;
auto result = pAddresses->Mtu;
FREE(pAddresses);
return result;
}
@ -618,7 +670,7 @@ namespace net {
found_address = true;
}
} if (found_address) {
result = pAddresses->Mtu;
auto result = pAddresses->Mtu;
FREE(pAddresses);
pAddresses = nullptr;
return result;

View File

@ -41,6 +41,11 @@ namespace util
* @param nDefault the default value to be returned
*/
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
@ -80,6 +85,10 @@ namespace util
*/
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.
@ -91,6 +100,18 @@ namespace util
* @return the path of the certificates directory
*/
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

View File

@ -15,7 +15,22 @@ The client should now reseed by itself.
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
===================
@ -28,3 +43,15 @@ On Ubuntu/Debian based
To build the tests, run
$ 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

View File

@ -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
------
@ -25,7 +16,7 @@ Optional packages:
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:

View File

@ -35,3 +35,4 @@ Cmdline options
* --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.
Options specified on the command line take precedence over those in the config file.
* --install= - Installs the webui files, see BUILDING.md for details.

View File

@ -1,5 +1,6 @@
#include <boost/test/unit_test.hpp>
#include "util/util.h"
#include "util/HTTP.h"
BOOST_AUTO_TEST_SUITE(UtilityTests)
@ -88,4 +89,116 @@ BOOST_AUTO_TEST_CASE(ParseUrlPassword)
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()

24
webui/404.html Normal file
View File

@ -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 Normal file
View File

@ -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 Normal file
View File

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

27
webui/help.html Normal file
View File

@ -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 Normal file
View File

@ -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>

View File

@ -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 Normal file
View File

@ -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 Normal file
View File

@ -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 Normal file
View File

@ -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>