Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
f0268d0
Make both HTTPS and QUIC listen on IPv6
jagerman Oct 27, 2025
ee906b5
Fix IPv6 requests
jagerman Oct 28, 2025
05394b9
version bump
jagerman Nov 26, 2025
c8e7054
Fix tests & formatting
jagerman Nov 27, 2025
1a4f8eb
Merge pull request #1 from jagerman/ipv6-quic-listening
jagerman Nov 27, 2025
5c73211
Fix some versions of cli11 not liking ip= in config file
jagerman Dec 11, 2025
b5394b9
fix contrib/onion-request debug build
jagerman Dec 18, 2025
3d159a1
Remove unhelpful issue template
jagerman Jan 6, 2026
d15f398
Slim repo cloning & repo key updates
jagerman Jan 23, 2026
6b6eaab
Merge pull request #3 from jagerman/keys-and-slim-cloning
jagerman Mar 3, 2026
208b545
Fix missing namespace on bulk message serialization
jagerman Mar 3, 2026
a590a2a
bump drone to clang-19
jagerman Mar 3, 2026
eacae77
Merge pull request #4 from jagerman/fix-msg-ns-serialization
jagerman Mar 3, 2026
9a3ddb1
Add --skip-bootstrap to avoid contacting of seed nodes on startup (us…
Doy-lee Apr 3, 2025
235ea97
Respect the skip_bootstrap_ flag
Doy-lee Apr 28, 2025
68339b6
Get rid of db getter and setter by making it public
Doy-lee May 16, 2025
ff19bd5
WIP: Return more extensive SN data ready payload
Doy-lee May 16, 2025
f0a5684
Move swarm logs into derivation function, serialise SNDataReady w/ he…
Doy-lee May 20, 2025
c041791
Timestamps in the DB are stored in millisecond precision
Doy-lee Jun 2, 2025
db01d96
Represent pending/ready members w/ an enum
Doy-lee Jun 2, 2025
844628e
Remove unused std::optional header
Doy-lee Jun 2, 2025
0fbff18
Store network swarm/swarm state to disk to resume from
Doy-lee Jun 4, 2025
e6a545e
Update outdated comments
Doy-lee Jun 5, 2025
592c90c
Add tmp work-around for DB swarm dump on migration to DB v1
Doy-lee Jun 5, 2025
8f6f6b3
Infinite retries with exponential fallback
Doy-lee Jun 12, 2025
a850672
Get rid of duplicate fnv hash, already present in serialize.h
Doy-lee Jun 12, 2025
a367ac5
Remove unused TEST_RETRY_INTERVAl/PERIOD constants
Doy-lee Jun 12, 2025
32afbf4
Rename BTSerialise to Serialise and prefix SN's SerialiseResult to be…
Doy-lee Jun 12, 2025
e994039
Remove unused MessageTestStatus
Doy-lee Jun 12, 2025
27da443
Move retryable requests into its own thread and restore pending swarm…
Doy-lee Jun 12, 2025
691a3a5
Revert SN data ready response, timestamp not needed
Doy-lee Jun 13, 2025
5a0a208
Serialise retryable requests to the DB
Doy-lee Jun 16, 2025
1dc5b93
Match comment w/ code regarding when to add retryable request on failure
Doy-lee Jun 17, 2025
42cdea0
Revert NEW_SWARM_MEMBER_INTERVAL to 10s
Doy-lee Jun 17, 2025
9870ca4
Update the swarms serialisation to match retryable requests patterns
Doy-lee Jun 17, 2025
a762481
Remove unused swarm_member_check_deadline, add comments
Doy-lee Jun 17, 2025
b30f1e2
Simplify swarm member status states by removing ContactDetailsReady
Doy-lee Jun 17, 2025
af9e977
Fix dblocation typo -> db_location
Doy-lee Jun 17, 2025
3a74a33
Remove RETRY_BACKOFF_COEFF from retry request on done lambda
Doy-lee Jun 17, 2025
ff69570
Linting
Doy-lee Jun 17, 2025
3d56a4c
Request a DB dump on data-ready handshake if DB is empty
Doy-lee Jun 19, 2025
5077fbd
Add 10GiB message payload to serialise test
Doy-lee Jun 23, 2025
111b878
Linting
Doy-lee Jun 27, 2025
ba0b0d1
Reduce serialisation test to 5GiB to not OOM on CI
Doy-lee Jun 27, 2025
932dc5f
Reduce serialisation test to 100MiB to not OOM on CI
Doy-lee Jun 27, 2025
506c4a3
Stop potential infinite bootstrap of DB if swarm ID doesn't change
Doy-lee Jul 14, 2025
02efd4e
Update static zlib to use github releases URL for reliability
Doy-lee Mar 23, 2026
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
18 changes: 16 additions & 2 deletions .drone.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,21 @@ local default_deps_nocxx = ['libsodium-dev'] + default_deps_base; // libsodium-
local default_deps = ['g++'] + default_deps_nocxx; // g++ sometimes needs replacement
local docker_base = 'registry.oxen.rocks/';

local submodules_commands = ['git fetch --tags', 'git submodule update --init --recursive --depth=1 --jobs=4'];
local submodules_commands = [
'git fetch --tags',

// uWebSockets includes nearly 900MB of crap via submodules that we don't use and want to clone on
// every CI job, so do this song and dance to get rid of the junk.
'git submodule update --init --depth=1 external/uWebSockets',
'cd external/uWebSockets',
'git rm fuzzing/seed-corpus',
'git submodule update --init --depth=1 uSockets',
'cd uSockets',
'git rm boringssl lsquic',
'cd ../../..',

'git submodule update --init --recursive --depth=1 --jobs=4',
];
local submodules = {
name: 'submodules',
image: 'drone/git',
Expand Down Expand Up @@ -181,7 +195,7 @@ local static_check_and_upload = [
// Various debian builds
debian_pipeline('Debian (amd64)', docker_base + 'debian-sid', lto=true),
debian_pipeline('Debian Debug (amd64)', docker_base + 'debian-sid', build_type='Debug'),
clang(17, lto=true),
clang(19, lto=true),
debian_pipeline('Debian stable (i386)', docker_base + 'debian-stable/i386', werror=false),
debian_pipeline('Ubuntu LTS (amd64)', docker_base + 'ubuntu-lts', oxen_repo=true),
debian_pipeline('Ubuntu latest (amd64)', docker_base + 'ubuntu-rolling'),
Expand Down
22 changes: 0 additions & 22 deletions .github/ISSUE_TEMPLATE/bug_report.md

This file was deleted.

2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ endif()
cmake_minimum_required(VERSION 3.13)

project(oxenss
VERSION 2.11.1
VERSION 2.11.3
LANGUAGES CXX C)

set(CMAKE_CXX_STANDARD 20)
Expand Down
2 changes: 1 addition & 1 deletion cmake/StaticBuild.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ set(LIBUV_HASH SHA512=b153d019c630831819913ccd0615c22737df49125da533f86db27f24a5
CACHE STRING "libuv source hash")

set(ZLIB_VERSION 1.3.1 CACHE STRING "zlib version")
set(ZLIB_MIRROR ${LOCAL_MIRROR} https://zlib.net
set(ZLIB_MIRROR ${LOCAL_MIRROR} https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}
CACHE STRING "zlib mirror(s)")
set(ZLIB_SOURCE zlib-${ZLIB_VERSION}.tar.xz)
set(ZLIB_HASH SHA256=38ef96b8dfe510d42707d9c781877914792541133e1870841463bfa73f883e32
Expand Down
Binary file removed contrib/deb.oxen.io.gpg
Binary file not shown.
1 change: 1 addition & 0 deletions contrib/deb.oxen.io.gpg
Binary file added contrib/deb.session.foundation.gpg
Binary file not shown.
4 changes: 2 additions & 2 deletions contrib/onion-request.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ void onion_request(std::string ip, uint16_t port, std::vector<std::pair<ed25519_

last_etype = final_etype = enc_type.value_or(random_etype());
#ifndef NDEBUG
std::cerr << "Encrypting for final hop using " << to_string(last_etype) << "/" << A << "\n";
std::cerr << "Encrypting for final hop using " << to_string(last_etype) << "/" << eph.pub.hex() << "\n";
#endif
blob = e.encrypt(last_etype, data, keys.back().second);
// Save these because we need them again to decrypt the final response:
Expand All @@ -305,7 +305,7 @@ void onion_request(std::string ip, uint16_t port, std::vector<std::pair<ed25519_
last_etype = enc_type.value_or(random_etype());

#ifndef NDEBUG
std::cerr << "Encrypting for next-last hop using " << to_string(last_etype) << "/" << A << "\n";
std::cerr << "Encrypting for next-last hop using " << to_string(last_etype) << "/" << eph.pub.hex() << "\n";
#endif
blob = e.encrypt(last_etype, blob, it->second);
}
Expand Down
27 changes: 27 additions & 0 deletions oxenss/common/serialize.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once

#include <string>
#include <string_view>
#include <stdint.h>

namespace oxenss {
enum class Serialise {
Read,
Write,
};

struct SerialiseBTResult {
bool success;
std::string write_payload;
std::string error;
};

constexpr uint64_t FNV1A64_SEED = 14695981039346656037ULL;

inline uint64_t fnv1a64_hasher(std::string_view bytes, uint64_t hash) {
for (size_t i = 0; i < bytes.size(); i++)
hash = (bytes[i] ^ hash) * 1099511628211 /*FNV Prime*/;
return hash;
}

}; // namespace oxenss
1 change: 0 additions & 1 deletion oxenss/crypto/keys.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#include <array>
#include <cstddef>
#include <functional>
#include <optional>
#include <string>
#include <string_view>

Expand Down
14 changes: 6 additions & 8 deletions oxenss/daemon/command_line.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,19 +169,17 @@ parse_result parse_cli_args(int argc, char* argv[]) {
"Deprecated port argument; use --https-port option instead")
->capture_default_str()
->type_name("PORT");
// TODO: need to support multiple here (e.g. so we can listen on public + lokinet)
cli.add_option(
"--bind-ip",
options.ip,
"IP address on which to listen for connections; typically this should be the "
"0.0.0.0 (the IPv4 \"any\" address)")
->capture_default_str()
->type_name("IP");
cli.add_option("--bind-ip,--ip", options.ip_ignored, "Deprecated and ignored.");
cli.add_flag("--testnet", options.testnet, "Start storage server in testnet mode");
cli.add_flag(
"--force-start",
options.force_start,
"Ignore the initialisation ready check (primarily for debugging).");
cli.add_flag(
"--skip-bootstrap-nodes",
options.skip_bootstrap,
"Skip the contacting of bootstrap seed nodes on startup (primarily for private node "
"networks)");
cli.add_option(
"--stats-access-key",
options.stats_access_keys,
Expand Down
3 changes: 2 additions & 1 deletion oxenss/daemon/command_line.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
namespace oxenss::cli {

struct command_line_options {
std::string ip = "0.0.0.0";
std::string ip_ignored = "0.0.0.0";
uint16_t https_port = 22021;
uint16_t omq_quic_port = 22020;
std::string oxend_omq_rpc; // Defaults to ipc://$HOME/.oxen/[testnet/]oxend.sock
bool skip_bootstrap = false;
bool force_start = false;
bool testnet = false;
std::string log_level = "info";
Expand Down
45 changes: 28 additions & 17 deletions oxenss/daemon/oxen-storage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,6 @@ int main(int argc, char* argv[]) {
// Always print version for the logs
log::info(logcat, "{}", STORAGE_SERVER_VERSION_INFO);

if (options.ip == "127.0.0.1") {
log::critical(
logcat,
"Tried to bind oxen-storage to localhost, please bind "
"to outward facing address");
return EXIT_FAILURE;
}

log::info(logcat, "Setting log level to {}", options.log_level);
log::info(logcat, "Setting database location to {}", util::to_sv(options.data_dir.u8string()));
log::info(logcat, "Connecting to oxend @ {}", options.oxend_omq_rpc);
Expand Down Expand Up @@ -161,31 +153,49 @@ int main(int argc, char* argv[]) {
auto& oxenmq_server = *oxenmq_server_ptr;

snode::ServiceNode service_node{
l_keys, me, oxenmq_server, options.data_dir, options.force_start};
l_keys,
me,
oxenmq_server,
options.data_dir,
options.force_start,
options.skip_bootstrap};

rpc::RequestHandler request_handler{service_node, channel_encryption, ed_keys.sec};

rpc::RateLimiter rate_limiter{*oxenmq_server};

std::vector<std::tuple<std::string, uint16_t, bool>> https_bind;
std::vector<oxen::quic::Address> quic_bind;
#ifdef IPV6_V6ONLY
// If this define is set then listen in dual stack mode. uWebSockets doesn't give us any
// way to disable this; for quic it's a flag on the address object.
https_bind.emplace_back("::", options.https_port, true);
quic_bind.emplace_back("::", options.omq_quic_port);
quic_bind.back().dual_stack = true;
#else
https_bind.emplace_back("0.0.0.0", options.https_port, true);
https_bind.emplace_back("::", options.https_port, true);

quic_bind.emplace_back("0.0.0.0", options.omq_quic_port);
quic_bind.emplace_back("::", options.omq_quic_port);
quic_bind.back().dual_stack = false;
#endif

server::HTTPS https_server{
service_node,
request_handler,
rate_limiter,
{{options.ip, options.https_port, true}},
std::move(https_bind),
ssl_cert,
ssl_key,
ssl_dh,
l_keys};

auto quic = std::make_unique<server::QUIC>(
service_node,
request_handler,
rate_limiter,
oxen::quic::Address{options.ip, options.omq_quic_port},
ed_keys.sec);
service_node, request_handler, rate_limiter, std::move(quic_bind), ed_keys.sec);
service_node.register_mq_server(quic.get());

auto http_client = std::make_shared<http::Client>(quic->loop());
auto http_client = std::make_shared<http::Client>(quic->loop);
service_node.set_http_client(http_client);
request_handler.set_http_client(http_client);

Expand Down Expand Up @@ -217,7 +227,8 @@ int main(int argc, char* argv[]) {
std::this_thread::sleep_for(100ms);

log::warning(logcat, "Received signal {}; shutting down...", signalled.load());
http_client.reset(); // Kills outgoing requests and prevents new ones
http_client.reset(); // Kills outgoing requests and prevents new ones. Also depends on
// `quic`'s event loop so *must* be destroyed before `quic`.
service_node.shutdown();
log::info(logcat, "Stopping https server");
https_server.shutdown(true);
Expand Down
22 changes: 10 additions & 12 deletions oxenss/http/http_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ struct curl_context {
curl_context(Client& client, curl_socket_t fd) :
client{client},
sockfd{fd},
evt{event_new(client.loop->get_event_base(), sockfd, 0, Client::curl_perform_c, this)} {
}
evt{event_new(client.loop.get_event_base(), sockfd, 0, Client::curl_perform_c, this)} {}
~curl_context() {
event_del(evt);
event_free(evt);
Expand Down Expand Up @@ -92,7 +91,7 @@ int Client::handle_socket_c(
event_del(curl_ctx->evt);
event_assign(
curl_ctx->evt,
client.loop->get_event_base(),
client.loop.get_event_base(),
curl_ctx->sockfd,
events,
Client::curl_perform_c,
Expand Down Expand Up @@ -142,15 +141,14 @@ void Client::check_multi_info() {
}
}

Client::Client(std::shared_ptr<oxen::quic::Loop> loop_) :
loop{std::move(loop_)},
Client::Client(oxen::quic::Loop& loop_) :
loop{loop_},
ev_timeout{evtimer_new(
loop->get_event_base(),
loop.get_event_base(),
[](evutil_socket_t /*fd*/, short /*events*/, void* arg) {
static_cast<Client*>(arg)->on_timeout();
},
this)} {
assert(loop);
curl_multi = curl_multi_init();
curl_multi_setopt(curl_multi, CURLMOPT_SOCKETDATA, this);
curl_multi_setopt(curl_multi, CURLMOPT_SOCKETFUNCTION, Client::handle_socket_c);
Expand All @@ -159,7 +157,7 @@ Client::Client(std::shared_ptr<oxen::quic::Loop> loop_) :
}

Client::~Client() {
loop->call_get([this] {
loop.call_get([this] {
alive.reset();
for (auto& [session, cb] : active_reqs)
curl_multi_remove_handle(curl_multi, session->GetCurlHolder()->handle);
Expand Down Expand Up @@ -196,10 +194,10 @@ void Client::post(
sess->SetBody(std::move(payload));
curl_easy_setopt(sess->GetCurlHolder()->handle, CURLOPT_PRIVATE, sess.get());
sess->PreparePost();
loop->call([this,
alive = std::weak_ptr{alive},
sess = std::move(sess),
cb = std::move(cb)]() mutable {
loop.call([this,
alive = std::weak_ptr{alive},
sess = std::move(sess),
cb = std::move(cb)]() mutable {
if (alive.expired())
return; // this got destroyed before we got into the call
curl_multi_add_handle(curl_multi, sess->GetCurlHolder()->handle);
Expand Down
4 changes: 2 additions & 2 deletions oxenss/http/http_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Client {
using response_callback = std::function<void(cpr::Response r)>;

// Starts a new client, attaching itself to the event loop and ready for requests.
explicit Client(std::shared_ptr<oxen::quic::Loop> loop);
explicit Client(oxen::quic::Loop& loop);

// Non-copyable, non-movable
Client(const Client&) = delete;
Expand All @@ -44,7 +44,7 @@ class Client {
bool https_disable_validation = false);

private:
std::shared_ptr<oxen::quic::Loop> loop;
oxen::quic::Loop& loop;
event* ev_timeout;
std::shared_ptr<const bool> alive = std::make_shared<bool>(true);
CURLM* curl_multi;
Expand Down
10 changes: 5 additions & 5 deletions oxenss/rpc/client_rpc_endpoints.h
Original file line number Diff line number Diff line change
Expand Up @@ -541,9 +541,9 @@ struct delete_before final : recursive {
};

/// Updates (shortens) the expiry of all stored messages, and broadcasts the update request to all
/// other swarm members. Note that this will not extend existing expiries, it will only shorten the
/// expiry of any messages that have expiries after the requested value. (To extend expiries of one
/// or more individual messages use the `expire` endpoint).
/// other swarm members. Note that this will not extend existing expiries, it will only shorten the
/// expiry of any messages that have expiries after the requested value. (To extend expiries of one
/// or more individual messages use the `expire_msgs` endpoint).
///
/// Takes parameters of:
/// - pubkey -- the pubkey whose messages shall have their expiries reduced, in hex (66) or bytes
Expand Down Expand Up @@ -625,8 +625,8 @@ struct expire_all final : recursive {
/// ("expire" || ShortenOrExtend || expiry || messages[0] || ... || messages[N])
/// where `expiry` is the expiry timestamp expressed as a string, for a single expiry, or the
/// expiries concatenated together (expiry[0] || expiry[1] || ...) for multiple expiries.
/// `ShortenOrExtend` is string "shorten" if the shorten option is given (and true), "extend" if
/// `extend` is true, and empty otherwise. The signature must be base64 encoded (json) or bytes
/// `ShortenOrExtend` is the string "shorten" if the shorten option is given (and true), "extend"
/// if `extend` is true, and empty otherwise. The signature must be base64 encoded (json) or bytes
/// (bt).
///
/// Returns dict of:
Expand Down
10 changes: 2 additions & 8 deletions oxenss/rpc/rate_limiter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ bool RateLimiter::should_rate_limit(
return !remove_token(it->second, now, true);
}

bool RateLimiter::should_rate_limit_client(uint32_t ip, steady_clock::time_point now) {
bool RateLimiter::should_rate_limit_client(
const oxen::quic::ipv6& ip, steady_clock::time_point now) {
std::lock_guard lock{mutex_};

if (auto it = client_buckets_.find(ip); it != client_buckets_.end())
Expand All @@ -87,13 +88,6 @@ bool RateLimiter::should_rate_limit_client(uint32_t ip, steady_clock::time_point
return false;
}

bool RateLimiter::should_rate_limit_client(
const std::string& ip_dotted_quad, steady_clock::time_point now) {
struct in_addr ip;
int res = inet_pton(AF_INET, ip_dotted_quad.c_str(), &ip);
return res == 1 ? should_rate_limit_client(ip.s_addr, now) : false;
}

void RateLimiter::clean_buckets(steady_clock::time_point now) {
for (auto it = client_buckets_.begin(); it != client_buckets_.end();) {
if (fill_bucket(it->second, now))
Expand Down
Loading