diff options
Diffstat (limited to 'link')
| -rw-r--r-- | link/link-protocol.c | 19 | ||||
| -rw-r--r-- | link/link-protocol.h | 13 | ||||
| -rw-r--r-- | link/test-link.c | 40 |
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]; |
