Commit Graph
1198 Commits
Author SHA1 Message Date
Adrien Beraud e092e4d89f network: size value fragments from the minimum MTU
Fragments carried 1280 bytes of value data, plus the msgpack framing and,
for encrypted peers, the envelope overhead: packets exceeded the 1280-byte
IPv6 minimum MTU. Derive both fragment sizes from the 1232-byte UDP payload
that fits in it, less an upper bound of the fragment framing.
2026-10-29 23:28:48 -04:00
Adrien Beraud 0d5e4c761a network: enforce transport trust and address validation boundaries 2026-10-29 23:28:48 -04:00
Adrien Beraud 353c3d4e11 network: bind fragmented message parts to their envelope state
Value data fragments carry no node ID and were merged into a partial
message by source address and transaction ID alone, first come first
served. Transaction IDs increment from a random start and show in any
plaintext packet of a pair, such as the first-contact ping. An attacker
spoofing the sender's address could thus pre-seed forged plaintext
fragments, or a plaintext header, for an encrypted reply: the real parts
were then dropped as overlapping, forging unsigned values or losing the
message.

Parts of a message must now all be plaintext, or all come in envelopes
from the same sender key as its first part. An encrypted part replaces
plaintext state, which anyone can forge from a spoofed address; plaintext
or foreign-key parts never join an encrypted message.
2026-10-29 23:28:48 -04:00
Adrien Beraud 6aee7ceb5c network: cache sessions only for address-validated peers
A session was cached for an address whenever we answered a peer there,
including replies to Initial and Hello envelopes, whose source address is
not verified. A spoofed Initial or Hello therefore replaced the session of
the real owner of the address, whose Data envelopes were then dropped until
a retry, and spoofed Initials from many addresses filled the cache and
evicted legitimate sessions, which were picked by address order.

Only cache a session for a peer whose ephemeral key arrived in a Session
or Data envelope, i.e. opened with our key for its address. Replies to
Initial and Hello envelopes derive their keys without caching them, so a
node that sent an Initial only confirms the session, and switches to Data
envelopes, once the peer answered a Session envelope. Evict the least
recently used session when the cache is full.
2026-10-29 23:28:48 -04:00
Adrien Beraud e78e54b8c5 transport: send keyless Data envelopes once the session is confirmed
Sessions are keyed by peer address, so the receiver finds the keys from the source address: Data envelopes drop both keys from the header (30 bytes of overhead instead of 94) and need no key derivation to reject. A session is confirmed when the peer answers our envelope request with the session keys, or sends Data itself. Credentials only travel in key-carrying envelopes, which replaces Peer::acked. A received Initial envelope unconfirms the session, and full session caches evict instead of refusing, so a peer that derived keys keeps them.
2026-10-29 23:28:48 -04:00
Adrien Beraud 22d87a0f0e transport: replace write tokens with address-bound sessions
Ephemeral keys derive from a per-epoch secret and the peer address, so only that address learns them. A Session envelope then proves return routability: encrypted put, refresh and listen are accepted without token, and messages to encrypted peers no longer carry one (40 bytes saved). Initial envelopes cannot write. Tokens for plaintext peers move from Dht to NetworkEngine and derive from the same epoch secret. Session caches are keyed by address, and cached keys of both epochs are tried before any derivation.
2026-10-29 23:28:48 -04:00
Adrien Beraud 26b30d4162 transport: add forward secrecy with rotated ephemeral keys
Each node rotates an ephemeral X25519 key every 30 minutes and keeps the
previous one for another period. Envelopes carry the sender static and
ephemeral keys. Session envelopes use keys derived from all four DH
combinations of static and ephemeral keys, erased with the epoch.
Initial envelopes, sent before the peer's ephemeral key is known, mix
the sender ephemeral and static keys with the receiver static key.

