quic 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,260 @@
1
+ #include "tls_context.h"
2
+ #include "connection_common.h"
3
+
4
+ #include <stddef.h>
5
+ #include <string.h>
6
+
7
+ #include <openssl/bio.h>
8
+ #include <openssl/err.h>
9
+ #include <openssl/pem.h>
10
+ #include <openssl/x509.h>
11
+
12
+ VALUE rb_cQUICTLSContext;
13
+
14
+ void
15
+ quic_tls_ctx_ref(quic_tls_ctx_t *t)
16
+ {
17
+ t->refcnt++;
18
+ }
19
+
20
+ void
21
+ quic_tls_ctx_unref(quic_tls_ctx_t *t)
22
+ {
23
+ if (--t->refcnt > 0) return;
24
+ if (t->sign_cert_initialized) ptls_openssl_dispose_sign_certificate(&t->sign_cert);
25
+ /* picotls malloc()ed the list and each entry, so free(), not xfree(). */
26
+ if (t->ctx.certificates.list != NULL) {
27
+ for (size_t i = 0; i < t->ctx.certificates.count; i++) free(t->ctx.certificates.list[i].base);
28
+ free(t->ctx.certificates.list);
29
+ }
30
+ xfree(t->alpn);
31
+ xfree(t->alpn_buf);
32
+ xfree(t);
33
+ }
34
+
35
+ /* The QUIC::TLSContext object holds one reference. A context it never
36
+ finished building (an exception mid-way) is released the same way. */
37
+ static void
38
+ quic_tls_context_free(void *ptr)
39
+ {
40
+ if (ptr != NULL) quic_tls_ctx_unref((quic_tls_ctx_t *)ptr);
41
+ }
42
+
43
+ static size_t
44
+ quic_tls_context_size(const void *ptr)
45
+ {
46
+ (void)ptr;
47
+ return sizeof(quic_tls_ctx_t);
48
+ }
49
+
50
+ static const rb_data_type_t quic_tls_context_type = {
51
+ "QUIC::TLSContext",
52
+ {NULL, quic_tls_context_free, quic_tls_context_size,},
53
+ NULL, NULL,
54
+ RUBY_TYPED_FREE_IMMEDIATELY,
55
+ };
56
+
57
+ quic_tls_ctx_t *
58
+ quic_tls_context_get(VALUE tls_context)
59
+ {
60
+ return (quic_tls_ctx_t *)rb_check_typeddata(tls_context, &quic_tls_context_type);
61
+ }
62
+
63
+ /* Pick the first of the server's ALPN protocols, in the server's order of
64
+ preference, that the client offered. Runs inside read_pkt with the GVL
65
+ held and never touches Ruby objects. */
66
+ static int
67
+ quic_tls_on_client_hello_cb(ptls_on_client_hello_t *self, ptls_t *tls,
68
+ ptls_on_client_hello_parameters_t *params)
69
+ {
70
+ quic_tls_ctx_t *t = (quic_tls_ctx_t *)((char *)self - offsetof(quic_tls_ctx_t, on_client_hello));
71
+ for (size_t i = 0; i < t->alpn_count; i++) {
72
+ for (size_t j = 0; j < params->negotiated_protocols.count; j++) {
73
+ ptls_iovec_t offered = params->negotiated_protocols.list[j];
74
+ if (offered.len == t->alpn[i].len &&
75
+ memcmp(offered.base, t->alpn[i].base, offered.len) == 0) {
76
+ return ptls_set_negotiated_protocol(tls, (const char *)offered.base, offered.len) == 0
77
+ ? 0 : PTLS_ERROR_NO_MEMORY;
78
+ }
79
+ }
80
+ }
81
+ return PTLS_ALERT_NO_APPLICATION_PROTOCOL;
82
+ }
83
+
84
+ /* Never supply a passphrase. Without a callback, PEM_read_bio_PrivateKey
85
+ falls back to PEM_def_callback, which prompts on the controlling terminal;
86
+ this way an encrypted key fails to load like any other unreadable one. */
87
+ static int
88
+ quic_tls_no_passphrase_cb(char *buf, int size, int rwflag, void *userdata)
89
+ {
90
+ (void)buf;
91
+ (void)size;
92
+ (void)rwflag;
93
+ (void)userdata;
94
+ return -1;
95
+ }
96
+
97
+ /* Load the certificate chain and private key into t->ctx and install the
98
+ signer. The certificate file is PEM, the leaf first and then any
99
+ intermediates; there is no limit on their number. A misconfiguration
100
+ raises ArgumentError naming the step that failed. Everything allocated
101
+ here is released before raising, and libcrypto's error queue, which is
102
+ shared with Ruby's openssl extension, is left empty on every return. */
103
+ static void
104
+ quic_tls_ctx_load_certificate_and_key(quic_tls_ctx_t *t, VALUE certificate_path,
105
+ VALUE private_key_path)
106
+ {
107
+ VALUE failure_class = rb_eArgError;
108
+ const char *failure = NULL;
109
+ VALUE failed_path = Qnil;
110
+ BIO *cert_bio = NULL;
111
+ BIO *key_bio = NULL;
112
+ X509 *leaf = NULL;
113
+ X509 *x;
114
+ STACK_OF(X509) *chain = NULL;
115
+ EVP_PKEY *pkey = NULL;
116
+ unsigned long err;
117
+
118
+ ERR_clear_error();
119
+
120
+ cert_bio = BIO_new_file(RSTRING_PTR(certificate_path), "r");
121
+ if (cert_bio == NULL || (leaf = PEM_read_bio_X509(cert_bio, NULL, NULL, NULL)) == NULL) {
122
+ failure = "failed to load certificate";
123
+ failed_path = certificate_path;
124
+ goto done;
125
+ }
126
+
127
+ if ((chain = sk_X509_new_null()) == NULL) {
128
+ failure_class = rb_eRuntimeError;
129
+ failure = "sk_X509_new_null failed";
130
+ goto done;
131
+ }
132
+ while ((x = PEM_read_bio_X509(cert_bio, NULL, NULL, NULL)) != NULL) {
133
+ if (!sk_X509_push(chain, x)) {
134
+ X509_free(x);
135
+ failure_class = rb_eRuntimeError;
136
+ failure = "sk_X509_push failed";
137
+ goto done;
138
+ }
139
+ }
140
+ /* The loop ends with PEM_R_NO_START_LINE once no PEM block is left. Any
141
+ other error means a broken block, as in SSL_CTX_use_certificate_chain_file. */
142
+ err = ERR_peek_last_error();
143
+ if (ERR_GET_LIB(err) != ERR_LIB_PEM || ERR_GET_REASON(err) != PEM_R_NO_START_LINE) {
144
+ failure = "failed to load certificate";
145
+ failed_path = certificate_path;
146
+ goto done;
147
+ }
148
+
149
+ key_bio = BIO_new_file(RSTRING_PTR(private_key_path), "r");
150
+ if (key_bio == NULL ||
151
+ (pkey = PEM_read_bio_PrivateKey(key_bio, NULL, quic_tls_no_passphrase_cb, NULL)) == NULL) {
152
+ failure = "failed to load private key";
153
+ failed_path = private_key_path;
154
+ goto done;
155
+ }
156
+
157
+ /* picotls has no counterpart of SSL_CTX_check_private_key. */
158
+ if (X509_check_private_key(leaf, pkey) != 1) {
159
+ failure = "private key does not match certificate";
160
+ failed_path = private_key_path;
161
+ goto done;
162
+ }
163
+
164
+ /* Takes its own reference on pkey. */
165
+ if (ptls_openssl_init_sign_certificate(&t->sign_cert, pkey) != 0) {
166
+ failure = "unsupported private key type";
167
+ failed_path = private_key_path;
168
+ goto done;
169
+ }
170
+ t->sign_cert_initialized = true;
171
+ t->ctx.sign_certificate = &t->sign_cert.super;
172
+
173
+ /* Copies the DER of each certificate into a list quic_tls_ctx_unref
174
+ releases. It fails only on allocation, and frees what it allocated then. */
175
+ if (ptls_openssl_load_certificates(&t->ctx, leaf, chain) != 0) {
176
+ failure_class = rb_eRuntimeError;
177
+ failure = "ptls_openssl_load_certificates failed";
178
+ goto done;
179
+ }
180
+
181
+ done:
182
+ sk_X509_pop_free(chain, X509_free);
183
+ X509_free(leaf);
184
+ EVP_PKEY_free(pkey);
185
+ BIO_free(cert_bio);
186
+ BIO_free(key_bio);
187
+ ERR_clear_error();
188
+
189
+ if (failure == NULL) return;
190
+ if (NIL_P(failed_path)) rb_raise(failure_class, "%s", failure);
191
+ rb_raise(failure_class, "%s: %"PRIsVALUE, failure, failed_path);
192
+ }
193
+
194
+ VALUE
195
+ quic_tls_context_server_new(VALUE certificate_path, VALUE private_key_path, VALUE alpn_ary)
196
+ {
197
+ certificate_path = quic_path_value(certificate_path, "certificate_path");
198
+ private_key_path = quic_path_value(private_key_path, "private_key_path");
199
+ if (NIL_P(certificate_path)) rb_raise(rb_eArgError, "certificate_path must not be nil");
200
+ if (NIL_P(private_key_path)) rb_raise(rb_eArgError, "private_key_path must not be nil");
201
+
202
+ /* The object owns the context from the start, so that an exception below
203
+ releases whatever was built so far. */
204
+ VALUE self = TypedData_Wrap_Struct(rb_cQUICTLSContext, &quic_tls_context_type, NULL);
205
+ quic_tls_ctx_t *t = ZALLOC(quic_tls_ctx_t);
206
+ t->refcnt = 1;
207
+ DATA_PTR(self) = t;
208
+
209
+ /* Without an ALPN to pick, every handshake would end in
210
+ no_application_protocol. */
211
+ quic_copy_alpn(alpn_ary, &t->alpn, &t->alpn_count, &t->alpn_buf);
212
+ if (t->alpn_count == 0) rb_raise(rb_eArgError, "alpn must not be empty for a server");
213
+
214
+ t->ctx = (ptls_context_t){
215
+ .random_bytes = ptls_openssl_random_bytes,
216
+ .get_time = &ptls_get_time,
217
+ .key_exchanges = quic_key_exchanges,
218
+ .cipher_suites = quic_cipher_suites,
219
+ /* No session resumption: encrypt_ticket stays NULL, so no ticket is
220
+ ever issued. Keep this so that PSK-only resumption is refused once
221
+ resumption is added. */
222
+ .require_dhe_on_psk = 1,
223
+ .server_cipher_preference = 1,
224
+ };
225
+ if (ngtcp2_crypto_picotls_configure_server_context(&t->ctx) != 0) {
226
+ rb_raise(rb_eRuntimeError, "ngtcp2_crypto_picotls_configure_server_context failed");
227
+ }
228
+ /* verify_certificate stays NULL: no client certificate is requested. */
229
+ quic_tls_ctx_load_certificate_and_key(t, certificate_path, private_key_path);
230
+ t->on_client_hello.cb = quic_tls_on_client_hello_cb;
231
+ t->ctx.on_client_hello = &t->on_client_hello;
232
+ return self;
233
+ }
234
+
235
+ /* QUIC::TLSContext.server(certificate_path:, private_key_path:, alpn:). A
236
+ missing or unknown keyword raises ArgumentError as a Ruby method would. */
237
+ static VALUE
238
+ quic_tls_context_s_server(int argc, VALUE *argv, VALUE klass)
239
+ {
240
+ (void)klass;
241
+ VALUE opts = Qnil;
242
+ rb_scan_args(argc, argv, "0:", &opts);
243
+ if (NIL_P(opts)) opts = rb_hash_new();
244
+
245
+ ID keywords[3];
246
+ VALUE values[3];
247
+ keywords[0] = rb_intern("certificate_path");
248
+ keywords[1] = rb_intern("private_key_path");
249
+ keywords[2] = rb_intern("alpn");
250
+ rb_get_kwargs(opts, keywords, 3, 0, values);
251
+ return quic_tls_context_server_new(values[0], values[1], values[2]);
252
+ }
253
+
254
+ void
255
+ Init_quic_tls_context(VALUE rb_mQUICArg)
256
+ {
257
+ rb_cQUICTLSContext = rb_define_class_under(rb_mQUICArg, "TLSContext", rb_cObject);
258
+ rb_undef_alloc_func(rb_cQUICTLSContext);
259
+ rb_define_singleton_method(rb_cQUICTLSContext, "server", quic_tls_context_s_server, -1);
260
+ }
@@ -0,0 +1,46 @@
1
+ #ifndef QUIC_TLS_CONTEXT_H
2
+ #define QUIC_TLS_CONTEXT_H 1
3
+
4
+ #include "quic.h"
5
+
6
+ /* A server's TLS context: the certificate chain, the signer and the ALPN
7
+ list, read once and shared by every connection made from it. Reference
8
+ counted: the QUIC::TLSContext object holds one reference and each server
9
+ connection another, so a context replaced on an Endpoint lives on until
10
+ the last connection that uses it is freed. */
11
+ typedef struct {
12
+ /* Handed to ptls_server_new. certificates.list is malloc()ed by picotls
13
+ and freed with the context. */
14
+ ptls_context_t ctx;
15
+ /* Referenced by ctx.sign_certificate; holds a reference to the key. */
16
+ ptls_openssl_sign_certificate_t sign_cert;
17
+ bool sign_cert_initialized;
18
+ /* ctx.on_client_hello points here; the callback recovers this struct
19
+ from its self argument with offsetof, so it needs no connection. */
20
+ ptls_on_client_hello_t on_client_hello;
21
+ /* The ALPN protocols the server accepts, in its order of preference. The
22
+ entries point into alpn_buf. */
23
+ ptls_iovec_t *alpn;
24
+ size_t alpn_count;
25
+ uint8_t *alpn_buf;
26
+ long refcnt;
27
+ } quic_tls_ctx_t;
28
+
29
+ extern VALUE rb_cQUICTLSContext;
30
+
31
+ void Init_quic_tls_context(VALUE rb_mQUICArg);
32
+
33
+ /* QUIC::TLSContext.server, callable from C: a new QUIC::TLSContext object
34
+ holding the one reference to a new context. alpn_ary must be a non-empty
35
+ Array of 1-255 byte Strings. Raises ArgumentError naming what could not
36
+ be loaded or used. */
37
+ VALUE quic_tls_context_server_new(VALUE certificate_path, VALUE private_key_path, VALUE alpn_ary);
38
+
39
+ /* The context behind a QUIC::TLSContext object. Raises TypeError for any
40
+ other object. */
41
+ quic_tls_ctx_t *quic_tls_context_get(VALUE tls_context);
42
+
43
+ void quic_tls_ctx_ref(quic_tls_ctx_t *t);
44
+ void quic_tls_ctx_unref(quic_tls_ctx_t *t);
45
+
46
+ #endif /* QUIC_TLS_CONTEXT_H */
@@ -4,46 +4,11 @@ require "socket"
4
4
 
