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.
- checksums.yaml +4 -4
- data/.clang-format +1 -0
- data/.git-blame-ignore-revs +2 -0
- data/CHANGELOG.md +33 -0
- data/LICENSE-DEPENDENCIES.txt +2 -19
- data/README.md +101 -2
- data/Rakefile +7 -0
- data/examples/demo_certificates.rb +67 -0
- data/examples/echo_demo.rb +5 -6
- data/examples/echo_endpoint_demo.rb +170 -0
- data/examples/echo_server_demo.rb +71 -0
- data/ext/quic/connection_client.c +95 -934
- data/ext/quic/connection_common.c +1255 -0
- data/ext/quic/connection_common.h +226 -0
- data/ext/quic/connection_server.c +205 -0
- data/ext/quic/endpoint.c +829 -0
- data/ext/quic/endpoint.h +31 -0
- data/ext/quic/ext_api.c +106 -0
- data/ext/quic/extconf.rb +12 -5
- data/ext/quic/include/quic/ext.h +91 -0
- data/ext/quic/quic.c +95 -62
- data/ext/quic/quic.h +12 -10
- data/ext/quic/stream.c +124 -82
- data/ext/quic/stream.h +18 -12
- data/ext/quic/tls_context.c +269 -0
- data/ext/quic/tls_context.h +47 -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 +17 -1
data/ext/quic/endpoint.c
ADDED
|
@@ -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(¶ms, 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
|
+
}
|