quic 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,829 @@
1
+ #include "endpoint.h"
2
+
3
+ #include <string.h>
4
+
5
+ #include <openssl/rand.h>
6
+
7
+ /* How many Stateless Resets an Endpoint sends in a burst; the allowance comes
8
+ back at one a second. As examples/server.cc's
9
+ NGTCP2_STATELESS_RESET_BURST. */
10
+ #define QUIC_STATELESS_RESET_BURST 100
11
+
12
+ /* Length of the secret Stateless Reset tokens are derived from. */
13
+ #define QUIC_STATELESS_RESET_SECRETLEN 32
14
+
15
+ /* How many of the Endpoint's own answers may wait for #write_pkt. Any
16
+ datagram can prompt one, so without a limit a flood read faster than it
17
+ is answered would take memory without bound. Every one of them may be
18
+ lost on the wire anyway, so those beyond are dropped. */
19
+ #define QUIC_ENDPOINT_MAX_RESPONSES 1024
20
+
21
+ VALUE rb_cQUICEndpoint;
22
+
23
+ /* Many server connections on one UDP socket. The Endpoint routes each
24
+ datagram by its DCID and answers what no connection can (Version
25
+ Negotiation, Stateless Reset, CONNECTION_REFUSED); the I/O loop stays
26
+ with the caller. Dispatch follows Server::read_pkt in ngtcp2's
27
+ examples/server.cc. */
28
+ struct quic_endpoint {
29
+ /* CID -> the Connection::Server it routes to. A connection is entered
30
+ under the client's first DCID and every SCID it issues. Keys are
31
+ xmalloc()ed ngtcp2_cid copies; values are marked by dmark and followed
32
+ by dcompact. */
33
+ st_table *cids;
34
+ /* The connections in the table, in the order they joined. */
35
+ VALUE conns;
36
+ /* The index into conns #write_pkt continues from. */
37
+ long write_cursor;
38
+ /* [packet, sockaddr] pairs the Endpoint itself answered with, sent before
39
+ any connection's packets. */
40
+ VALUE responses;
41
+ /* Connections that left the table since the last #take_closed. */
42
+ VALUE closed;
43
+ /* The local address of every path (no IP_PKTINFO in v1). */
44
+ VALUE local_sockaddr;
45
+ /* What new connections are made with. */
46
+ VALUE tls_context;
47
+ VALUE transport_params;
48
+ VALUE settings;
49
+ long max_connections;
50
+ bool accepting;
51
+ uint8_t reset_secret[QUIC_STATELESS_RESET_SECRETLEN];
52
+ /* Stateless Resets left to send, and when the allowance last grew. */
53
+ size_t reset_bucket;
54
+ ngtcp2_tstamp reset_refill_ts;
55
+ };
56
+
57
+ /* Seeds the CID hash, so that a client cannot pick initial DCIDs that
58
+ collide. */
59
+ static st_index_t quic_cid_hash_seed;
60
+
61
+ static int quic_cid_cmp(st_data_t a, st_data_t b) {
62
+ return !ngtcp2_cid_eq((const ngtcp2_cid *)a, (const ngtcp2_cid *)b);
63
+ }
64
+
65
+ static st_index_t quic_cid_hash(st_data_t a) {
66
+ const ngtcp2_cid *cid = (const ngtcp2_cid *)a;
67
+ return st_hash(cid->data, cid->datalen, quic_cid_hash_seed);
68
+ }
69
+
70
+ static const struct st_hash_type quic_cid_hash_type = {quic_cid_cmp,
71
+ quic_cid_hash};
72
+
73
+ static int quic_endpoint_mark_i(st_data_t key, st_data_t value, st_data_t arg) {
74
+ (void)key;
75
+ (void)arg;
76
+ rb_gc_mark_movable((VALUE)value);
77
+ return ST_CONTINUE;
78
+ }
79
+
80
+ static void quic_endpoint_mark(void *ptr) {
81
+ quic_endpoint_t *ep = (quic_endpoint_t *)ptr;
82
+ if (ep->cids != NULL)
83
+ st_foreach(ep->cids, quic_endpoint_mark_i, 0);
84
+ rb_gc_mark_movable(ep->conns);
85
+ rb_gc_mark_movable(ep->responses);
86
+ rb_gc_mark_movable(ep->closed);
87
+ rb_gc_mark_movable(ep->local_sockaddr);
88
+ rb_gc_mark_movable(ep->tls_context);
89
+ rb_gc_mark_movable(ep->transport_params);
90
+ rb_gc_mark_movable(ep->settings);
91
+ }
92
+
93
+ static int quic_endpoint_compact_check_i(st_data_t key, st_data_t value,
94
+ st_data_t arg, int error) {
95
+ (void)key;
96
+ (void)value;
97
+ (void)arg;
98
+ (void)error;
99
+ return ST_REPLACE;
100
+ }
101
+
102
+ static int quic_endpoint_compact_replace_i(st_data_t *key, st_data_t *value,
103
+ st_data_t arg, int existing) {
104
+ (void)key;
105
+ (void)arg;
106
+ (void)existing;
107
+ *value = (st_data_t)rb_gc_location((VALUE)*value);
108
+ return ST_CONTINUE;
109
+ }
110
+
111
+ static void quic_endpoint_compact(void *ptr) {
112
+ quic_endpoint_t *ep = (quic_endpoint_t *)ptr;
113
+ if (ep->cids != NULL) {
114
+ st_foreach_with_replace(ep->cids, quic_endpoint_compact_check_i,
115
+ quic_endpoint_compact_replace_i, 0);
116
+ }
117
+ ep->conns = rb_gc_location(ep->conns);
118
+ ep->responses = rb_gc_location(ep->responses);
119
+ ep->closed = rb_gc_location(ep->closed);
120
+ ep->local_sockaddr = rb_gc_location(ep->local_sockaddr);
121
+ ep->tls_context = rb_gc_location(ep->tls_context);
122
+ ep->transport_params = rb_gc_location(ep->transport_params);
123
+ ep->settings = rb_gc_location(ep->settings);
124
+ }
125
+
126
+ static int quic_endpoint_free_key_i(st_data_t key, st_data_t value,
127
+ st_data_t arg) {
128
+ (void)value;
129
+ (void)arg;
130
+ xfree((void *)key);
131
+ return ST_CONTINUE;
132
+ }
133
+
134
+ /* Connections still in the table are unreachable too (each marks the
135
+ Endpoint), and quic_conn_free never reaches back here. */
136
+ static void quic_endpoint_free(void *ptr) {
137
+ quic_endpoint_t *ep = (quic_endpoint_t *)ptr;
138
+ if (ep->cids != NULL) {
139
+ st_foreach(ep->cids, quic_endpoint_free_key_i, 0);
140
+ st_free_table(ep->cids);
141
+ }
142
+ OPENSSL_cleanse(ep->reset_secret, sizeof(ep->reset_secret));
143
+ xfree(ep);
144
+ }
145
+
146
+ static size_t quic_endpoint_size(const void *ptr) {
147
+ const quic_endpoint_t *ep = (const quic_endpoint_t *)ptr;
148
+ return sizeof(*ep) + (ep->cids != NULL ? st_memsize(ep->cids) : 0);
149
+ }
150
+
151
+ static const rb_data_type_t quic_endpoint_data_type = {
152
+ "QUIC::Endpoint",
153
+ {
154
+ quic_endpoint_mark,
155
+ quic_endpoint_free,
156
+ quic_endpoint_size,
157
+ quic_endpoint_compact,
158
+ },
159
+ NULL,
160
+ NULL,
161
+ RUBY_TYPED_FREE_IMMEDIATELY,
162
+ };
163
+
164
+ static quic_endpoint_t *quic_endpoint_get(VALUE self) {
165
+ quic_endpoint_t *ep;
166
+ TypedData_Get_Struct(self, quic_endpoint_t, &quic_endpoint_data_type, ep);
167
+ return ep;
168
+ }
169
+
170
+ void quic_endpoint_associate_cid(quic_endpoint_t *ep, const ngtcp2_cid *cid,
171
+ VALUE conn) {
172
+ /* For a key already there, st_insert replaces the value and keeps the
173
+ key it has, so a copy is made only for a new key. */
174
+ if (st_lookup(ep->cids, (st_data_t)cid, NULL)) {
175
+ st_insert(ep->cids, (st_data_t)cid, (st_data_t)conn);
176
+ return;
177
+ }
178
+ ngtcp2_cid *key = ALLOC(ngtcp2_cid);
179
+ *key = *cid;
180
+ st_insert(ep->cids, (st_data_t)key, (st_data_t)conn);
181
+ }
182
+
183
+ void quic_endpoint_dissociate_cid(quic_endpoint_t *ep, const ngtcp2_cid *cid,
184
+ VALUE conn) {
185
+ st_data_t key = (st_data_t)cid;
186
+ st_data_t value;
187
+ if (!st_lookup(ep->cids, key, &value) || (VALUE)value != conn)
188
+ return;
189
+ /* st_delete hands back the stored key. */
190
+ if (st_delete(ep->cids, &key, &value))
191
+ xfree((void *)key);
192
+ }
193
+
194
+ int quic_endpoint_generate_reset_token(const quic_endpoint_t *ep,
195
+ uint8_t *token, const ngtcp2_cid *cid) {
196
+ return ngtcp2_crypto_generate_stateless_reset_token(
197
+ token, ep->reset_secret, sizeof(ep->reset_secret), cid) == 0
198
+ ? 0
199
+ : -1;
200
+ }
201
+
202
+ static VALUE quic_addr_str(const ngtcp2_addr *addr) {
203
+ return rb_str_new((const char *)addr->addr, (long)addr->addrlen);
204
+ }
205
+
206
+ /* Queue an answer for #write_pkt. Returns false when the queue is full and
207
+ the answer was dropped. */
208
+ static bool quic_endpoint_push_response(quic_endpoint_t *ep, VALUE pkt,
209
+ VALUE to) {
210
+ if (RARRAY_LEN(ep->responses) >= QUIC_ENDPOINT_MAX_RESPONSES)
211
+ return false;
212
+ rb_ary_push(ep->responses, rb_assoc_new(pkt, to));
213
+ return true;
214
+ }
215
+
216
+ static void quic_conn_set_error(quic_conn_t *c, VALUE exc) {
217
+ /* The first failure is the reason; what follows is its consequence. */
218
+ if (NIL_P(c->error))
219
+ c->error = exc;
220
+ }
221
+
222
+ /* Take the Stateless Resets that came back since the last call, one per
223
+ whole second. Computed on read_pkt rather than with a timer. */
224
+ static void quic_endpoint_refill_reset_bucket(quic_endpoint_t *ep,
225
+ ngtcp2_tstamp now) {
226
+ if (ep->reset_bucket >= QUIC_STATELESS_RESET_BURST) {
227
+ ep->reset_refill_ts = now;
228
+ return;
229
+ }
230
+ uint64_t n = (now - ep->reset_refill_ts) / NGTCP2_SECONDS;
231
+ if (n == 0)
232
+ return;
233
+ ep->reset_bucket = n >= QUIC_STATELESS_RESET_BURST - ep->reset_bucket
234
+ ? QUIC_STATELESS_RESET_BURST
235
+ : ep->reset_bucket + (size_t)n;
236
+ ep->reset_refill_ts += n * NGTCP2_SECONDS;
237
+ }
238
+
239
+ static void quic_endpoint_send_version_negotiation(quic_endpoint_t *ep,
240
+ const ngtcp2_version_cid *vc,
241
+ VALUE remote) {
242
+ uint8_t unused_bits;
243
+ uint32_t grease;
244
+ if (RAND_bytes(&unused_bits, 1) != 1 ||
245
+ RAND_bytes((uint8_t *)&grease, sizeof(grease)) != 1) {
246
+ return;
247
+ }
248
+ /* A reserved version (0x?a?a?a?a) ahead of the one supported, so that
249
+ clients keep handling versions they do not know (RFC 9000 section
250
+ 6.3). */
251
+ uint32_t versions[] = {(grease & 0xf0f0f0f0U) | 0x0a0a0a0aU,
252
+ NGTCP2_PROTO_VER_V1};
253
+
254
+ VALUE pkt = rb_str_buf_new(NGTCP2_MAX_UDP_PAYLOAD_SIZE);
255
+ ngtcp2_ssize n = ngtcp2_pkt_write_version_negotiation(
256
+ (uint8_t *)RSTRING_PTR(pkt), NGTCP2_MAX_UDP_PAYLOAD_SIZE, unused_bits,
257
+ vc->scid, vc->scidlen, vc->dcid, vc->dcidlen, versions,
258
+ sizeof(versions) / sizeof(versions[0]));
259
+ if (n <= 0)
260
+ return;
261
+ rb_str_set_len(pkt, n);
262
+ quic_endpoint_push_response(ep, pkt, rb_str_dup(remote));
263
+ }
264
+
265
+ /* Tell the peer that this Endpoint has no state for dcid, as
266
+ examples/server.cc's send_stateless_reset does. */
267
+ static void quic_endpoint_send_stateless_reset(quic_endpoint_t *ep,
268
+ const ngtcp2_cid *dcid,
269
+ size_t pktlen, VALUE remote) {
270
+ if (ep->reset_bucket == 0)
271
+ return;
272
+ ep->reset_bucket--;
273
+
274
+ ngtcp2_stateless_reset_token token;
275
+ if (quic_endpoint_generate_reset_token(ep, token.data, dcid) != 0)
276
+ return;
277
+
278
+ /* Shorter than the packet that prompted it, so that two endpoints cannot
279
+ keep answering each other's resets (RFC 9000 section 10.3.3): one byte
280
+ shorter up to 43 bytes, 42 bytes beyond. */
281
+ enum {
282
+ MAX_RANDLEN = NGTCP2_MAX_CIDLEN + 22 - NGTCP2_STATELESS_RESET_TOKENLEN
283
+ };
284
+ uint8_t rand_bytes[MAX_RANDLEN];
285
+ size_t randlen =
286
+ pktlen <= 43 ? pktlen - NGTCP2_STATELESS_RESET_TOKENLEN - 1 : MAX_RANDLEN;
287
+ if (RAND_bytes(rand_bytes, (int)randlen) != 1)
288
+ return;
289
+
290
+ VALUE pkt = rb_str_buf_new(NGTCP2_MAX_UDP_PAYLOAD_SIZE);
291
+ ngtcp2_ssize n = ngtcp2_pkt_write_stateless_reset2(
292
+ (uint8_t *)RSTRING_PTR(pkt), NGTCP2_MAX_UDP_PAYLOAD_SIZE, &token,
293
+ rand_bytes, randlen);
294
+ if (n <= 0)
295
+ return;
296
+ rb_str_set_len(pkt, n);
297
+ quic_endpoint_push_response(ep, pkt, rb_str_dup(remote));
298
+ }
299
+
300
+ /* Turn a new connection away with CONNECTION_REFUSED in an Initial packet,
301
+ without making any state for it. */
302
+ static void quic_endpoint_refuse(quic_endpoint_t *ep, const ngtcp2_pkt_hd *hd,
303
+ VALUE remote) {
304
+ VALUE pkt = rb_str_buf_new(NGTCP2_MAX_UDP_PAYLOAD_SIZE);
305
+ /* Despite its documentation, this returns the packet's length. */
306
+ ngtcp2_ssize n = ngtcp2_crypto_write_connection_close(
307
+ (uint8_t *)RSTRING_PTR(pkt), NGTCP2_MAX_UDP_PAYLOAD_SIZE, hd->version,
308
+ &hd->scid, &hd->dcid, NGTCP2_CONNECTION_REFUSED, NULL, 0);
309
+ if (n <= 0)
310
+ return;
311
+ rb_str_set_len(pkt, n);
312
+ quic_endpoint_push_response(ep, pkt, rb_str_dup(remote));
313
+ }
314
+
315
+ /* The closing and draining periods last three PTOs (RFC 9000 section
316
+ 10.2). */
317
+ static ngtcp2_tstamp quic_close_deadline(const quic_conn_t *c,
318
+ ngtcp2_tstamp now) {
319
+ return now + 3 * ngtcp2_conn_get_pto2(c->conn);
320
+ }
321
+
322
+ /* Enter the closing period with a CONNECTION_CLOSE carrying ccerr, and
323
+ queue that packet. Returns false when ngtcp2 cannot build it; the
324
+ connection then has to leave the table at once. */
325
+ static bool quic_endpoint_start_closing(quic_endpoint_t *ep, quic_conn_t *c,
326
+ const ngtcp2_ccerr *ccerr) {
327
+ ngtcp2_tstamp now = quic_now();
328
+ if (ngtcp2_conn_in_closing_period2(c->conn) ||
329
+ ngtcp2_conn_in_draining_period2(c->conn)) {
330
+ if (c->close_deadline == 0)
331
+ c->close_deadline = quic_close_deadline(c, now);
332
+ return true;
333
+ }
334
+
335
+ ngtcp2_path_storage ps;
336
+ ngtcp2_path_storage_zero(&ps);
337
+ ngtcp2_pkt_info pi;
338
+ VALUE pkt = rb_str_buf_new(QUIC_WRITE_PKT_BUFLEN);
339
+ ngtcp2_ssize n = ngtcp2_conn_write_connection_close(
340
+ c->conn, &ps.path, &pi, (uint8_t *)RSTRING_PTR(pkt),
341
+ QUIC_WRITE_PKT_BUFLEN, ccerr, now);
342
+ if (n < 0)
343
+ return false;
344
+
345
+ c->close_deadline = quic_close_deadline(c, now);
346
+ c->close_next_pkts_recv = 1;
347
+ if (n == 0)
348
+ return true;
349
+ rb_str_set_len(pkt, n);
350
+ c->closebuf = pkt;
351
+ quic_endpoint_push_response(ep, rb_str_dup(pkt),
352
+ quic_addr_str(&ps.path.remote));
353
+ return true;
354
+ }
355
+
356
+ /* A packet came for a connection in its closing period: send the
357
+ CONNECTION_CLOSE again, but no more than three times what the peer sent
358
+ and to every 1st, 2nd, 4th, 8th, ... packet only (examples/server.cc's
359
+ Handler::send_conn_close). */
360
+ static void quic_endpoint_resend_close(quic_endpoint_t *ep, quic_conn_t *c,
361
+ size_t pktlen, VALUE remote) {
362
+ if (NIL_P(c->closebuf))
363
+ return;
364
+
365
+ c->close_bytes_recv += pktlen;
366
+ c->close_pkts_recv++;
367
+ uint64_t len = (uint64_t)RSTRING_LEN(c->closebuf);
368
+ if (c->close_pkts_recv < c->close_next_pkts_recv ||
369
+ c->close_bytes_recv * 3 < c->close_bytes_sent + len) {
370
+ return;
371
+ }
372
+ if (!quic_endpoint_push_response(ep, rb_str_dup(c->closebuf),
373
+ rb_str_dup(remote)))
374
+ return;
375
+ c->close_bytes_sent += len;
376
+ c->close_next_pkts_recv *= 2;
377
+ }
378
+
379
+ /* Deal with a failure of connection c: rv from ngtcp2, or an exception a
380
+ Ruby callback left in c->pending_exc (rv is ignored then). Never raises
381
+ for it; the reason goes to c->error. Returns true when the connection has
382
+ to leave the table now, false when it stays for its closing or draining
383
+ period. */
384
+ static bool quic_endpoint_handle_error(quic_endpoint_t *ep, quic_conn_t *c,
385
+ int rv) {
386
+ VALUE exc = c->pending_exc;
387
+ c->pending_exc = Qnil;
388
+ if (!NIL_P(exc))
389
+ rv = NGTCP2_ERR_CALLBACK_FAILURE;
390
+
391
+ ngtcp2_ccerr ccerr;
392
+ switch (rv) {
393
+ case NGTCP2_ERR_DRAINING:
394
+ /* The peer closed the connection. */
395
+ c->close_deadline = quic_close_deadline(c, quic_now());
396
+ return false;
397
+ case NGTCP2_ERR_IDLE_CLOSE:
398
+ /* Gone without a word, as RFC 9000 section 10.1 has it. */
399
+ return true;
400
+ case NGTCP2_ERR_DROP_CONN:
401
+ case NGTCP2_ERR_RETRY:
402
+ quic_conn_set_error(c, quic_ngtcp2_error_new(rv));
403
+ return true;
404
+ case NGTCP2_ERR_CALLBACK_FAILURE:
405
+ quic_conn_set_error(c, NIL_P(exc) ? quic_ngtcp2_error_new(rv) : exc);
406
+ if (c->close_error_set) {
407
+ ccerr = c->close_error;
408
+ } else {
409
+ ngtcp2_ccerr_set_transport_error(&ccerr, NGTCP2_INTERNAL_ERROR, NULL, 0);
410
+ }
411
+ break;
412
+ case NGTCP2_ERR_CRYPTO:
413
+ /* Send the TLS alert itself, as examples/server.cc does; set_liberr
414
+ would only know that the handshake failed. */
415
+ quic_conn_set_error(c, quic_conn_error_new(c, rv));
416
+ ngtcp2_ccerr_set_tls_alert(&ccerr, ngtcp2_conn_get_tls_alert2(c->conn),
417
+ NULL, 0);
418
+ break;
419
+ default:
420
+ quic_conn_set_error(c, quic_conn_error_new(c, rv));
421
+ ngtcp2_ccerr_set_liberr(&ccerr, rv, NULL, 0);
422
+ break;
423
+ }
424
+ return !quic_endpoint_start_closing(ep, c, &ccerr);
425
+ }
426
+
427
+ /* Drop every table entry of conn and cut it loose from the Endpoint. */
428
+ static void quic_endpoint_detach(quic_endpoint_t *ep, VALUE conn,
429
+ quic_conn_t *c) {
430
+ quic_endpoint_dissociate_cid(
431
+ ep, ngtcp2_conn_get_client_initial_dcid2(c->conn), conn);
432
+
433
+ size_t n = ngtcp2_conn_get_scid2(c->conn, NULL);
434
+ VALUE tmp;
435
+ ngtcp2_cid *scids = ALLOCV_N(ngtcp2_cid, tmp, n);
436
+ ngtcp2_conn_get_scid2(c->conn, scids);
437
+ for (size_t i = 0; i < n; i++)
438
+ quic_endpoint_dissociate_cid(ep, &scids[i], conn);
439
+ ALLOCV_END(tmp);
440
+
441
+ c->endpoint = NULL;
442
+ c->endpoint_owner = Qnil;
443
+ /* The Endpoint never drives the connection again. */
444
+ quic_conn_close_qlog(c);
445
+ }
446
+
447
+ /* Take conn out of the table and hand it to #take_closed. */
448
+ static void quic_endpoint_remove(quic_endpoint_t *ep, VALUE conn,
449
+ quic_conn_t *c) {
450
+ quic_endpoint_detach(ep, conn, c);
451
+ long len = RARRAY_LEN(ep->conns);
452
+ for (long i = 0; i < len; i++) {
453
+ if (RARRAY_AREF(ep->conns, i) != conn)
454
+ continue;
455
+ rb_ary_delete_at(ep->conns, i);
456
+ if (i < ep->write_cursor)
457
+ ep->write_cursor--;
458
+ break;
459
+ }
460
+ rb_ary_push(ep->closed, conn);
461
+ }
462
+
463
+ void quic_endpoint_close_conn(quic_endpoint_t *ep, quic_conn_t *c,
464
+ const ngtcp2_ccerr *ccerr) {
465
+ if (!quic_endpoint_start_closing(ep, c, ccerr))
466
+ quic_endpoint_remove(ep, c->owner, c);
467
+ }
468
+
469
+ /* QUIC::Endpoint._new(local_sockaddr:, tls_context:, transport_params:,
470
+ settings:, max_connections:), every keyword required. Endpoint.new
471
+ (lib/quic/endpoint.rb) fills in the defaults. */
472
+ static VALUE quic_endpoint_s_new(int argc, VALUE *argv, VALUE klass) {
473
+ VALUE opts = Qnil;
474
+ rb_scan_args(argc, argv, "0:", &opts);
475
+ if (NIL_P(opts))
476
+ opts = rb_hash_new();
477
+
478
+ ID keywords[5];
479
+ VALUE values[5];
480
+ keywords[0] = rb_intern("local_sockaddr");
481
+ keywords[1] = rb_intern("tls_context");
482
+ keywords[2] = rb_intern("transport_params");
483
+ keywords[3] = rb_intern("settings");
484
+ keywords[4] = rb_intern("max_connections");
485
+ rb_get_kwargs(opts, keywords, 5, 0, values);
486
+
487
+ quic_require_sockaddr(values[0], "local_sockaddr");
488
+ quic_tls_context_get(values[1]);
489
+ /* Read both once here, so that a bad value raises now rather than on
490
+ every new connection. */
491
+ ngtcp2_transport_params params;
492
+ quic_fill_transport_params(&params, values[2]);
493
+ ngtcp2_settings settings;
494
+ quic_fill_settings(&settings, values[3]);
495
+ long max_connections = NUM2LONG(values[4]);
496
+ if (max_connections <= 0) {
497
+ rb_raise(rb_eArgError, "max_connections must be positive (got %ld)",
498
+ max_connections);
499
+ }
500
+
501
+ quic_endpoint_t *ep;
502
+ VALUE self = TypedData_Make_Struct(klass, quic_endpoint_t,
503
+ &quic_endpoint_data_type, ep);
504
+ ep->conns = Qnil;
505
+ ep->responses = Qnil;
506
+ ep->closed = Qnil;
507
+ ep->local_sockaddr = rb_str_new_frozen(values[0]);
508
+ ep->tls_context = values[1];
509
+ ep->transport_params = values[2];
510
+ ep->settings = values[3];
511
+ ep->max_connections = max_connections;
512
+ ep->accepting = true;
513
+ ep->reset_bucket = QUIC_STATELESS_RESET_BURST;
514
+ ep->reset_refill_ts = quic_now();
515
+ if (RAND_bytes(ep->reset_secret, sizeof(ep->reset_secret)) != 1) {
516
+ rb_raise(rb_eRuntimeError, "RAND_bytes(stateless reset secret) failed");
517
+ }
518
+ ep->conns = rb_ary_new();
519
+ ep->responses = rb_ary_new();
520
+ ep->closed = rb_ary_new();
521
+ ep->cids = st_init_table(&quic_cid_hash_type);
522
+ return self;
523
+ }
524
+
525
+ struct quic_endpoint_read_args {
526
+ VALUE self;
527
+ quic_endpoint_t *ep;
528
+ VALUE packet;
529
+ VALUE remote;
530
+ ngtcp2_path path;
531
+ };
532
+
533
+ /* A packet for a connection in the table. */
534
+ static VALUE quic_endpoint_dispatch(struct quic_endpoint_read_args *a,
535
+ VALUE conn) {
536
+ quic_conn_t *c = quic_conn_get(conn);
537
+ const uint8_t *data = (const uint8_t *)RSTRING_PTR(a->packet);
538
+ size_t datalen = (size_t)RSTRING_LEN(a->packet);
539
+
540
+ if (ngtcp2_conn_in_closing_period2(c->conn)) {
541
+ quic_endpoint_resend_close(a->ep, c, datalen, a->remote);
542
+ return Qnil;
543
+ }
544
+ if (c->close_deadline != 0)
545
+ return Qnil;
546
+
547
+ int rv = quic_conn_read_pkt_raw(c, &a->path, data, datalen);
548
+ if ((rv != 0 || !NIL_P(c->pending_exc)) &&
549
+ quic_endpoint_handle_error(a->ep, c, rv)) {
550
+ quic_endpoint_remove(a->ep, conn, c);
551
+ return Qnil;
552
+ }
553
+ return conn;
554
+ }
555
+
556
+ /* A packet for no connection in the table: a new connection, or one this
557
+ Endpoint has forgotten. */
558
+ static VALUE quic_endpoint_accept(struct quic_endpoint_read_args *a,
559
+ const ngtcp2_cid *dcid) {
560
+ quic_endpoint_t *ep = a->ep;
561
+ const uint8_t *data = (const uint8_t *)RSTRING_PTR(a->packet);
562
+ size_t datalen = (size_t)RSTRING_LEN(a->packet);
563
+
564
+ ngtcp2_pkt_hd hd;
565
+ if (ngtcp2_accept(&hd, data, datalen) != 0) {
566
+ /* A short header packet long enough that a reset shorter than it can
567
+ still pass for one (examples/server.cc). */
568
+ if (!(data[0] & 0x80) && datalen >= QUIC_SCID_LEN + 21) {
569
+ quic_endpoint_send_stateless_reset(ep, dcid, datalen, a->remote);
570
+ }
571
+ return Qnil;
572
+ }
573
+
574
+ if (!ep->accepting || RARRAY_LEN(ep->conns) >= ep->max_connections) {
575
+ quic_endpoint_refuse(ep, &hd, a->remote);
576
+ return Qnil;
577
+ }
578
+
579
+ /* As in Server._accept, a token is accepted without checking it. */
580
+ VALUE conn = quic_server_conn_new(rb_cQUICConnectionServer, &hd, &a->path,
581
+ ep->tls_context, ep->transport_params,
582
+ ep->settings, ep, a->self);
583
+ quic_conn_t *c = quic_conn_get(conn);
584
+ int rv = quic_conn_read_pkt_raw(c, &a->path, data, datalen);
585
+ if (rv != 0 || !NIL_P(c->pending_exc)) {
586
+ /* As examples/server.cc: send the CONNECTION_CLOSE, if there is one,
587
+ and forget the connection. It never joins the table. */
588
+ quic_endpoint_handle_error(ep, c, rv);
589
+ quic_endpoint_detach(ep, conn, c);
590
+ return Qnil;
591
+ }
592
+
593
+ quic_endpoint_associate_cid(ep, ngtcp2_conn_get_client_initial_dcid2(c->conn),
594
+ conn);
595
+ size_t n = ngtcp2_conn_get_scid2(c->conn, NULL);
596
+ VALUE tmp;
597
+ ngtcp2_cid *scids = ALLOCV_N(ngtcp2_cid, tmp, n);
598
+ ngtcp2_conn_get_scid2(c->conn, scids);
599
+ for (size_t i = 0; i < n; i++)
600
+ quic_endpoint_associate_cid(ep, &scids[i], conn);
601
+ ALLOCV_END(tmp);
602
+ rb_ary_push(ep->conns, conn);
603
+ return conn;
604
+ }
605
+
606
+ static VALUE quic_endpoint_read_pkt_body(VALUE arg) {
607
+ struct quic_endpoint_read_args *a = (struct quic_endpoint_read_args *)arg;
608
+ const uint8_t *data = (const uint8_t *)RSTRING_PTR(a->packet);
609
+ size_t datalen = (size_t)RSTRING_LEN(a->packet);
610
+
611
+ quic_endpoint_refill_reset_bucket(a->ep, quic_now());
612
+
613
+ /* ngtcp2_pkt_decode_version_cid asserts datalen > 0. */
614
+ if (datalen == 0)
615
+ return Qnil;
616
+
617
+ ngtcp2_version_cid vc;
618
+ int rv = ngtcp2_pkt_decode_version_cid(&vc, data, datalen, QUIC_SCID_LEN);
619
+ if (rv == NGTCP2_ERR_VERSION_NEGOTIATION) {
620
+ quic_endpoint_send_version_negotiation(a->ep, &vc, a->remote);
621
+ return Qnil;
622
+ }
623
+ /* A supported version never carries a DCID longer than this. */
624
+ if (rv != 0 || vc.dcidlen > NGTCP2_MAX_CIDLEN)
625
+ return Qnil;
626
+
627
+ ngtcp2_cid dcid;
628
+ ngtcp2_cid_init(&dcid, vc.dcid, vc.dcidlen);
629
+ st_data_t conn;
630
+ if (st_lookup(a->ep->cids, (st_data_t)&dcid, &conn)) {
631
+ return quic_endpoint_dispatch(a, (VALUE)conn);
632
+ }
633
+ return quic_endpoint_accept(a, &dcid);
634
+ }
635
+
636
+ static VALUE quic_endpoint_read_pkt_unlock(VALUE arg) {
637
+ rb_str_unlocktmp(((struct quic_endpoint_read_args *)arg)->packet);
638
+ return Qnil;
639
+ }
640
+
641
+ /* #read_pkt(packet, remote:, local: nil): route one datagram. Returns the
642
+ Connection::Server it went to, which may be a new one, or nil when the
643
+ Endpoint answered or dropped it. A connection's own failure is never
644
+ raised; it closes that connection (see quic_endpoint_handle_error). */
645
+ static VALUE quic_endpoint_read_pkt(int argc, VALUE *argv, VALUE self) {
646
+ VALUE packet = Qnil;
647
+ VALUE opts = Qnil;
648
+ rb_scan_args(argc, argv, "1:", &packet, &opts);
649
+ if (NIL_P(opts))
650
+ opts = rb_hash_new();
651
+
652
+ ID keywords[2];
653
+ VALUE values[2];
654
+ keywords[0] = rb_intern("remote");
655
+ keywords[1] = rb_intern("local");
656
+ rb_get_kwargs(opts, keywords, 1, 1, values);
657
+
658
+ quic_endpoint_t *ep = quic_endpoint_get(self);
659
+ VALUE remote = values[0];
660
+ VALUE local =
661
+ values[1] == Qundef || NIL_P(values[1]) ? ep->local_sockaddr : values[1];
662
+ quic_require_binary(packet, "packet");
663
+ quic_require_sockaddr(remote, "remote");
664
+ quic_require_sockaddr(local, "local");
665
+
666
+ struct quic_endpoint_read_args args;
667
+ args.self = self;
668
+ args.ep = ep;
669
+ args.packet = packet;
670
+ args.remote = remote;
671
+ args.path.local.addr = (struct sockaddr *)RSTRING_PTR(local);
672
+ args.path.local.addrlen = (ngtcp2_socklen)RSTRING_LEN(local);
673
+ args.path.remote.addr = (struct sockaddr *)RSTRING_PTR(remote);
674
+ args.path.remote.addrlen = (ngtcp2_socklen)RSTRING_LEN(remote);
675
+ args.path.user_data = NULL;
676
+
677
+ /* Callbacks run Ruby code, which must not change the packet under ngtcp2. */
678
+ rb_str_locktmp(packet);
679
+ VALUE conn = rb_ensure(quic_endpoint_read_pkt_body, (VALUE)&args,
680
+ quic_endpoint_read_pkt_unlock, (VALUE)&args);
681
+ RB_GC_GUARD(local);
682
+ RB_GC_GUARD(remote);
683
+ return conn;
684
+ }
685
+
686
+ /* #write_pkt: the next packet to send as [packet, destination sockaddr], or
687
+ nil when there is none left. The Endpoint's own answers go first, then
688
+ each connection's packets until it has none, resuming where the last
689
+ call stopped. After nil, the next call starts over. */
690
+ static VALUE quic_endpoint_write_pkt(VALUE self) {
691
+ quic_endpoint_t *ep = quic_endpoint_get(self);
692
+ for (;;) {
693
+ if (RARRAY_LEN(ep->responses) > 0)
694
+ return rb_ary_shift(ep->responses);
695
+ if (ep->write_cursor >= RARRAY_LEN(ep->conns)) {
696
+ ep->write_cursor = 0;
697
+ return Qnil;
698
+ }
699
+
700
+ VALUE conn = RARRAY_AREF(ep->conns, ep->write_cursor);
701
+ quic_conn_t *c = quic_conn_get(conn);
702
+ /* A closing connection's CONNECTION_CLOSE goes through responses. */
703
+ if (c->close_deadline != 0) {
704
+ ep->write_cursor++;
705
+ continue;
706
+ }
707
+
708
+ VALUE pkt = rb_str_buf_new(QUIC_WRITE_PKT_BUFLEN);
709
+ ngtcp2_path_storage ps;
710
+ ngtcp2_path_storage_zero(&ps);
711
+ ngtcp2_ssize n =
712
+ quic_conn_write_pkt_raw(conn, c, &ps.path, (uint8_t *)RSTRING_PTR(pkt));
713
+ if (n < 0 || !NIL_P(c->pending_exc)) {
714
+ if (quic_endpoint_handle_error(ep, c, n < 0 ? (int)n : 0)) {
715
+ quic_endpoint_remove(ep, conn, c);
716
+ }
717
+ continue;
718
+ }
719
+ if (n == 0) {
720
+ ep->write_cursor++;
721
+ continue;
722
+ }
723
+ rb_str_set_len(pkt, n);
724
+ return rb_assoc_new(pkt, quic_addr_str(&ps.path.remote));
725
+ }
726
+ }
727
+
728
+ /* #expiry: the earliest time (CLOCK_MONOTONIC ns) any connection in the
729
+ table needs #handle_expiry, the end of a closing or draining period
730
+ included. nil when no connection waits for one. */
731
+ static VALUE quic_endpoint_expiry(VALUE self) {
732
+ quic_endpoint_t *ep = quic_endpoint_get(self);
733
+ ngtcp2_tstamp earliest = UINT64_MAX;
734
+ for (long i = 0; i < RARRAY_LEN(ep->conns); i++) {
735
+ quic_conn_t *c = quic_conn_get(RARRAY_AREF(ep->conns, i));
736
+ ngtcp2_tstamp t = c->close_deadline != 0 ? c->close_deadline
737
+ : ngtcp2_conn_get_expiry2(c->conn);
738
+ if (t < earliest)
739
+ earliest = t;
740
+ }
741
+ return earliest == UINT64_MAX ? Qnil : ULL2NUM((unsigned long long)earliest);
742
+ }
743
+
744
+ /* #handle_expiry: run the timers of every connection due, and take out of
745
+ the table those whose closing or draining period is over or that timed
746
+ out. */
747
+ static VALUE quic_endpoint_handle_expiry(VALUE self) {
748
+ quic_endpoint_t *ep = quic_endpoint_get(self);
749
+ ngtcp2_tstamp now = quic_now();
750
+ /* A copy, since connections leave ep->conns along the way. */
751
+ VALUE conns = rb_ary_dup(ep->conns);
752
+ for (long i = 0; i < RARRAY_LEN(conns); i++) {
753
+ VALUE conn = RARRAY_AREF(conns, i);
754
+ quic_conn_t *c = quic_conn_get(conn);
755
+ if (c->endpoint == NULL)
756
+ continue;
757
+ if (c->close_deadline != 0) {
758
+ if (c->close_deadline <= now)
759
+ quic_endpoint_remove(ep, conn, c);
760
+ continue;
761
+ }
762
+ if (ngtcp2_conn_get_expiry2(c->conn) > now)
763
+ continue;
764
+
765
+ c->now = now;
766
+ int rv = ngtcp2_conn_handle_expiry(c->conn, now);
767
+ if ((rv != 0 || !NIL_P(c->pending_exc)) &&
768
+ quic_endpoint_handle_error(ep, c, rv)) {
769
+ quic_endpoint_remove(ep, conn, c);
770
+ }
771
+ }
772
+ RB_GC_GUARD(conns);
773
+ return Qnil;
774
+ }
775
+
776
+ /* #take_closed: the connections that left the table since the last call,
777
+ in the order they left. */
778
+ static VALUE quic_endpoint_take_closed(VALUE self) {
779
+ quic_endpoint_t *ep = quic_endpoint_get(self);
780
+ VALUE closed = ep->closed;
781
+ ep->closed = rb_ary_new();
782
+ return closed;
783
+ }
784
+
785
+ /* #connections: the connections in the table, closing and draining ones
786
+ included. */
787
+ static VALUE quic_endpoint_connections(VALUE self) {
788
+ return rb_ary_dup(quic_endpoint_get(self)->conns);
789
+ }
790
+
791
+ /* #stop_accepting: refuse every new connection from now on with
792
+ CONNECTION_REFUSED. There is no way back. */
793
+ static VALUE quic_endpoint_stop_accepting(VALUE self) {
794
+ quic_endpoint_get(self)->accepting = false;
795
+ return Qnil;
796
+ }
797
+
798
+ /* #tls_context=: the context new connections are made with. Connections
799
+ already made keep theirs. */
800
+ static VALUE quic_endpoint_set_tls_context(VALUE self, VALUE tls_context) {
801
+ quic_endpoint_t *ep = quic_endpoint_get(self);
802
+ quic_tls_context_get(tls_context);
803
+ ep->tls_context = tls_context;
804
+ return tls_context;
805
+ }
806
+
807
+ void Init_quic_endpoint(VALUE rb_mQUICArg) {
808
+ if (RAND_bytes((uint8_t *)&quic_cid_hash_seed, sizeof(quic_cid_hash_seed)) !=
809
+ 1) {
810
+ rb_raise(rb_eRuntimeError, "RAND_bytes(CID hash seed) failed");
811
+ }
812
+
813
+ rb_cQUICEndpoint = rb_define_class_under(rb_mQUICArg, "Endpoint", rb_cObject);
814
+ rb_undef_alloc_func(rb_cQUICEndpoint);
815
+ rb_define_singleton_method(rb_cQUICEndpoint, "_new", quic_endpoint_s_new, -1);
816
+ rb_define_method(rb_cQUICEndpoint, "read_pkt", quic_endpoint_read_pkt, -1);
817
+ rb_define_method(rb_cQUICEndpoint, "write_pkt", quic_endpoint_write_pkt, 0);
818
+ rb_define_method(rb_cQUICEndpoint, "expiry", quic_endpoint_expiry, 0);
819
+ rb_define_method(rb_cQUICEndpoint, "handle_expiry",
820
+ quic_endpoint_handle_expiry, 0);
821
+ rb_define_method(rb_cQUICEndpoint, "take_closed", quic_endpoint_take_closed,
822
+ 0);
823
+ rb_define_method(rb_cQUICEndpoint, "connections", quic_endpoint_connections,
824
+ 0);
825
+ rb_define_method(rb_cQUICEndpoint, "stop_accepting",
826
+ quic_endpoint_stop_accepting, 0);
827
+ rb_define_method(rb_cQUICEndpoint,
828
+ "tls_context=", quic_endpoint_set_tls_context, 1);
829
+ }