5
5
  module QUIC
6
6
  module Connection
7
- class Client
8
- attr_reader :remote_address
9
-
10
- # Build a Client and pin a single peer address. address_family takes a
11
- # Symbol (:inet for IPv4, :inet6 for IPv6) or nil to defer family
12
- # selection to Addrinfo.udp's implicit resolution. Pin the resolved
13
- # Addrinfo into @remote_address so callers can match their UDPSocket's
14
- # connected sockaddr (otherwise DNS round-robin between Client.new and
15
- # sock.connect can produce a path that ngtcp2 silently drops).
16
- def self.new(host:, port:, address_family: nil, transport_params: nil, settings: nil)
17
- family = case address_family
18
- when nil then nil
19
- when :inet then Socket::AF_INET
20
- when :inet6 then Socket::AF_INET6
21
- else
22
- raise ArgumentError, "unknown address_family: #{address_family.inspect}"
23
- end
24
-
25
- remote_address = if family
26
- Addrinfo.getaddrinfo(host, port, family, :DGRAM, Socket::IPPROTO_UDP).first
27
- else
28
- Addrinfo.udp(host, port)
29
- end
30
-
31
- remote_sockaddr = remote_address.to_sockaddr
32
- local_sockaddr = Addrinfo.udp("0.0.0.0", 0).to_sockaddr
33
-
34
- client = _open(
35
- local_sockaddr: local_sockaddr,
36
- remote_sockaddr: remote_sockaddr,
37
- server_name: host,
38
- transport_params: transport_params || QUIC::TransportParams.default,
39
- settings: settings || QUIC::Settings.default
40
- )
41
- client.instance_variable_set(:@host, host)
42
- client.instance_variable_set(:@port, port)
43
- client.instance_variable_set(:@remote_address, remote_address)
44
- client
45
- end
46
-
7
+ # The Ruby side of the I/O loop, shared by Client and Server. It is an
8
+ # internal implementation detail: not meant to be included in other
9
+ # classes, and the ivars and methods it relies on are not a public
10
+ # contract.
11
+ module Pump
47
12
  # Attach an IO.select / #send / #recvfrom-compatible UDP socket so that
