aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--link/link-protocol.c19
-rw-r--r--link/link-protocol.h13
-rw-r--r--link/test-link.c40
3 files changed, 52 insertions, 20 deletions
diff --git a/link/link-protocol.c b/link/link-protocol.c
index 1f43e6c..8845ea5 100644
--- a/link/link-protocol.c
+++ b/link/link-protocol.c
@@ -22,8 +22,9 @@ enum { F_PADDING = 0, F_STREAM = 1, F_RECORD = 2, F_ACK = 3, F_STORED = 4, F_PIN
F_RESPONSE = 8, F_OFFER = 9, F_ACCEPT = 10, STORED_GAP = 1 };
/* A packet's first byte is VERSION with the sending side's epoch (mod 16)
* in its low four bits; OFFER and ACCEPT are an epoch's number (u32) and a
- * public key made for it. */
-enum { EPOCH_MASK = 15, EPOCH_FRAME = 1 + 4 + PURE_SEAL_KEY };
+ * public key made for it, and ACCEPT the first bytes of the key it answers
+ * (a sender may offer an epoch again with another key). */
+enum { EPOCH_MASK = 15, EPOCH_FRAME = 1 + 4 + PURE_SEAL_KEY, ANSWERS = 4, ACCEPT_FRAME = EPOCH_FRAME + ANSWERS };
enum { RECORDS_HEAD = 10 }; /* type, the first number, the count */
enum { RANGES = 32, RUNS = 2, UNITS = 64, CREDIT = 1024, BATCH = 256, RECORD_HEAD = 19 };
enum { WINDOW_FIRST = 10 * DATAGRAM, WINDOW_LEAST = 2 * DATAGRAM };
@@ -712,10 +713,12 @@ void pure_link_sender_take(PureLinkSender *l, const unsigned char *packet, size_
if (l->responding < RESPONSES) ++l->responding;
}
at += 8;
- } else if (type == F_ACCEPT) { /* the epoch offered begins */
- if (at + EPOCH_FRAME - 1 > size) break;
- if (l->offering && get32(plain + at) == l->offer_epoch) begin_epoch(l, plain + at + 4, now);
- at += EPOCH_FRAME - 1;
+ } else if (type == F_ACCEPT) { /* the epoch offered begins: an answer to this offer, not one before */
+ if (at + ACCEPT_FRAME - 1 > size) break;
+ if (l->offering && get32(plain + at) == l->offer_epoch &&
+ !memcmp(plain + at + EPOCH_FRAME - 1, l->offer_public, ANSWERS))
+ begin_epoch(l, plain + at + 4, now);
+ at += ACCEPT_FRAME - 1;
} else if (type != F_PING)
break; /* padding, or what this side does not know: the rest */
}
@@ -954,6 +957,7 @@ void pure_link_sender_tick(PureLinkSender *l, uint64_t now) {
offer(l, 1);
l->renegotiating = true;
l->offer_again = 0;
+ ++l->counts.fallbacks;
}
if (l->loss_time && l->loss_time <= now) detect_lost(l, now);
else if (l->eliciting && l->last_eliciting + pto_period(l) <= now) {
@@ -2165,7 +2169,8 @@ static void answer(PureLinkReceiver *s, uint64_t now, PureLinkSend *send, void *
body[at] = F_ACCEPT;
put32(body + at + 1, r->accept_epoch);
memcpy(body + at + 5, r->accept_public, PURE_SEAL_KEY);
- at += EPOCH_FRAME;
+ memcpy(body + at + EPOCH_FRAME, r->offered, ANSWERS);
+ at += ACCEPT_FRAME;
}
if (r->ack_due && r->got_count) {
body[at++] = F_ACK;
diff --git a/link/link-protocol.h b/link/link-protocol.h
index f54514b..a8dd826 100644
--- a/link/link-protocol.h
+++ b/link/link-protocol.h
@@ -95,7 +95,9 @@
* epoch's secret, until it is accepted
* 0x0a ACCEPT u32 epoch, a public key: the receiver's half, until a
* packet of that epoch opens (the same offer again gets the
- * same; one a second for a run)
+ * same; one a second for a sender), then the first four
+ * bytes of the key offered it answers (a sender that took
+ * its epoch for lost offers epoch 1 again with another)
* 0x00 PADDING to the end of the packet: a full packet sent in a burst is
* a whole datagram
* Losses are detected as RFC 9002 says, with RACK's patience for packets
@@ -114,12 +116,13 @@ enum { PURE_LINK_DATAGRAM = 1200, PURE_LINK_DATAGRAM_MOST = 1452, PURE_LINK_NAME
* packets lost and acknowledged, packets received, deliveries handed over,
* the last number stored; packets called lost that came late, and
* congestion events undone for it; runs keyed (keys made from a secret),
- * secrets made (X25519 of the nodes' keys), epochs begun; a receiver's senders woken (their active
- * state made), of them with a secret remembered (no X25519), let go when
- * idle, and active now. */
+ * secrets made (X25519), epochs begun, and a sender's epochs taken for lost
+ * (begun again from the nodes' keys); a receiver's senders woken (their
+ * active state made), of them with a secret remembered (no X25519), let go
+ * when idle, and active now. */
typedef struct {
uint64_t packets, records, resent, lost, acknowledged, received, deliveries, stored, late, undone, keyed, secrets,
- epochs, woken, remembered, swept, active;
+ epochs, fallbacks, woken, remembered, swept, active;
} PureLinkCounts;
/* ---- The sending side: one output's saved sends to a receiver's input, as
diff --git a/link/test-link.c b/link/test-link.c
index 957c383..0cfd3e4 100644
--- a/link/test-link.c
+++ b/link/test-link.c
@@ -83,6 +83,10 @@ typedef struct {
uint64_t attacker_in, attacker_out, bent, replayed;
unsigned loss, doubled, spread_ms; /* percent, percent, the most a packet is held back */
uint64_t now, sent;
+ /* The path cut (nothing passes) until then: cuts times, 3 s each, the
+ * moment the sender begins an epoch (before the receiver has seen it). */
+ uint64_t cut_until;
+ unsigned cuts;
/* The receiving app: its last heard (kept across the receiver's
* restarts), what it said of deliveries (given at the next turn). */
uint64_t heard, applied, deliveries;
@@ -188,6 +192,7 @@ typedef struct {
bool tamper; /* every packet comes changed three ways first */
uint64_t epoch_ms; /* epochs this long (0: an hour) */
bool forgetful; /* the receiver keeps no memo: it starts again knowing no epoch */
+ unsigned cuts; /* the path cut for 3 s so many times, as an epoch begins */
} Case;
static bool nothing_sent(void *context, const unsigned char *packet, size_t size, const unsigned char *to, size_t to_size) {
@@ -353,7 +358,7 @@ static Outcome run(const Case *c, const char *dir) {
unlink(memo);
World w = {.packets = malloc(PACKETS * sizeof(Packet)), .loss = c->loss, .doubled = c->doubled,
.spread_ms = c->spread_ms, .fail_every = c->fail_every, .replay = c->replay, .tamper = c->tamper,
- .history = malloc(HISTORY * sizeof(Packet))};
+ .cuts = c->cuts, .history = malloc(HISTORY * sizeof(Packet))};
if (!w.packets || !w.history) exit(1);
char from[8] = "sender0";
epoch_ms = c->epoch_ms;
@@ -410,9 +415,19 @@ static Outcome run(const Case *c, const char *dir) {
++k;
continue;
}
+ if (w.now < w.cut_until) { /* the path is cut */
+ *p = w.packets[--w.count];
+ continue;
+ }
if (w.tamper) tamper(&w, s, r, p, from);
if (p->to_receiver) pure_link_receiver_take(r, p->bytes, p->size, from, 7, w.now);
- else pure_link_sender_take(s, p->bytes, p->size, w.now);
+ else {
+ PureLinkCounts before, after;
+ pure_link_sender_counts(s, &before);
+ pure_link_sender_take(s, p->bytes, p->size, w.now);
+ pure_link_sender_counts(s, &after);
+ if (w.cuts && after.epochs > before.epochs) --w.cuts, w.cut_until = w.now + 3000 * MS;
+ }
w.history[w.history_count++ % HISTORY] = *p;
*p = w.packets[--w.count];
}
@@ -442,6 +457,7 @@ static Outcome run(const Case *c, const char *dir) {
pure_link_sender_counts(s, &o.sent);
pure_link_receiver_counts(r, &o.heard);
o.sent.packets += sent_before.packets, o.sent.resent += sent_before.resent, o.sent.epochs += sent_before.epochs;
+ o.sent.fallbacks += sent_before.fallbacks;
o.heard.woken += heard_before.woken, o.heard.swept += heard_before.swept;
CHECK(w.heard == c->records && w.applied == c->records, "%s: applied %llu, heard %llu of %llu", c->name,
(unsigned long long)w.applied, (unsigned long long)w.heard, (unsigned long long)c->records);
@@ -605,7 +621,7 @@ int main(void) {
return 1;
}
/* A clean network: nothing is sent twice. */
- Case clean = {"clean", 3000, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, false, 0, false};
+ Case clean = {"clean", 3000, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, false, 0, false, 0};
Outcome o = run(&clean, dir);
CHECK(o.sent.resent == 0 && o.sent.lost == 0, "clean: %llu sent again, %llu lost", (unsigned long long)o.sent.resent,
(unsigned long long)o.sent.lost);
@@ -614,7 +630,7 @@ int main(void) {
* starting again: the receiver remembers its secret, so it wakes the
* sender without an X25519. */
for (uint64_t s = 1; s <= 4; ++s) {
- Case rough = {"rough", 3000, s * 7919, 10, 3, 20, 37, 1500, 800, 2200, 300, 0, false, 0, false};
+ Case rough = {"rough", 3000, s * 7919, 10, 3, 20, 37, 1500, 800, 2200, 300, 0, false, 0, false, 0};
o = run(&rough, dir);
CHECK(o.sent.resent > 0 && o.heard.swept >= 1 && o.heard.woken >= 1 && o.heard.remembered >= 1,
"rough: sent again %llu, swept %llu, woken %llu, remembered %llu", (unsigned long long)o.sent.resent,
@@ -624,21 +640,29 @@ int main(void) {
/* An attacker on the path: packets changed on the way, and packets of
* the past sent again, from the sender's address and its own, across the
* same restarts and pause. */
- Case changed = {"changed", 1500, 31, 3, 0, 5, 0, 0, 0, 0, 0, 0, true, 0, false};
+ Case changed = {"changed", 1500, 31, 3, 0, 5, 0, 0, 0, 0, 0, 0, true, 0, false, 0};
run(&changed, dir);
for (uint64_t s = 1; s <= 2; ++s) {
- Case again = {"replayed", 3000, s * 104729, 5, 3, 20, 37, 1500, 800, 2200, 300, 25, false, 0, false};
+ Case again = {"replayed", 3000, s * 104729, 5, 3, 20, 37, 1500, 800, 2200, 300, 25, false, 0, false, 0};
run(&again, dir);
}
/* Epochs a fifth of a second long (a new one a second, as the receiver
* accepts), and a receiver that starts again with no memo: it knows no
* epoch, and the sender begins them again. */
- Case epochs = {"epochs", 3000, 271, 5, 3, 20, 37, 1500, 800, 2200, 300, 10, false, 200, false};
+ Case epochs = {"epochs", 3000, 271, 5, 3, 20, 37, 1500, 800, 2200, 300, 10, false, 200, false, 0};
o = run(&epochs, dir);
CHECK(o.sent.epochs >= 10, "epochs: %llu begun", (unsigned long long)o.sent.epochs);
- Case forgetful = {"forgetful", 3000, 409, 5, 3, 20, 37, 0, 800, 0, 0, 0, false, 0, true};
+ Case forgetful = {"forgetful", 3000, 409, 5, 3, 20, 37, 0, 800, 0, 0, 0, false, 0, true, 0};
o = run(&forgetful, dir);
CHECK(o.sent.epochs >= 2, "forgetful: %llu epochs begun (none again)", (unsigned long long)o.sent.epochs);
+ /* The path cut for 3 s the moment the sender begins an epoch, three
+ * times: the receiver never saw the epoch used, and the sender, hearing
+ * nothing, offers another epoch 1 with a new key. The accept of the old
+ * offer must not begin it. */
+ Case cut = {"cut", 3000, 919, 0, 0, 20, 37, 0, 0, 0, 0, 0, false, 200, false, 3};
+ o = run(&cut, dir);
+ CHECK(o.sent.epochs >= 4 && o.sent.fallbacks == 3, "cut: %llu epochs begun, %llu taken for lost (3 cuts)",
+ (unsigned long long)o.sent.epochs, (unsigned long long)o.sent.fallbacks);
impostors(dir);
flood(dir);
char command[600];