A hello is unauthenticated: it is only answered, without adding or
moving its claimed node. A peer is acknowledged only by a reply to a
request sent in an envelope, and a request retry restarts with an
Initial envelope and our credential.
2026-10-29 23:28:48 -04:00
Adrien Beraud a32acbfa08 dht: persist the generated transport key in the node state
With persist_path, a transport key generated by the node is saved in the
state (owner-only file permissions) and restored on start, keeping the
node ID across restarts. Configured keys are never written to the state.
2026-10-29 23:28:48 -04:00
Adrien Beraud 7bc287fe2f network_engine: encrypt the DHT protocol
Nodes using transport encryption derive their node ID from their X25519
transport key, advertise it in plaintext messages ("k") and in node
lists ("k4"/"k6"), and encrypt all traffic with nodes whose key is
known. Keys are bound to node IDs, so they can't be forged, and
plaintext messages never move a node known to support encryption.
Legacy nodes keep talking in plaintext.

Private networks (TransportConfig::authorities) only accept nodes with
a credential issued by one of the network authorities, never send or
accept plaintext, and introduce themselves to addresses with a Hello.
2026-10-29 23:28:48 -04:00
Adrien Beraud 1c32dd0efb transport: add protocol encryption and credential primitives
X25519 transport keys, node ID derivation from the public key,
HKDF-SHA256 key agreement, ChaCha20-Poly1305 envelopes that legacy
msgpack parsers reject, and Ed25519 network authority credentials.
2026-10-29 23:28:48 -04:00
cyrilleandCopilot 17f83edce4 proxy: add Huawei (HMS) push type
Route push notifications to Huawei devices, which have no Google Play
Services and therefore cannot receive FCM. gorush, already used as the
push relay, supports HMS natively as platform 3.

HMS takes its payload as a serialized JSON string in "huawei_data" and
carries its own ttl/priority fields, so it needs its own branch rather
than the generic "data" object used for FCM and APNs.

The enum value is appended last: MSGPACK_ADD_ENUM serializes it as an
integer, so reordering would break compatibility with deployed proxies.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-29 00:02:02 +00:00
Cyrille BéraudandCopilot 9b9056c0c8 dht: do not report unfiltered answers to filtered queries
Query::isSatisfiedBy() considers an unfiltered answer sufficient for a
filtered query, which is true for a vanilla get: onGetValuesDone() applies
the operation filter to the values before calling back.

A projected query is different. It receives the fields another query selected
and can no longer evaluate its own filters on them, all the more so as those
filters may target fields that were not selected at all. Two concurrent
queries such as "SELECT id" and "SELECT id WHERE value_type=1" therefore made
the filtered callback report identifiers whose type does not match, and
searchNodeGetDone() additionally cancelled its own request so it never got a
correctly filtered answer.

Add Query::isProjectionSatisfiedBy(), which additionally requires both queries
to apply the same filters, and use it for operations with a query callback.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-26 09:21:10 -04:00
Adrien Beraud 34eb86a119 value: support sequence numbers in wire filters 2026-10-26 09:19:57 -04:00
Cyrille BéraudandCopilot 2893823a77 securedht: forward push platform and topic
SecureDht only forwarded the push token to the DHT it wraps, so the
push platform and topic given to DhtRunner after the proxy client was
created never reached it. A device started without push platform
(e.g. a Jami account created by a build without push support) then
subscribed with an empty platform once given a token, and the proxy
server pushed it as an iOS device.

Co-authored-by: Copilot (Claude Opus 5.5) <223556219+Copilot@users.noreply.github.com>
2026-10-26 02:55:59 +00:00
Cyrille BéraudandCopilot 3ec03ccc0b proxy_server: keep push platform up to date
The DHT listen callback of a push subscription kept the push type and
topic of the request that created it. Refreshing the subscription
with another platform updated the listener, but values kept being
pushed to the first platform: an Android device that once subscribed
without platform was pushed as an iOS device forever (BadDeviceToken)
and never received values such as connection requests.

Keep the push type and topic in the session context shared with the
callback, and update them on each subscription refresh and when the
state is restored.

Co-authored-by: Copilot (Claude Opus 5.5) <223556219+Copilot@users.noreply.github.com>
2026-10-26 02:55:59 +00:00
Adrien Beraud 57ed00579e pht: preserve prefix padding encoding 2026-10-25 03:03:35 +00:00
Cyrille BéraudandCopilot d99324ebd4 pht: fix the bit arithmetic of Prefix
Prefix::size_ counts bits, but commonBits walked content_ a byte at a
time up to it. Two prefixes that agree on their whole common length are
therefore read well past their end, which AddressSanitizer reports as a
heap-buffer-overflow, and the count that comes back is eight times too
large. A lookup with a common prefix length hits this as soon as an
index entry matches the key it is looking for, and entry.prefix comes
straight off the network. The flags were indexed the same way, so a
partially known prefix was read at the wrong position too.