48
13
  # #run and Stream blocking I/O can drive the wire transparently. Caller
49
14
  # is responsible for socket lifetime; we do not close it. The socket
@@ -61,7 +26,7 @@ module QUIC
61
26
  # QUIC::Error::NotBound if #bind has not been called. Propagates any
62
27
  # QUIC::Error subclass raised by #read_pkt / #handle_expiry.
63
28
  def run
64
- raise QUIC::Error::NotBound, "QUIC::Connection::Client#bind(sock) has not been called" if @sock.nil?
29
+ raise QUIC::Error::NotBound, "#{self.class}#bind(sock) has not been called" if @sock.nil?
65
30
 
66
31
  pump_until { handshake_completed? }
67
32
  end
@@ -72,7 +37,7 @@ module QUIC
72
37
  # fire. Public-ish so Stream blocking ops can share it; documented as
73
38
  # "internal" for users.
74
39
  def pump_once(timeout: nil)
75
- raise QUIC::Error::NotBound, "QUIC::Connection::Client#bind(sock) has not been called" if @sock.nil?
40
+ raise QUIC::Error::NotBound, "#{self.class}#bind(sock) has not been called" if @sock.nil?
76
41
 
77
42
  while (pkt = write_pkt)
78
43
  @sock.send(pkt, 0)
