quic 0.0.2 → 0.0.3

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