swapBit had the other half of the confusion: it flipped bit
(8 - bit) % 8 of the byte, while isActiveBit reads bit 7 - (bit % 8).
getSibling, which flips the last bit of a prefix to name the sibling
node in the trie, therefore returned a prefix differing in some other
bit and sharing the last one, so lookup and insertion looked up the
wrong node. Splitting a leaf and finding a canary both rely on it.

Add tests covering both, which fail before the fix: commonBits returns
193 for two identical 160 bit prefixes, and a prefix and its sibling
share 7 bits rather than all but the last.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-25 03:03:35 +00:00
Cyrille BéraudandCopilot b9c8f5e0fd runner: complete pushes received while stopped
Resolve idle-runner push futures immediately under the operation queue lock. A stopped runner has no loop left to execute the queued promise, otherwise consumers can wait indefinitely.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-09-21 16:02:47 -04:00
Adrien BeraudandCopilot 3a383ac873 http: close connections on the io_context thread
asio sockets are not thread-safe. http::Connection::close() used to close
the socket synchronously on the calling thread. Request::cancel() and
Request::~Request() (via terminate()) are called from arbitrary threads
(e.g. DhtProxyClient::stop(), cancelAllListeners(), cancelListen()),
so the close raced with in-flight operations on the io_context thread.

In particular, asio::async_connect's range_connect_op closes and
re-opens the socket when trying the next endpoint (common on mobile
where the IPv6 endpoint fails first). A concurrent close() from another
thread corrupts the epoll reactor's per-descriptor state, leading to
crashes in epoll_reactor::deregister_descriptor / register_descriptor
called from range_connect_op::process.

Dispatch the close to the io_context thread when not already on it.
Also keep the connection alive during the TLS handshake, like every
other asynchronous operation, so that the destructor can never run
while an operation is pending.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-09-04 14:13:20 -04:00
Adrien Beraud 3149fe60c4 proxy: increase OP_MARGIN to 8 hours 2026-09-04 11:04:42 -04:00
Adrien Beraud d5956e0778 proxy: use system_clock for subscriber expiration timeouts
Track subscribe/permanent-put expiration with std::chrono::system_clock
(wall clock) instead of steady_clock in both the proxy client and server,
so persisted expirations survive restarts and map directly to epoch time.

- DhtProxyServer::clock is now system_clock; expireTimer/expireNotifyTimer
  use asio::system_timer.
- DhtProxyClient::refreshSubscriberTimer uses asio::system_timer and
  system_clock::now() for subscriber refresh.
2026-09-04 11:04:42 -04:00
Adrien Beraud 82ab21dc3f crypto: support explicit EC curve generation 2026-09-01 21:34:36 -04:00
Adrien Beraud c211b3c376 dht: persist values with system time 2026-08-05 16:14:15 -04:00
Adrien Beraud 44e1ad839e net: harden fragmented reply completion 2026-08-05 14:29:03 -04:00
Cyrille BéraudandCopilot 0826822a4d net: harden fragmented message reassembly
Key partial messages by source and transaction ID, buffer bounded fragments received before their header, and avoid charging reordered requests twice against rate limits. Cover source collisions, early fragments, empty data and large values.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-05 14:29:03 -04:00
Adrien Beraud f98df1d615 crypto: allow signing a revocation list with an explicit CRL number
RevocationList::sign() always overwrote the CRL Number extension: random
on the first signature, incremented afterwards. This leaves no way for a
certificate authority replicated over several devices to keep numbers
unique, since every device branching from the same list mints the same
number for a different content.

Add an optional `number` parameter, used verbatim when non-zero, so
callers can partition the number space (RFC 5280 5.2.3 requires
monotonicity for a given CRL scope and issuer, not globally).

Also fix the default increment path: the extension holds a DER integer
with leading zeros stripped, so reading it straight into an uint64_t only
worked for numbers wide enough to occupy all 8 bytes. Signing a list
numbered 5 produced 0x0500000000000001 instead of 6. Read and write the
number as a big-endian byte string instead, which makes the `endian()`
helper unnecessary.