@@ -82,10 +47,11 @@ module QUIC
82
47
  ready = IO.select([@sock], nil, nil, wait_for)
83
48
  if ready
84
49
  data, _addr = @sock.recvfrom(2048)
85
- # Reuse the same path tuple that Client.new / Client._open passed to
86
- # ngtcp2_conn_client_new so the read_pkt path matches the stored
87
- # connection path. local is 0.0.0.0:0 (Client.new convention) and
88
- # remote comes from the connected socket (assumes #connect was used).
50
+ # Reuse the same path tuple the connection was built with so the
51
+ # read_pkt path matches the stored connection path. A Client leaves
52
+ # @local_sockaddr unset, so local is 0.0.0.0:0 (Client.new
53
+ # convention); remote comes from the connected socket (assumes
54
+ # #connect was used).
89
55
  read_pkt(
90
56
  data,
91
57
  local_sockaddr: pump_local_sockaddr,
@@ -102,9 +68,9 @@ module QUIC
102
68
  pump_once until pred.call
103
69
  end
104
70
 
105
- # Pop the next peer-initiated (server) stream off the accept queue,
106
- # driving the I/O loop until one arrives. The queue is fed by the
107
- # recv_stream_data callback the first time a server stream carries data.
71
+ # Pop the next peer-initiated stream off the accept queue, driving the
72
+ # I/O loop until one arrives. The queue is fed by the recv_stream_data
73
+ # callback the first time a peer-initiated stream carries data.
108
74
  #
109
75
  # timeout: nil block until a stream is available (requires #bind)
110
76
  # timeout: 0 return immediately; nil if the queue is empty
@@ -112,7 +78,8 @@ module QUIC
112
78
  #
113
79
  # Returns a QUIC::Stream, or nil on timeout.
114
80
  def accept_stream(timeout: nil)
115
- stream = @accept_queue.shift
81
+ require_stream_mode
82
+ stream = take_accepted_stream
116
83
  return stream unless stream.nil?
117
84
  return nil if timeout == 0
118
85
 
@@ -125,22 +92,40 @@ module QUIC
125
92
  else
126
93
  pump_once
127
94
  end
128
- stream = @accept_queue.shift
95
+ stream = take_accepted_stream
129
96
  return stream unless stream.nil?
130
97
  end
131
98
  end
132
99
 
133
100
  # Non-blocking variant of #accept_stream. Returns the next queued
134
- # server stream, or raises QUIC::Error::WaitReadable (IO::WaitReadable
135
- # mixin) when the queue is empty. Does not require #bind.
101
+ # peer-initiated stream, or raises QUIC::Error::WaitReadable
102
+ # (IO::WaitReadable mixin) when the queue is empty. Does not require
103
+ # #bind.
136
104
  def accept_stream_nonblock
137
- stream = @accept_queue.shift
138
- raise QUIC::Error::WaitReadable, "no server-initiated stream available" if stream.nil?
105
+ require_stream_mode
106
+ stream = take_accepted_stream
107
+ raise QUIC::Error::WaitReadable, "no peer-initiated stream available" if stream.nil?
139
108
  stream
140
109
  end
141
110
 
142
111
  private
143
112
 
113
+ # A C stream handler (quic/ext.h) registered on the connection owns
114
+ # every stream, so no QUIC::Stream is ever queued for #accept_stream.
115
+ def require_stream_mode
116
+ return unless @stream_handler
117
+ raise QUIC::Error::InvalidState, "the connection's streams belong to a C stream handler"
118
+ end
119
+
120
+ # The next stream off the accept queue, or nil. Telling the stream it
121
+ # has been accepted lets the peer open another one in place of a
122
+ # stream that closed while it waited in the queue.
123
+ def take_accepted_stream
124
+ stream = @accept_queue.shift
125
+ stream&.__send__(:_accepted)
126
+ stream
127
+ end
128
+
144
129
  def pump_timeout(caller_timeout)
145
130
  ngtcp2_expiry_ns = expiry
146
131
  if ngtcp2_expiry_ns.nil?
@@ -153,11 +138,111 @@ module QUIC
153
138
  end
154
139
 
155
140
  def pump_local_sockaddr
156
- @pump_local_sockaddr ||= Addrinfo.udp("0.0.0.0", 0).to_sockaddr
141
+ @local_sockaddr ||= Addrinfo.udp("0.0.0.0", 0).to_sockaddr
157
142
  end
158
143
 
159
144
  def pump_remote_sockaddr
160
- @pump_remote_sockaddr ||= @sock.remote_address.to_sockaddr
145
+ @remote_sockaddr ||= @sock.remote_address.to_sockaddr
146
+ end
147
+ end
148
+
149
+ class Client
150
+ include Pump
151
+
152
+ attr_reader :remote_address
153
+
154
+ # Build a Client and pin a single peer address. address_family takes a
155
+ # Symbol (:inet for IPv4, :inet6 for IPv6) or nil to defer family
156
+ # selection to Addrinfo.udp's implicit resolution. Pin the resolved
157
+ # Addrinfo into @remote_address so callers can match their UDPSocket's
158
+ # connected sockaddr (otherwise DNS round-robin between Client.new and
159
+ # sock.connect can produce a path that ngtcp2 silently drops).
160
+ def self.new(host:, port:, address_family: nil, transport_params: nil, settings: nil)
161
+ family = case address_family
162
+ when nil then nil
163
+ when :inet then Socket::AF_INET
164
+ when :inet6 then Socket::AF_INET6
165
+ else
166
+ raise ArgumentError, "unknown address_family: #{address_family.inspect}"
167
+ end
168
+
169
+ remote_address = if family
170
+ Addrinfo.getaddrinfo(host, port, family, :DGRAM, Socket::IPPROTO_UDP).first
171
+ else
172
+ Addrinfo.udp(host, port)
173
+ end
174
+
175
+ remote_sockaddr = remote_address.to_sockaddr
176
+ local_sockaddr = Addrinfo.udp("0.0.0.0", 0).to_sockaddr
177
+ settings ||= QUIC::Settings.default
178
+ settings.check_qlog_dir
179
+
180
+ client = _open(
181
+ local_sockaddr: local_sockaddr,
182
+ remote_sockaddr: remote_sockaddr,
183
+ server_name: host,
184
+ transport_params: transport_params || QUIC::TransportParams.default,
185
+ settings: settings
186
+ )
187
+ client.instance_variable_set(:@host, host)
188
+ client.instance_variable_set(:@port, port)
189
+ client.instance_variable_set(:@remote_address, remote_address)
190
+ client
191
+ end
192
+ end
193
+
194
+ # One server-side connection. Built by Server.accept (or Server._accept)
195
+ # from the first datagram a client sent; there is no public Server.new,
196
+ # since an object without an ngtcp2 connection behind it would be
197
+ # unusable.
198
+ class Server
199
+ include Pump
200
+
201
+ attr_reader :remote_address
202
+
203
+ private_class_method :new
204
+
205
+ # Wait for the first datagram on sock and build a connection for its
206
+ # sender. sock must be bound to the listen address and not connected.
207
+ # It is connected to the first sender, so this Server serves exactly
208
+ # that one client and shares the I/O loop with Client as is. The
209
+ # handshake is left to #run.
210
+ #
211
+ # The wait for the first datagram has no timeout; close sock from
212
+ # another thread to give up. If anything fails after that datagram
213
+ # arrived, sock stays connected to its sender: discard it and accept on
214
+ # a new socket. accept never closes sock; its lifetime is the caller's.
215
+ #
216
+ # settings must name at least one ALPN protocol, so the default
217
+ # (QUIC::Settings.default) raises ArgumentError. A qlog_dir that is not
218
+ # a writable directory raises ArgumentError before the wait.
219
+ def self.accept(sock:, certificate_path:, private_key_path:, transport_params: nil, settings: nil)
220
+ settings ||= QUIC::Settings.default
221
+ settings.check_qlog_dir
222
+
223
+ data, sender = sock.recvfrom(2048)
224
+ sock.connect(sender[3], sender[1])
225
+ # Read both addresses back from the connected socket so that the path
226
+ # given to ngtcp2 here and every later read_pkt path come from one
227
+ # source and always match.
228
+ local_sockaddr = sock.local_address.to_sockaddr
229
+ remote_sockaddr = sock.remote_address.to_sockaddr
230
+
231
+ server = _accept(
232
+ initial_packet: data,
233
+ local_sockaddr: local_sockaddr,
234
+ remote_sockaddr: remote_sockaddr,
235
+ certificate_path: certificate_path,
236
+ private_key_path: private_key_path,
237
+ transport_params: transport_params || QUIC::TransportParams.default,
238
+ settings: settings
239
+ )
240
+ server.instance_variable_set(:@local_sockaddr, local_sockaddr)
241
+ server.instance_variable_set(:@remote_sockaddr, remote_sockaddr)
242
+ server.instance_variable_set(:@remote_address, sock.remote_address)
243
+ server.bind(sock)
244
+ server.read_pkt(data, local_sockaddr: local_sockaddr, remote_sockaddr: remote_sockaddr)
245
+ server
161
246
  end
162
247
  end
163
248
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+
5
+ module QUIC
6
+ # Many server connections on one UDP socket. The Endpoint routes each
7
+ # datagram to its connection by connection ID, makes connections for new
8
+ # clients and answers what no connection can (Version Negotiation,
9
+ # Stateless Reset, CONNECTION_REFUSED). The socket and the I/O loop stay
10
+ # with the caller:
11
+ #
12
+ # loop do
13
+ # data, addr = sock.recvfrom(65_535) # or recvmsg_nonblock
14
+ # endpoint.read_pkt(data, remote: addr.to_sockaddr)
15
+ # endpoint.handle_expiry # when endpoint.expiry is due
16
+ # while (pkt, to = endpoint.write_pkt)
17
+ # sock.send(pkt, 0, to)
18
+ # end
19
+ # endpoint.take_closed.each { |conn| ... }
20
+ # end
21
+ #
22
+ # The socket must not be connected, and must be bound to the address it
23
+ # serves (not 0.0.0.0): every path's local address is local_address.
24
+ #
25
+ # The Endpoint's own answers wait for #write_pkt in a queue of at most
26
+ # 1024; any beyond are dropped, so call #write_pkt often under load.
27
+ #
28
+ # A connection's failure never raises from the Endpoint's methods. It
29
+ # closes that connection alone, and Connection#error tells why. The
30
+ # connections have no socket, so only their non-blocking methods work;
31
+ # #close queues its CONNECTION_CLOSE for #write_pkt.
32
+ class Endpoint
33
+ attr_reader :local_address
34
+
35
+ # tls_context is a QUIC::TLSContext; its ALPN list is the server's, and
36
+ # settings.alpn is not read. max_connections counts connections in their
37
+ # closing or draining period too.
38
+ def self.new(local_address:, tls_context:, transport_params: nil, settings: nil, max_connections: 1024)
39
+ raise TypeError, "local_address must be an Addrinfo (got #{local_address.class})" unless local_address.is_a?(Addrinfo)
40
+ unless max_connections.is_a?(Integer)
41
+ raise TypeError, "max_connections must be an Integer (got #{max_connections.class})"
42
+ end
43
+ settings ||= QUIC::Settings.default
44
+ settings.check_qlog_dir
45
+
46
+ endpoint = _new(
47
+ local_sockaddr: local_address.to_sockaddr,
48
+ tls_context: tls_context,
49
+ transport_params: transport_params || QUIC::TransportParams.default,
50
+ settings: settings,
51
+ max_connections: max_connections
52
+ )
53
+ endpoint.instance_variable_set(:@local_address, local_address)
54
+ endpoint
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module QUIC
4
+ # The directory holding quic/ext.h, the C function table other native
5
+ # extensions build against. An extconf.rb adds it to its include path:
6
+ #
7
+ # require "quic/ext_include"
8
+ # append_cppflags("-I#{QUIC::EXT_INCLUDE_DIR}")
9
+ #
10
+ # This file does not load the extension, so extconf.rb can require it.
11
+ EXT_INCLUDE_DIR = File.expand_path("../../ext/quic/include", __dir__)
12
+ end