diff options
| author | eunhhu <[email protected]> | 2026-09-28 19:42:27 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-28 19:42:27 +0100 |
| commit | 3308de49adfdb2a55c646e6b675da998f2c36900 (patch) | |
| tree | 6548fc239b9f0ab8aabd32747fbaae0fd5517e44 /serve/link.c | |
| parent | 63cc9554f2cd93e46d0413a21cbce05014e2f2b3 (diff) | |
| download | pure-3308de49adfdb2a55c646e6b675da998f2c36900.tar.gz pure-3308de49adfdb2a55c646e6b675da998f2c36900.zip | |
Remember each sender's secret beside the node's key: no X25519 at restart
A listener lets a quiet sender's secret go with its active state, so a
node started again made one X25519 for every sender that came back (169
us on this Pi, most of a wake's CPU): asymmetric work in proportion to the
links, which design 011 counts as a reason to design again. The secret is
now remembered in a file beside the key (KEY.links, 0600, begun again for
another node's key): a place of 64 bytes a sender in the order allowed,
mapped, so a sender at rest takes no memory for it. A place is the first
28 bytes of the sender's key, a check (FNV-1a of the key and the secret)
and the secret, written secret first and key last; a place whose check
fails is made again. The UDP link checks see a receiver started again
make no X25519.
Diffstat (limited to 'serve/link.c')
| -rw-r--r-- | serve/link.c | 70 |
1 files changed, 67 insertions, 3 deletions
diff --git a/serve/link.c b/serve/link.c index ed1b695..e8c8a7a 100644 --- a/serve/link.c +++ b/serve/link.c @@ -13,8 +13,10 @@ #include <stdlib.h> #include <string.h> #include <sys/eventfd.h> +#include <sys/mman.h> #include <sys/random.h> #include <sys/socket.h> +#include <sys/stat.h> #include <time.h> #include <unistd.h> @@ -783,6 +785,8 @@ struct PureLinkListener { unsigned port; uint64_t run, idle, swept, trimmed, untrimmed; PureAead *aead; /* keyed by each packet's run */ + unsigned char *memo; /* the secrets remembered (pure_link_remember), mapped */ + size_t memo_size; int fd, wake; pthread_t thread; bool started; @@ -926,12 +930,71 @@ static void drop_held(Stream *st, size_t k) { --st->held_count; } -/* A peer's active state, made at its first packet: the secret its key shares with ours. */ +/* The memo of secrets (pure_link_remember): a header, then a place a peer + * in the order allowed, each the first 28 bytes of its key, a check (FNV-1a + * of its whole key and the secret) and the secret. A place whose check + * fails (never made, another key, cut short by a crash) is made again. */ +enum { MEMO_HEAD = 64, MEMO_PLACE = 64 }; +static const unsigned char memo_magic[8] = {'P', 'L', 'M', '1', 0, 0, 0, 0}; + +static uint32_t memo_check(const unsigned char key[PURE_SEAL_KEY], const unsigned char secret[32]) { + return fnv(fnv(2166136261u, key, PURE_SEAL_KEY), secret, 32); +} + +bool pure_link_remember(PureLinkListener *s, const char *path, char *error, size_t size) { + int fd = open(path, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC, 0600); + struct stat info; + unsigned char head[MEMO_HEAD]; + size_t needed = MEMO_HEAD + (s->peer_count ? s->peer_count : 1) * MEMO_PLACE; + bool ok = fd >= 0 && !fstat(fd, &info) && S_ISREG(info.st_mode) && !(info.st_mode & 077); + /* Another node's key, or none yet: begun again. */ + bool ours = ok && pread(fd, head, MEMO_HEAD, 0) == MEMO_HEAD && !memcmp(head, memo_magic, 8) && + !memcmp(head + 8, s->own_public, PURE_SEAL_KEY); + if (ok && !ours) { + memset(head, 0, MEMO_HEAD); + memcpy(head, memo_magic, 8); + memcpy(head + 8, s->own_public, PURE_SEAL_KEY); + ok = !ftruncate(fd, 0) && pwrite(fd, head, MEMO_HEAD, 0) == MEMO_HEAD; + } + ok = ok && (ours && (size_t)info.st_size >= needed ? true : !ftruncate(fd, (off_t)needed)); + void *map = ok ? mmap(NULL, needed, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0) : MAP_FAILED; + if (fd >= 0) close(fd); + if (map == MAP_FAILED) { + snprintf(error, size, "%s: cannot keep secrets there (a file only its owner may read)", path); + return false; + } + s->memo = map; + s->memo_size = needed; + return true; +} + +/* A peer's active state, made at its first packet: the secret its key + * shares with ours, remembered or made (one X25519, then remembered: the + * secret first, the key that makes the place its own last). */ static Active *activate(PureLinkListener *s, Peer *peer, uint64_t now) { Active *a = calloc(1, sizeof(*a)), **actives = room(s->actives, &s->active_capacity, s->active_count, sizeof(*actives), 64); if (actives) s->actives = actives; - if (!a || !actives || !pure_link_secret(s->own, peer->key, a->secret)) { - if (a) explicit_bzero(a->secret, sizeof(a->secret)); + if (!a || !actives) { + free(a); + return NULL; + } + size_t index = (size_t)(peer - s->peers); + unsigned char *place = s->memo && MEMO_HEAD + (index + 1) * MEMO_PLACE <= s->memo_size ? + s->memo + MEMO_HEAD + index * MEMO_PLACE : NULL; + if (place && !memcmp(place, peer->key, 28) && get32(place + 28) == memo_check(peer->key, place + 32)) { + memcpy(a->secret, place + 32, 32); + ++s->counts.remembered; + } else if (pure_link_secret(s->own, peer->key, a->secret)) { + if (place) { + memset(place, 0, 28); + __atomic_thread_fence(__ATOMIC_RELEASE); + memcpy(place + 32, a->secret, 32); + put32(place + 28, memo_check(peer->key, a->secret)); + __atomic_thread_fence(__ATOMIC_RELEASE); + memcpy(place, peer->key, 28); + } + } else { + explicit_bzero(a, sizeof(*a)); free(a); return NULL; } @@ -1409,6 +1472,7 @@ void pure_link_listener_close(PureLinkListener *s, PureLinkCounts *counts) { } while (s->active_count) let_go(s, s->actives[0]); pure_aead_free(s->aead); + if (s->memo) munmap(s->memo, s->memo_size); free(s->actives); free(s->peers); free(s->index); |