Add tests covering the default numbering, explicit numbers (including
values with leading zero bytes and the largest positive 8-byte integer),
pack/unpack round-trips, and a device-partitioned numbering scheme.
2026-07-29 13:26:23 -04:00
Adrien Beraud f9332985f5 thread_pool: only include ciso646 on C++<20 2026-07-06 15:45:01 -04:00
Adrien Beraud 732da0e478 default types: add ValueId to TrustRequest 2026-06-11 16:52:03 -04:00
Adrien Béraud 0bb393ec36 http: allow to build Request by std::string url 2026-06-03 19:30:53 -04:00
vx32 947610003b http: allow to build Url from std::string, cleanup 2026-06-03 19:14:32 -04:00
François-Simon Fauteux-Chapleau 7cf3e8b738 thread_pool: notify when task queue becomes empty
This prevents ThreadPool::stop from hanging if it is called with `wait`
set to true (which is notably the case in ThreadPool::join) and the
pool's task queue is non-empty.
2026-05-21 10:17:08 -04:00
Adrien Béraud 8b96652b32 dhtrunner: add signatures for putEncrypted(InfoHash, PkId to, Value&&) 2026-05-18 10:36:48 -04:00
Adrien Béraud 44f2aaa876 remove deprecated APIs 2026-05-11 09:27:56 -04:00
copilot-swe-agent[bot]andaberaud aae1bb2ae5 infohash: fix unaligned memory access on strict-alignment architectures (sparc64)
Agent-Logs-Url: https://github.com/savoirfairelinux/opendht/sessions/d4106989-db2c-4d34-b1ea-4ca2ecab524a

Co-authored-by: aberaud <101827+aberaud@users.noreply.github.com>
2026-04-02 16:08:33 -04:00
Pavan Koushik Nellore 593e59e093 proxy: add pushType field and propagate through proxy notifications 2026-04-02 15:03:11 -04:00
Adrien Béraud 99da0be2b7 misc: remove lock mutex template argument 2026-03-15 00:00:33 -04:00
Adrien Béraud 8acafe844d dht: release pending bootstrap after first attempt 2026-03-13 20:39:40 -04:00
Adrien Béraud 55af602026 tests: fix build with GCC 14 2026-03-13 15:32:45 -04:00
Adrien Béraud 603e777c9e dht: refresh token on listen confirmation 2026-03-13 15:10:12 -04:00
Adrien Béraud 4bd796cddd dht: state is connecting if bootstrap is pending 2026-03-13 11:53:32 -04:00
Adrien Béraud f94f072ad6 dht: send token in listen confirmation 2026-03-13 00:53:05 -04:00
Adrien Béraud c51beca374 dht: properly persist expiration time 2026-03-13 00:38:31 -04:00
Adrien Béraud 0eacd7fe6c tools: move to dht::tools namespace 2026-03-13 00:04:49 -04:00
Adrien Béraud b892be092a infohash: make core APIs constexpr
Make Hash default constructor, hex constructor, data(), const
operator[] and fromString constexpr. Change fromString to be
non-throwing: it now uses a validity flag and fills the hash with zeros
on parse error instead of throwing. Add a constexpr toHexArray helper
that returns a std::array<char, N*2> for compile-time hex conversion.
Update tests to include <string_view>, use std::string_view, add
constexpr InfoHash usage and a static_assert for the hex array.
2026-03-08 23:34:50 -04:00
Adrien Béraud 931d79b701 dht: handle out-of-order partial messages 2026-03-08 12:26:23 -04:00
Adrien Béraud 9ad6b3a608 dhtrunner, securedht: mark findCertificate(InfoHash hash) as deprecated 2026-02-27 14:26:39 -05:00
Adrien Béraud 9ebe6e8bb6 tools: pretty print byte count 2026-02-24 00:15:47 -05:00
Adrien Béraud 0263104373 proxy client: implement getLocalStoreSize 2026-02-22 18:55:02 -05:00
Adrien Béraud 0fc7e451e6 dhtrunner: move getNodeInfo implementation to securedht 2026-02-22 17:16:28 -05:00
Adrien Béraud 85cbee0f1a dht: add local storage stats in NodeInfo 2026-02-22 16:27:35 -05:00