Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
############################
# Find required packages #
############################
find_package(opendht 1.2.0 REQUIRED)
find_package(opendht 3.0.0 REQUIRED)
find_package(CURLpp REQUIRED)
find_package(glibmm REQUIRED)
find_package(B64 REQUIRED)
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Milis Linux: mps kur dpaste (https://github.com/milisarge/malfs-milis/blob/maste

## Dependencies

- [OpenDHT](https://github.com/savoirfairelinux/opendht/) (minimal version: 1.2.0)
- [OpenDHT](https://github.com/savoirfairelinux/opendht/) (minimal version: 3.0.0)
- [msgpack-c](https://github.com/msgpack/msgpack-c)
- [gpgmepp](https://github.com/KDE/gpgmepp)
- [json.hpp](https://github.com/nlohmann/json) (required version for CMake: 2.1.1)
Expand All @@ -89,6 +89,14 @@ Milis Linux: mps kur dpaste (https://github.com/milisarge/malfs-milis/blob/maste
- [catch](https://github.com/catchorg/Catch2) for unit tests
- getopt (util-linux)

## Caching

To avoid the multi-second DHT cold start on every invocation, `dpaste` caches
the DHT identity and the node state (routing table) on disk. The cache lives
in `${XDG_CACHE_HOME}/dpaste` (usually `~/.cache/dpaste`) and can be relocated
with the `DPASTE_CACHE_DIR` environment variable. The state is refreshed at
the end of every run; the first run remains slow, subsequent ones start warm.

## Pastebin over DHT

A DHT is efficient and requires no infrastructure. In practice, you can always
Expand Down
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ AS_IF([test "x$enable_debug" = "xyes"],
AC_PROG_CXX
AC_PROG_RANLIB

PKG_CHECK_MODULES([OpenDHT], [opendht >= 1.2])
PKG_CHECK_MODULES([OpenDHT], [opendht >= 3.0.0])
PKG_CHECK_MODULES([CURLPP], [curlpp])
PKG_CHECK_MODULES([GLIBMM], [glibmm-2.4])

Expand Down
75 changes: 75 additions & 0 deletions src/node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,90 @@
#include <algorithm>
#include <random>
#include <future>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iostream>

#include <opendht.h>
#include <glibmm.h>

#include "node.h"

namespace dpaste {

const constexpr char* Node::DPASTE_USER_TYPE;

namespace {

/**
* Directory holding the on-disk caches (identity + node state).
* Can be overridden with the DPASTE_CACHE_DIR environment variable
* (e.g. for tests). Defaults to ${XDG_CACHE_HOME}/dpaste.
*/
std::string cacheDir() {
const char* env = std::getenv("DPASTE_CACHE_DIR");
std::string dir = env and *env ? env : Glib::get_user_cache_dir() + "/dpaste";
std::error_code ec;
std::filesystem::create_directories(dir, ec);
return dir;
}

} /* anonymous namespace */

dht::crypto::Identity Node::loadIdentity() {
/* Only try the cache if the files exist: a missing cache is the normal
* first-run case and shouldn't print a scary error. */
std::ifstream key_file(identity_path_ + ".pem");
if (key_file.good()) {
try {
auto id = dht::crypto::loadIdentity(identity_path_);
if (id.first and id.second)
return id;
} catch (const std::exception& e) {
std::cerr << "dpaste: cached identity is corrupt (" << e.what() << "), generating a new one." << std::endl;
}
}

auto id = dht::crypto::generateIdentity();
try {
/* Write to temporary files first, then rename, so concurrent dpaste
* processes never leave a half-written identity in the cache. */
auto tmp_path = identity_path_ + ".tmp";
dht::crypto::saveIdentity(id, tmp_path);
std::rename((tmp_path + ".pem").c_str(), (identity_path_ + ".pem").c_str());
std::rename((tmp_path + ".crt").c_str(), (identity_path_ + ".crt").c_str());
} catch (const std::exception& e) {
std::cerr << "dpaste: failed to cache identity: " << e.what() << std::endl;
}
return id;
}

void Node::run(uint16_t port, std::string bootstrap_hostname, std::string bootstrap_port) {
if (running_)
return;

auto dir = cacheDir();
identity_path_ = dir + "/identity";
nodes_path_ = dir + "/nodes";

/* Load (or generate and cache) the identity so we don't pay for RSA key
* generation on every run. */
auto identity = loadIdentity();

/* Ask OpenDHT to load its state (routing table) on start and save it on
* shutdown; this turns the multi-second DHT cold start into a warm one. */
dht::DhtRunner::Config config;
config.dht_config.id = identity;
config.dht_config.node_config.persist_path = nodes_path_;
config.threaded = true;
node_.run(port, config);

node_.bootstrap(bootstrap_hostname, bootstrap_port);
running_ = true;
}

bool Node::paste(const std::string& code, dht::Blob&& blob, dht::DoneCallbackSimple&& cb) {
auto v = std::make_shared<dht::Value>(std::forward<dht::Blob>(blob));
v->user_type = DPASTE_USER_TYPE;
Expand Down
33 changes: 25 additions & 8 deletions src/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,22 @@ class Node {
static const constexpr char* DPASTE_USER_TYPE = "dpaste";

Node() {}
virtual ~Node () {}

void run(uint16_t port = 0, std::string bootstrap_hostname = DEFAULT_BOOTSTRAP_NODE, std::string bootstrap_port = DEFAULT_BOOTSTRAP_PORT) {
virtual ~Node() {
/* Persist the node state (routing table) on disk so the next run
* starts warm instead of cold-bootstrapping the DHT. */
if (running_)
return;
node_.run(port, dht::crypto::generateIdentity(), true);
node_.bootstrap(bootstrap_hostname, bootstrap_port);
running_ = true;
};
stop();
}

/**
* Start the DHT node. The identity and the node state are cached on disk
* (see DPASTE_CACHE_DIR below) so subsequent runs connect much faster.
*
* @param port Local port to bind (0 for random).
* @param bootstrap_hostname Hostname of the bootstrap node.
* @param bootstrap_port Port of the bootstrap node.
*/
void run(uint16_t port = 0, std::string bootstrap_hostname = DEFAULT_BOOTSTRAP_NODE, std::string bootstrap_port = DEFAULT_BOOTSTRAP_PORT);

void stop() {
std::condition_variable cv;
Expand Down Expand Up @@ -116,9 +123,19 @@ class Node {

private:

/**
* Load the DHT identity from the on-disk cache, generating and caching a
* new one if the cache is missing or corrupt.
*/
dht::crypto::Identity loadIdentity();

dht::DhtRunner node_;
bool running_ {false};

/* on-disk cache locations (identity + DHT node state) */
std::string identity_path_ {};
std::string nodes_path_ {};

std::uniform_int_distribution<uint32_t> codeDist_;
std::mt19937_64 rand_;
};
Expand Down