quic 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,226 @@
1
+ #ifndef QUIC_CONNECTION_COMMON_H
2
+ #define QUIC_CONNECTION_COMMON_H 1
3
+
4
+ #include <stdio.h>
5
+
6
+ #include "quic.h"
7
+ #include "quic/ext.h"
8
+ #include "tls_context.h"
9
+
10
+ /* Length of the source connection ID this endpoint picks for itself. */
11
+ #define QUIC_SCID_LEN 8
12
+
13
+ /* Buffer size for #write_pkt. NGTCP2_MAX_UDP_PAYLOAD_SIZE (1200) is the
14
+ minimum destlen ngtcp2 accepts. Revisit when PMTUD is enabled (Phase 4+). */
15
+ #define QUIC_WRITE_PKT_BUFLEN NGTCP2_MAX_UDP_PAYLOAD_SIZE
16
+
17
+ /* A QUIC::Endpoint (endpoint.c). */
18
+ typedef struct quic_endpoint quic_endpoint_t;
19
+
20
+ /* The C side of a QUIC::Connection::Client or QUIC::Connection::Server. Both
21
+ classes wrap it with quic_conn_data_type, so stream.c can reach the
22
+ ngtcp2_conn of either through quic_conn_ptr. Only the constructors
23
+ (_open / _accept) are class specific. */
24
+ typedef struct {
25
+ ngtcp2_conn *conn;
26
+ /* Client only. Referenced by cptls.ptls, which only keeps the pointer, so
27
+ this has to outlive the ptls_t. */
28
+ ptls_context_t tls_ctx;
29
+ /* Server only. The shared context cptls.ptls was made from, holding the
30
+ certificate chain, the signer and the ALPN list. This connection holds
31
+ a reference, released in quic_conn_free after the ptls_t. */
32
+ quic_tls_ctx_t *tls;
33
+ /* Client only. Referenced by tls_ctx.verify_certificate when verify_mode
34
+ is :peer. Holds a reference to an X509_STORE, released in
35
+ quic_conn_free. */
36
+ ptls_openssl_verify_certificate_t verify_cert;
37
+ bool verify_cert_initialized;
38
+ /* Filled by quic_override_verify_cb during the handshake so that a failed
39
+ read_pkt can raise CertificateVerifyFailed with the X509 error. */
40
+ bool verify_failed;
41
+ int verify_result; /* X509_V_ERR_*, 0 when the server sent no certificate */
42
+ /* The TLS native handle handed to ngtcp2 (&cptls, not cptls.ptls). */
43
+ ngtcp2_crypto_picotls_ctx cptls;
44
+ /* [0]: QUIC transport params, filled in by ngtcp2; [1]: terminator. */
45
+ ptls_raw_extension_t tls_exts[2];
46
+ /* Client only. ALPN list handed to picotls, which keeps the pointers for
47
+ the whole handshake (unlike SSL_set_alpn_protos, which copies). The
48
+ entries point into alpn_buf, and both are owned by this struct. */
49
+ ptls_iovec_t *alpn;
50
+ size_t alpn_count;
51
+ uint8_t *alpn_buf;
52
+ ngtcp2_cid scid;
53
+ ngtcp2_cid dcid;
54
+ /* ngtcp2 1.x requires applications to associate an ngtcp2_conn with the
55
+ TLS object so that crypto callbacks (e.g. add_handshake_data) can recover
56
+ the conn. picotls carries it in the ptls_t's data pointer, set via
57
+ ptls_get_data_ptr. The ref must outlive the ptls_t. */
58
+ ngtcp2_crypto_conn_ref conn_ref;
59
+ /* Back-reference to the Client or Server Ruby object that owns this
60
+ struct. ngtcp2 stream callbacks receive a void* user_data equal to this
61
+ struct, and they look up @streams via owner. We are stored INSIDE owner
62
+ via TypedData_Wrap_Struct, so owner is guaranteed alive while we exist
63
+ (no dmark needed). GC.compact may relocate owner, so quic_conn_compact
64
+ updates this field via rb_gc_location. */
65
+ VALUE owner;
66
+ /* An exception raised by Ruby code inside an ngtcp2 callback, caught with
67
+ rb_protect so that it does not unwind through ngtcp2's frames. #read_pkt,
68
+ #write_pkt and #handle_expiry re-raise it once ngtcp2 has returned.
69
+ Qnil when there is none. Marked by quic_conn_data_type's dmark. */
70
+ VALUE pending_exc;
71
+ /* The C stream handler registered through the quic_ext_api table
72
+ (set_stream_handler), or NULL. With a handler the connection makes no
73
+ QUIC::Stream objects: every stream callback goes to the handler, and
74
+ #write_pkt pulls stream data from it. handler_owner is marked while the
75
+ connection lives. */
76
+ const quic_ext_stream_handler *handler;
77
+ void *handler_ud;
78
+ VALUE handler_owner;
79
+ /* The time #read_pkt, #write_pkt or #handle_expiry read on entry. Handed
80
+ to the handler as `ts`, since ngtcp2 callbacks receive no time. */
81
+ ngtcp2_tstamp now;
82
+ /* The application error the handler left with set_application_error, to
83
+ close the connection with after it reports a failure. */
84
+ ngtcp2_ccerr close_error;
85
+ bool close_error_set;
86
+ /* Set by the constructor. Decides which peer-initiated streams
87
+ recv_stream_data queues for #accept_stream. */
88
+ bool is_server;
89
+ /* <qlog_dir>/<SCID>.sqlog while ngtcp2 writes qlog to it, else NULL.
90
+ quic_conn_close_qlog closes it as soon as the connection ends;
91
+ NGTCP2_QLOG_WRITE_FLAG_FIN, sent by ngtcp2_conn_del, closes one still
92
+ open when the connection is freed. */
93
+ FILE *qlog;
94
+ /* The rest is for a connection a QUIC::Endpoint made, and stays zero (nil)
95
+ otherwise. endpoint is the Endpoint whose table holds this connection,
96
+ NULL once it has left the table; endpoint_owner is that Endpoint's
97
+ object, marked so that it outlives the connections it holds. */
98
+ bool from_endpoint;
99
+ quic_endpoint_t *endpoint;
100
+ VALUE endpoint_owner;
101
+ /* Why the Endpoint closed the connection (Connection#error): nil for a
102
+ close by the peer, by idle timeout or by #close. */
103
+ VALUE error;
104
+ /* When the closing or draining period ends; 0 while the connection is
105
+ open. */
106
+ ngtcp2_tstamp close_deadline;
107
+ /* The CONNECTION_CLOSE packet built on entering the closing period, sent
108
+ again for packets the peer keeps sending, at most three times what they
109
+ add up to and with exponential backoff (examples/server.cc). */
110
+ VALUE closebuf;
111
+ uint64_t close_bytes_recv;
112
+ uint64_t close_bytes_sent;
113
+ uint64_t close_pkts_recv;
114
+ uint64_t close_next_pkts_recv;
115
+ } quic_conn_t;
116
+
117
+ extern const rb_data_type_t quic_conn_data_type;
118
+
119
+ extern ptls_key_exchange_algorithm_t *quic_key_exchanges[];
120
+ extern ptls_cipher_suite_t *quic_cipher_suites[];
121
+
122
+ /* Allocator for both connection classes. The struct starts zeroed, except
123
+ its VALUE fields, which start as Qnil. */
124
+ VALUE quic_conn_alloc(VALUE klass);
125
+
126
+ /* CLOCK_MONOTONIC in ngtcp2's units (nanoseconds). */
127
+ ngtcp2_tstamp quic_now(void);
128
+
129
+ /* Build the next packet of the connection self (wrapping c) into dest of
130
+ QUIC_WRITE_PKT_BUFLEN bytes, as #write_pkt does, and store its path in
131
+ *path. Returns the packet's length, 0 when there is nothing to send, or a
132
+ negative ngtcp2 error. Never raises for the connection's own failures: an
133
+ exception from a Ruby callback is left in c->pending_exc. */
134
+ ngtcp2_ssize quic_conn_write_pkt_raw(VALUE self, quic_conn_t *c,
135
+ ngtcp2_path *path, uint8_t *dest);
136
+
137
+ /* Feed one datagram to the connection, as #read_pkt does, and return what
138
+ ngtcp2_conn_read_pkt returned. Like quic_conn_write_pkt_raw, it leaves an
139
+ exception from a Ruby callback in c->pending_exc. */
140
+ int quic_conn_read_pkt_raw(quic_conn_t *c, const ngtcp2_path *path,
141
+ const uint8_t *data, size_t datalen);
142
+
143
+ /* The exception #read_pkt raises for rv, an error ngtcp2_conn_read_pkt
144
+ returned: a CryptoError (or CertificateVerifyFailed) with the TLS alert
145
+ for NGTCP2_ERR_CRYPTO, quic_ngtcp2_error_new(rv) otherwise. */
146
+ VALUE quic_conn_error_new(quic_conn_t *c, int rv);
147
+
148
+ /* Build a QUIC::Connection::Server of class klass for the client whose
149
+ first Initial is hd, received on path, with the TLS context tls_context
150
+ (a QUIC::TLSContext). ep is the Endpoint that will hold it (its object
151
+ ep_owner), or NULL for Server.accept. Raises if ngtcp2 or picotls cannot
152
+ set the connection up. */
153
+ VALUE quic_server_conn_new(VALUE klass, const ngtcp2_pkt_hd *hd,
154
+ const ngtcp2_path *path, VALUE tls_context,
155
+ VALUE transport_params, VALUE settings_v,
156
+ quic_endpoint_t *ep, VALUE ep_owner);
157
+
158
+ quic_conn_t *quic_conn_get(VALUE self);
159
+
160
+ /* The raw ngtcp2_conn of a Client or Server, for stream.c to issue ngtcp2
161
+ calls (e.g. ngtcp2_conn_shutdown_stream_read) on streams whose @client is
162
+ that connection. The caller must keep the connection object alive for the
163
+ duration of the call. */
164
+ ngtcp2_conn *quic_conn_ptr(VALUE owner);
165
+
166
+ /* ngtcp2_crypto_conn_ref.get_conn for quic_conn_t. */
167
+ ngtcp2_conn *quic_conn_get_conn(ngtcp2_crypto_conn_ref *conn_ref);
168
+
169
+ void quic_require_binary(VALUE str, const char *name);
170
+
171
+ /* str must be a binary String holding an AF_INET or AF_INET6 sockaddr of
172
+ exactly that family's size, as Addrinfo#to_sockaddr makes. ngtcp2 copies
173
+ an address by its length into storage only as large as sockaddr_in6, so
174
+ anything longer would overrun it. Raises ArgumentError otherwise. */
175
+ void quic_require_sockaddr(VALUE str, const char *name);
176
+
177
+ /* Validate a path argument and return it as a String, or Qnil for nil.
178
+ Accepts a String or anything responding to #to_path, and raises
179
+ ArgumentError for any other type or a path containing NUL. */
180
+ VALUE quic_path_value(VALUE value, const char *name);
181
+
182
+ /* Copy alpn_ary, an Array of 1-255 byte Strings, into a list and a buffer
183
+ the list's entries point into. Both are stored in *list and *buf as soon
184
+ as they are allocated, for the caller to release with xfree even when
185
+ this raises. An empty Array allocates nothing and sets *count to 0. */
186
+ void quic_copy_alpn(VALUE alpn_ary, ptls_iovec_t **list, size_t *count,
187
+ uint8_t **buf);
188
+
189
+ /* Copy settings.alpn into memory owned by c (c->alpn / c->alpn_buf). An
190
+ empty list leaves c->alpn_count at 0. */
191
+ void quic_conn_copy_alpn(quic_conn_t *c, VALUE settings_v);
192
+
193
+ /* Give the peer back the stream stream_id used up, if it is a
194
+ bidirectional stream the peer opened, as ngtcp2's examples/server.cc
195
+ does when such a stream closes. Without this the peer can open only
196
+ initial_max_streams_bidi streams over the whole connection. */
197
+ void quic_conn_give_back_peer_stream(ngtcp2_conn *conn, int64_t stream_id);
198
+
199
+ void quic_fill_transport_params(ngtcp2_transport_params *params, VALUE tp);
200
+ void quic_fill_settings(ngtcp2_settings *settings, VALUE st);
201
+
202
+ /* When settings_v.qlog_dir is set, open <qlog_dir>/<c->scid in hex>.sqlog
203
+ (as ngtcp2's examples name it) and have ngtcp2 write the connection's
204
+ qlog there. c->scid must be set. A file that cannot be opened only goes
205
+ without qlog: QUIC::Settings#check_qlog_dir already caught a wrong
206
+ directory when the connection or Endpoint was set up. */
207
+ void quic_conn_open_qlog(quic_conn_t *c, VALUE settings_v,
208
+ ngtcp2_settings *settings);
209
+
210
+ /* Close c's qlog file, if open. Called wherever the connection ends, so
211
+ that the file does not wait for GC; what ngtcp2 logs afterwards goes
212
+ nowhere. */
213
+ void quic_conn_close_qlog(quic_conn_t *c);
214
+
215
+ /* Zero *callbacks and fill in everything both endpoints share. The caller
216
+ adds the endpoint specific ones (client_initial / recv_retry, or
217
+ recv_client_initial). */
218
+ void quic_conn_fill_common_callbacks(ngtcp2_callbacks *callbacks);
219
+
220
+ /* Define the packet I/O, timer, state and stream methods on klass. */
221
+ void quic_conn_define_shared_methods(VALUE klass);
222
+
223
+ /* The quic_ext_api table (ext_api.c) that quic_ext_get_api hands out. */
224
+ extern const quic_ext_api quic_ext_api_table;
225
+
226
+ #endif /* QUIC_CONNECTION_COMMON_H */
@@ -0,0 +1,205 @@
1
+ #include "connection_common.h"
2
+ #include "endpoint.h"
3
+
4
+ #include <openssl/rand.h>
5
+
6
+ NORETURN(static void quic_server_raise_unacceptable(void));
7
+
8
+ /* The first datagram cannot start a connection. code stays nil: this is
9
+ _accept's own verdict, not an ngtcp2 error passed through. */
10
+ static void quic_server_raise_unacceptable(void) {
11
+ rb_raise(rb_eQUICErrorProto, "not an acceptable Initial packet");
12
+ }
13
+
14
+ VALUE
15
+ quic_server_conn_new(VALUE klass, const ngtcp2_pkt_hd *hd,
16
+ const ngtcp2_path *path, VALUE tls_context,
17
+ VALUE transport_params, VALUE settings_v,
18
+ quic_endpoint_t *ep, VALUE ep_owner) {
19
+ VALUE self = quic_conn_alloc(klass);
20
+ quic_conn_t *c = quic_conn_get(self);
21
+ c->owner = self;
22
+ c->is_server = true;
23
+ /* Set before ngtcp2_conn_server_new, whose callbacks may already look for
24
+ the Endpoint. */
25
+ if (ep != NULL) {
26
+ c->from_endpoint = true;
27
+ c->endpoint = ep;
28
+ c->endpoint_owner = ep_owner;
29
+ }
30
+
31
+ /* The reference taken here keeps the context alive until quic_conn_free,
32
+ whatever happens to the QUIC::TLSContext object. */
33
+ c->tls = quic_tls_context_get(tls_context);
34
+ quic_tls_ctx_ref(c->tls);
35
+
36
+ c->dcid = hd->scid;
37
+ c->scid.datalen = QUIC_SCID_LEN;
38
+ if (RAND_bytes(c->scid.data, QUIC_SCID_LEN) != 1) {
39
+ rb_raise(rb_eRuntimeError, "RAND_bytes(scid) failed");
40
+ }
41
+
42
+ ngtcp2_transport_params params;
43
+ quic_fill_transport_params(&params, transport_params);
44
+ /* Without Retry, the original DCID is the one in this first packet. The
45
+ handshake fails if it is missing. */
46
+ params.original_dcid = hd->dcid;
47
+ params.original_dcid_present = 1;
48
+ /* Only an Endpoint can answer with a Stateless Reset, for a CID it no
49
+ longer knows, so only its connections announce a token. */
50
+ if (ep != NULL) {
51
+ if (quic_endpoint_generate_reset_token(ep, params.stateless_reset_token,
52
+ &c->scid) != 0) {
53
+ rb_raise(rb_eRuntimeError,
54
+ "ngtcp2_crypto_generate_stateless_reset_token failed");
55
+ }
56
+ params.stateless_reset_token_present = 1;
57
+ }
58
+
59
+ /* quic_conn_alloc already zeroed the struct, but calling ctx_init keeps
60
+ the ngtcp2-side initialization explicit, as in _open. */
61
+ ngtcp2_crypto_picotls_ctx_init(&c->cptls);
62
+
63
+ ngtcp2_callbacks callbacks;
64
+ quic_conn_fill_common_callbacks(&callbacks);
65
+ callbacks.recv_client_initial = ngtcp2_crypto_recv_client_initial_cb;
66
+
67
+ ngtcp2_settings settings;
68
+ quic_fill_settings(&settings, settings_v);
69
+ /* Every raise below closes it first. */
70
+ quic_conn_open_qlog(c, settings_v, &settings);
71
+
72
+ c->conn_ref.get_conn = quic_conn_get_conn;
73
+ c->conn_ref.user_data = c;
74
+
75
+ int rv =
76
+ ngtcp2_conn_server_new(&c->conn, &c->dcid, &c->scid, path, hd->version,
77
+ &callbacks, &settings, &params, NULL, c);
78
+ if (rv != 0) {
79
+ quic_conn_close_qlog(c);
80
+ quic_raise_ngtcp2_error(rv);
81
+ }
82
+
83
+ c->cptls.ptls = ptls_server_new(&c->tls->ctx);
84
+ if (c->cptls.ptls == NULL) {
85
+ quic_conn_close_qlog(c);
86
+ rb_raise(rb_eRuntimeError, "ptls_server_new failed");
87
+ }
88
+ /* Let ngtcp2's crypto callbacks resolve the ngtcp2_conn from the ptls_t. */
89
+ *ptls_get_data_ptr(c->cptls.ptls) = &c->conn_ref;
90
+ /* [0] carries the QUIC transport parameters, filled in by ngtcp2; [1]
91
+ terminates the list. */
92
+ c->tls_exts[0].type = UINT16_MAX;
93
+ c->tls_exts[1].type = UINT16_MAX;
94
+ c->cptls.handshake_properties.additional_extensions = c->tls_exts;
95
+ if (ngtcp2_crypto_picotls_configure_server_session(&c->cptls) != 0) {
96
+ quic_conn_close_qlog(c);
97
+ rb_raise(rb_eRuntimeError,
98
+ "ngtcp2_crypto_picotls_configure_server_session failed");
99
+ }
100
+
101
+ ngtcp2_conn_set_tls_native_handle(c->conn, &c->cptls);
102
+
103
+ /* Stream registry and accept queue, as in _open. The accept queue holds
104
+ client-initiated streams here. */
105
+ rb_ivar_set(self, rb_intern("@streams"), rb_hash_new());
106
+ rb_ivar_set(self, rb_intern("@accept_queue"), rb_ary_new());
107
+
108
+ return self;
109
+ }
110
+
111
+ /* Build a server connection from the first datagram a client sent. Only the
112
+ ngtcp2_conn and the TLS session are set up here; QUIC::Connection::Server
113
+ .accept feeds that datagram to #read_pkt afterwards. */
114
+ static VALUE quic_server_accept(int argc, VALUE *argv, VALUE klass) {
115
+ VALUE opts = Qnil;
116
+ rb_scan_args(argc, argv, "0:", &opts);
117
+ if (NIL_P(opts)) {
118
+ rb_raise(
119
+ rb_eArgError,
120
+ "missing keywords: initial_packet, local_sockaddr, remote_sockaddr, "
121
+ "certificate_path, private_key_path, transport_params, settings");
122
+ }
123
+
124
+ VALUE initial_packet =
125
+ rb_hash_aref(opts, ID2SYM(rb_intern("initial_packet")));
126
+ VALUE local_sockaddr =
127
+ rb_hash_aref(opts, ID2SYM(rb_intern("local_sockaddr")));
128
+ VALUE remote_sockaddr =
129
+ rb_hash_aref(opts, ID2SYM(rb_intern("remote_sockaddr")));
130
+ VALUE certificate_path =
131
+ rb_hash_aref(opts, ID2SYM(rb_intern("certificate_path")));
132
+ VALUE private_key_path =
133
+ rb_hash_aref(opts, ID2SYM(rb_intern("private_key_path")));
134
+ VALUE transport_params =
135
+ rb_hash_aref(opts, ID2SYM(rb_intern("transport_params")));
136
+ VALUE settings_v = rb_hash_aref(opts, ID2SYM(rb_intern("settings")));
137
+
138
+ if (NIL_P(initial_packet) || NIL_P(local_sockaddr) ||
139
+ NIL_P(remote_sockaddr) || NIL_P(certificate_path) ||
140
+ NIL_P(private_key_path) || NIL_P(transport_params) || NIL_P(settings_v)) {
141
+ rb_raise(rb_eArgError,
142
+ "all keywords required: initial_packet, local_sockaddr, "
143
+ "remote_sockaddr, "
144
+ "certificate_path, private_key_path, transport_params, settings");
145
+ }
146
+
147
+ quic_require_binary(initial_packet, "initial_packet");
148
+ quic_require_sockaddr(local_sockaddr, "local_sockaddr");
149
+ quic_require_sockaddr(remote_sockaddr, "remote_sockaddr");
150
+ certificate_path = quic_path_value(certificate_path, "certificate_path");
151
+ private_key_path = quic_path_value(private_key_path, "private_key_path");
152
+
153
+ const uint8_t *data = (const uint8_t *)RSTRING_PTR(initial_packet);
154
+ size_t datalen = (size_t)RSTRING_LEN(initial_packet);
155
+
156
+ /* ngtcp2_pkt_decode_version_cid asserts datalen > 0, and ngtcp2 is built
157
+ with assertions enabled, so an empty datagram would abort the process. */
158
+ if (datalen == 0)
159
+ quic_server_raise_unacceptable();
160
+
161
+ ngtcp2_version_cid vc;
162
+ int rv = ngtcp2_pkt_decode_version_cid(&vc, data, datalen, QUIC_SCID_LEN);
163
+ if (rv == NGTCP2_ERR_VERSION_NEGOTIATION) {
164
+ rb_raise(rb_eQUICErrorProto,
165
+ "unsupported QUIC version 0x%08x (Version Negotiation is not "
166
+ "implemented)",
167
+ (unsigned int)vc.version);
168
+ }
169
+ if (rv != 0)
170
+ quic_server_raise_unacceptable();
171
+
172
+ /* Unlike vc, hd holds the CIDs by value, so it outlives the packet buffer.
173
+ A token (address validation) is accepted without checking it: no Retry
174
+ is ever sent, so a well-behaved client does not send one. */
175
+ ngtcp2_pkt_hd hd;
176
+ if (ngtcp2_accept(&hd, data, datalen) != 0)
177
+ quic_server_raise_unacceptable();
178
+
179
+ /* A context of this connection's own, as one made by
180
+ QUIC::TLSContext.server. verify_mode, ca_file and ca_path are not read,
181
+ since no client certificate is requested. */
182
+ VALUE tls_context =
183
+ quic_tls_context_server_new(certificate_path, private_key_path,
184
+ rb_funcall(settings_v, rb_intern("alpn"), 0));
185
+
186
+ ngtcp2_path path = {
187
+ {(struct sockaddr *)RSTRING_PTR(local_sockaddr),
188
+ (ngtcp2_socklen)RSTRING_LEN(local_sockaddr)},
189
+ {(struct sockaddr *)RSTRING_PTR(remote_sockaddr),
190
+ (ngtcp2_socklen)RSTRING_LEN(remote_sockaddr)},
191
+ NULL,
192
+ };
193
+
194
+ return quic_server_conn_new(klass, &hd, &path, tls_context, transport_params,
195
+ settings_v, NULL, Qnil);
196
+ }
197
+
198
+ void Init_quic_connection_server(VALUE rb_mQUICConnectionArg) {
199
+ rb_cQUICConnectionServer =
200
+ rb_define_class_under(rb_mQUICConnectionArg, "Server", rb_cObject);
201
+ rb_define_alloc_func(rb_cQUICConnectionServer, quic_conn_alloc);
202
+ rb_define_singleton_method(rb_cQUICConnectionServer, "_accept",
203
+ quic_server_accept, -1);
204
+ quic_conn_define_shared_methods(rb_cQUICConnectionServer);
205
+ }