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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -0
- data/LICENSE-DEPENDENCIES.txt +2 -19
- data/README.md +27 -2
- data/Rakefile +7 -0
- data/examples/echo_demo.rb +5 -6
- data/examples/echo_server_demo.rb +127 -0
- data/ext/quic/connection_client.c +23 -880
- data/ext/quic/connection_common.c +1262 -0
- data/ext/quic/connection_common.h +217 -0
- data/ext/quic/connection_server.c +186 -0
- data/ext/quic/endpoint.c +823 -0
- data/ext/quic/endpoint.h +28 -0
- data/ext/quic/ext_api.c +112 -0
- data/ext/quic/extconf.rb +12 -5
- data/ext/quic/include/quic/ext.h +84 -0
- data/ext/quic/quic.c +29 -4
- data/ext/quic/quic.h +8 -8
- data/ext/quic/stream.c +81 -20
- data/ext/quic/stream.h +13 -7
- data/ext/quic/tls_context.c +260 -0
- data/ext/quic/tls_context.h +46 -0
- data/lib/quic/connection.rb +142 -57
- data/lib/quic/endpoint.rb +57 -0
- data/lib/quic/ext_include.rb +12 -0
- data/lib/quic/settings.rb +26 -3
- data/lib/quic/stream.rb +4 -3
- data/lib/quic/version.rb +1 -1
- data/lib/quic.rb +2 -0
- data/sig/quic.rbs +203 -22
- metadata +13 -1
data/ext/quic/endpoint.c
ADDED
|
@@ -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(¶ms, 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
|
+
}
|