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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9032a4ca3ee79288be00be635d283adcfe5435a9aea166e9c15868f051b675ea
|
|
4
|
+
data.tar.gz: 52377290a15ad9644312b2db8350bfe3979834eae926be6cd9e1e460e0091b01
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9df57929c35a332b353df2c2955a1799e42a94d0a9afb4b1b697df085daba67aca60c2a906fd3f76c1eb07ac2cbb48d657ee12a44c87d41a4d881275ae596d8a
|
|
7
|
+
data.tar.gz: 340e5424b7fe6dc6aad37dd5be581d5d4e077b062f480e1f60d5a6c7c13d296425a66244b1278a35ed1b1cd125d052df1c8dd57e9989ebf3741c650a22f5fe51
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
## [0.0.3] - 2026-10-13
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
- `QUIC::Connection::Server` serves a single connection per UDP socket. `Server.accept(sock:, certificate_path:, private_key_path:, transport_params: nil, settings: nil)` waits for the first datagram, connects the socket to its sender and returns a Server whose handshake `#run` completes; streams, `#accept_stream` and `#close` then work as on a Client. The certificate file is PEM, leaf first, and `settings` must name at least one ALPN protocol; `verify_mode`, `ca_file` and `ca_path` are ignored, since no client certificate is requested. A certificate or key that cannot be loaded or used, or an empty ALPN list, raises `ArgumentError`; a first datagram that cannot start a connection raises `QUIC::Error::Proto`. There is no Retry, address validation or Version Negotiation, and `Server.new` is private. ([#9](https://github.com/unasuke/quic-ruby/pull/9))
|
|
5
|
+
- `QUIC::Stream#closed?` returns true once both directions of the stream have finished and everything sent has been acknowledged. It stays false if the connection closes first. ([#9](https://github.com/unasuke/quic-ruby/pull/9))
|
|
6
|
+
- A C function table for other native extensions, declared in `quic/ext.h` (ABI 1.0). An extension looks up `quic_ext_get_api` with `rb_ext_resolve_symbol` (Ruby 3.3 or later) and registers a C stream handler on a connection; the handler then owns every stream of that connection, receiving stream data and handing out what to send as `#write_pkt` builds each packet. `QUIC::EXT_INCLUDE_DIR` tells an extconf.rb where the header is. quic.so now exports `quic_ext_get_api` besides `Init_quic`; ngtcp2 and picotls stay hidden. ([#12](https://github.com/unasuke/quic-ruby/pull/12))
|
|
7
|
+
- `QUIC::Error::InvalidState`, raised by `#open_bidi_stream`, `#open_uni_stream`, `#accept_stream` and `#accept_stream_nonblock` of a connection whose streams belong to a C stream handler. ([#12](https://github.com/unasuke/quic-ruby/pull/12))
|
|
8
|
+
- `QUIC::TLSContext.server(certificate_path:, private_key_path:, alpn:)` reads a server's certificate chain and private key once and keeps them with its ALPN protocols, for every connection made from it to share. A connection holds on to the context it started with. The checks and `ArgumentError` messages are those of `Server.accept`, which now builds one inside; `TLSContext.new` raises `TypeError`. ([#13](https://github.com/unasuke/quic-ruby/pull/13))
|
|
9
|
+
- `QUIC::Endpoint` serves many connections on one unconnected UDP socket, leaving the socket and the I/O loop to the caller. `Endpoint.new(local_address:, tls_context:, transport_params: nil, settings: nil, max_connections: 1024)`; `#read_pkt(packet, remote:, local: nil)` routes a datagram by its connection ID and returns the `Connection::Server` it went to, making one for a new client; `#write_pkt` returns `[packet, sockaddr]` pairs until nil; `#expiry` and `#handle_expiry` run every connection's timers; `#take_closed` hands out the connections that have finished; `#connections`, `#stop_accepting` and `#tls_context=` complete it. It answers an unsupported version with Version Negotiation, a packet for an unknown connection with a Stateless Reset (at most 100 in a burst, then one a second), and a new connection beyond `max_connections` or after `#stop_accepting` with CONNECTION_REFUSED. At most 1024 of these answers wait for `#write_pkt`; any beyond are dropped. A connection's own failure closes that connection alone and never raises from the Endpoint. Its connections have no socket: `#close` queues the CONNECTION_CLOSE for `#write_pkt`, and only non-blocking stream methods work. There is no Retry or address validation. ([#14](https://github.com/unasuke/quic-ruby/pull/14))
|
|
10
|
+
- `QUIC::Connection::Server#error` (and `Client#error`) tells why an Endpoint closed the connection: nil when the peer closed it, it timed out idle or `#close` was called, the exception itself when Ruby code in a callback raised, and otherwise a `QUIC::Error` made from ngtcp2's error code. It is always nil for a connection no Endpoint made. ([#14](https://github.com/unasuke/quic-ruby/pull/14))
|
|
11
|
+
- `QUIC::Settings#qlog_dir` (a String or Pathname, nil by default) makes every connection write its qlog to `<qlog_dir>/<its first source connection ID in hex>.sqlog`. `Client.new`, `Connection::Server.accept` and `Endpoint.new` raise `ArgumentError` unless it is a writable directory; a file that cannot be opened later, when a connection is made, only costs that connection its qlog. ([#15](https://github.com/unasuke/quic-ruby/pull/15))
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- The extension no longer uses the ngtcp2 functions and callbacks deprecated in 1.22.0 and 1.23.0, and calls their `*2` replacements instead. Behavior is unchanged. ([#8](https://github.com/unasuke/quic-ruby/pull/8))
|
|
15
|
+
- `#accept_stream_nonblock` now raises `QUIC::Error::WaitReadable` with "no peer-initiated stream available" (was "no server-initiated stream available"), as it serves `QUIC::Connection::Server` too. ([#9](https://github.com/unasuke/quic-ruby/pull/9))
|
|
16
|
+
- picotls's `lib/pembase64.c`, a PEM loader nothing uses, is no longer built at install time, so every picotls source compiled into the gem is MIT licensed. ([#10](https://github.com/unasuke/quic-ruby/pull/10))
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- `QUIC::Stream#read` and `#read_nonblock` give the peer flow control credit back for what they hand to the application. Before, a connection stopped receiving once `initial_max_data` (1 MiB by default) had been used up. ([#11](https://github.com/unasuke/quic-ruby/pull/11))
|
|
20
|
+
- When a stream the peer opened closes, the peer may open another one. Before, it could open only `initial_max_streams_bidi` bidirectional streams over the whole connection. A stream that closes while it waits for `#accept_stream` is given back only once it has been accepted, so that the streams waiting there stay within that limit. ([#11](https://github.com/unasuke/quic-ruby/pull/11))
|
|
21
|
+
- An exception raised by Ruby code inside an ngtcp2 callback (for example a `FrozenError`) comes out of `#read_pkt`, `#write_pkt` or `#handle_expiry` instead of unwinding through ngtcp2. ([#11](https://github.com/unasuke/quic-ruby/pull/11))
|
|
22
|
+
- `QUIC::Stream#write` and `#write_nonblock` count the bytes already queued but not yet sent against the peer's window, and `#write_pkt` no longer raises `QUIC::Error::FlowControl` when that window is full; the data waits until the peer extends it. ([#11](https://github.com/unasuke/quic-ruby/pull/11))
|
|
23
|
+
- `#read_pkt`, `Client._open` and `Server._accept` raise `ArgumentError` for a sockaddr String other than an AF_INET or AF_INET6 sockaddr of that family's exact size, as `Addrinfo#to_sockaddr` makes. ngtcp2 copied an address of any length into room for a `sockaddr_in6`, so a longer String overran the heap. ([#14](https://github.com/unasuke/quic-ruby/pull/14))
|
|
24
|
+
|
|
1
25
|
## [0.0.2] - 2026-09-27
|
|
2
26
|
|
|
3
27
|
### Added
|
data/LICENSE-DEPENDENCIES.txt
CHANGED
|
@@ -57,9 +57,8 @@ picotls f07f1c8 -- https://github.com/h2o/picotls
|
|
|
57
57
|
-------------------------------------------------
|
|
58
58
|
|
|
59
59
|
picotls has no top-level license file; the terms are stated in the header of
|
|
60
|
-
each source file.
|
|
61
|
-
lib/hpke.c and lib/openssl.c are MIT licensed
|
|
62
|
-
an ISC-style license. Both texts are reproduced below.
|
|
60
|
+
each source file. The files compiled into libpicotls.a, lib/picotls.c,
|
|
61
|
+
lib/hpke.c and lib/openssl.c, are MIT licensed; the text is reproduced below.
|
|
63
62
|
|
|
64
63
|
The MIT License
|
|
65
64
|
|
|
@@ -83,19 +82,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
83
82
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
84
83
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
85
84
|
IN THE SOFTWARE.
|
|
86
|
-
|
|
87
|
-
The ISC-style license
|
|
88
|
-
|
|
89
|
-
Copyright (c) 2016 Christian Huitema <huitema@huitema.net>
|
|
90
|
-
|
|
91
|
-
Permission to use, copy, modify, and distribute this software for any
|
|
92
|
-
purpose with or without fee is hereby granted, provided that the above
|
|
93
|
-
copyright notice and this permission notice appear in all copies.
|
|
94
|
-
|
|
95
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
96
|
-
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
97
|
-
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
98
|
-
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
99
|
-
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
100
|
-
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
101
|
-
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
data/README.md
CHANGED
|
@@ -50,10 +50,35 @@ More complete programs live in [`examples/`](examples/):
|
|
|
50
50
|
|
|
51
51
|
- [`handshake_demo.rb`](examples/handshake_demo.rb): prints the library versions and times a handshake.
|
|
52
52
|
- [`echo_demo.rb`](examples/echo_demo.rb): exchanges data on a stream with a local echo server.
|
|
53
|
+
- [`echo_server_demo.rb`](examples/echo_server_demo.rb): the echo server for `echo_demo.rb`, generating its own certificate.
|
|
53
54
|
- [`doq_demo.rb`](examples/doq_demo.rb): sends a DNS over QUIC query.
|
|
54
55
|
- [`io_loop_demo.rb`](examples/io_loop_demo.rb): sends the same query from an I/O loop the script owns, calling `#write_pkt` / `#read_pkt` / `#handle_expiry` itself.
|
|
55
56
|
- [`verify_demo.rb`](examples/verify_demo.rb): tries the certificate verification settings described below against a public server.
|
|
56
57
|
|
|
58
|
+
### Server
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
require "quic"
|
|
62
|
+
require "socket"
|
|
63
|
+
|
|
64
|
+
sock = UDPSocket.new(Socket::AF_INET)
|
|
65
|
+
sock.bind("127.0.0.1", 4433)
|
|
66
|
+
server = QUIC::Connection::Server.accept(
|
|
67
|
+
sock: sock, certificate_path: "server.pem", private_key_path: "server.key",
|
|
68
|
+
settings: QUIC::Settings.default.with(alpn: ["perf"])
|
|
69
|
+
)
|
|
70
|
+
server.run # completes the handshake
|
|
71
|
+
stream = server.accept_stream
|
|
72
|
+
stream.write(stream.read, fin: true) # echo everything up to the client's FIN
|
|
73
|
+
begin
|
|
74
|
+
server.pump_until { stream.closed? }
|
|
75
|
+
rescue QUIC::Error::Closed
|
|
76
|
+
# the client closed the connection before the stream finished
|
|
77
|
+
end
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`accept` connects the socket to the first client that sends to it and serves that one connection only. [`echo_server_demo.rb`](examples/echo_server_demo.rb) generates its own certificate, so it runs as is against `echo_demo.rb`.
|
|
81
|
+
|
|
57
82
|
### Certificate verification
|
|
58
83
|
|
|
59
84
|
The server certificate is verified by default, against the system's trusted CAs.
|
|
@@ -74,7 +99,7 @@ settings = QUIC::Settings.default.with(alpn: ["perf"], verify_mode: :none)
|
|
|
74
99
|
|
|
75
100
|
- **No certificate revocation checking.** CRLs and OCSP are not consulted.
|
|
76
101
|
- **No session resumption or 0-RTT.**
|
|
77
|
-
- **
|
|
102
|
+
- **The server handles one connection per socket.** `QUIC::Connection::Server.accept` serves the first client that sends to the socket; there is no Retry, address validation, Version Negotiation, or client certificate authentication.
|
|
78
103
|
- **Key exchanges and cipher suites are fixed.** X25519, secp256r1 and secp384r1 with AES-128-GCM, AES-256-GCM and ChaCha20-Poly1305. They cannot be selected from Ruby.
|
|
79
104
|
|
|
80
105
|
## Development
|
|
@@ -91,7 +116,7 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/unasuk
|
|
|
91
116
|
|
|
92
117
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
93
118
|
|
|
94
|
-
The gem ships no third-party binaries: [ngtcp2](https://github.com/ngtcp2/ngtcp2) (MIT, with
|
|
119
|
+
The gem ships no third-party binaries: [ngtcp2](https://github.com/ngtcp2/ngtcp2) (MIT, with an embedded PCG random number generator under Apache-2.0 OR MIT) and [picotls](https://github.com/h2o/picotls) (MIT) are downloaded and built on the installing machine, and libcrypto is the host's. Their license texts are in [LICENSE-DEPENDENCIES.txt](LICENSE-DEPENDENCIES.txt) for reference.
|
|
95
120
|
|
|
96
121
|
## Code of Conduct
|
|
97
122
|
|
data/Rakefile
CHANGED
|
@@ -17,4 +17,11 @@ Rake::ExtensionTask.new("quic", GEMSPEC) do |ext|
|
|
|
17
17
|
ext.lib_dir = "lib/quic"
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
# A test-only extension that drives quic/ext.h like the http3 gem does
|
|
21
|
+
# (test/test_quic_ext_api.rb). It lands in test/ext/ and is never packaged.
|
|
22
|
+
Rake::ExtensionTask.new("quic_ext_echo") do |ext|
|
|
23
|
+
ext.ext_dir = "test/ext/quic_ext_echo"
|
|
24
|
+
ext.lib_dir = "test/ext"
|
|
25
|
+
end
|
|
26
|
+
|
|
20
27
|
task default: %i[clobber compile test standard]
|
data/examples/echo_demo.rb
CHANGED
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
# Stream API demo: open a bidirectional stream, write a small payload with
|
|
4
4
|
# FIN, then block on Stream#read until the peer echoes the bytes back and
|
|
5
5
|
# closes its side. Requires a local QUIC echo server (default: 127.0.0.1:4433
|
|
6
|
-
# with ALPN "perf")
|
|
7
|
-
# when built with --enable-examples.
|
|
6
|
+
# with ALPN "perf"), such as examples/echo_server_demo.rb.
|
|
8
7
|
#
|
|
9
8
|
# Override the target via env vars:
|
|
10
9
|
# QUIC_ECHO_HOST (default 127.0.0.1)
|
|
@@ -12,10 +11,10 @@
|
|
|
12
11
|
# QUIC_ECHO_ALPN (default perf)
|
|
13
12
|
# QUIC_ECHO_CA_FILE (default unset: the server certificate is not verified)
|
|
14
13
|
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# is set, the certificate must match QUIC_ECHO_HOST,
|
|
18
|
-
# of 127.0.0.1 with the default host.
|
|
14
|
+
# examples/echo_server_demo.rb prints the path of the CA it generated; pass
|
|
15
|
+
# it as QUIC_ECHO_CA_FILE to verify the certificate. Verification is off
|
|
16
|
+
# without it. When it is set, the certificate must match QUIC_ECHO_HOST,
|
|
17
|
+
# which means an IP SAN of 127.0.0.1 with the default host.
|
|
19
18
|
#
|
|
20
19
|
# Run with: bundle exec ruby examples/echo_demo.rb
|
|
21
20
|
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Echo server for examples/echo_demo.rb: accept one QUIC connection, echo
|
|
4
|
+
# every stream back to the client (read up to its FIN, then write it all
|
|
5
|
+
# back with FIN), and exit once the client closes the connection, goes away,
|
|
6
|
+
# or opens no new stream for 10 seconds.
|
|
7
|
+
#
|
|
8
|
+
# No certificate files are needed. A throwaway CA and a server certificate it
|
|
9
|
+
# signed are generated at startup, and the CA's path is printed; the files
|
|
10
|
+
# are removed when the server exits.
|
|
11
|
+
#
|
|
12
|
+
# echo_demo.rb reads the same env vars, so it connects without any setup:
|
|
13
|
+
# QUIC_ECHO_HOST address to bind (default 127.0.0.1); also added to the
|
|
14
|
+
# certificate's SAN
|
|
15
|
+
# QUIC_ECHO_PORT (default 4433)
|
|
16
|
+
# QUIC_ECHO_ALPN (default perf)
|
|
17
|
+
#
|
|
18
|
+
# Run with: bundle exec ruby examples/echo_server_demo.rb
|
|
19
|
+
# then, from another terminal: bundle exec ruby examples/echo_demo.rb
|
|
20
|
+
|
|
21
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
22
|
+
require "quic"
|
|
23
|
+
require "ipaddr"
|
|
24
|
+
require "openssl"
|
|
25
|
+
require "socket"
|
|
26
|
+
require "tmpdir"
|
|
27
|
+
|
|
28
|
+
BIND_HOST = ENV.fetch("QUIC_ECHO_HOST", "127.0.0.1")
|
|
29
|
+
BIND_PORT = Integer(ENV.fetch("QUIC_ECHO_PORT", "4433"))
|
|
30
|
+
ALPN = ENV.fetch("QUIC_ECHO_ALPN", "perf")
|
|
31
|
+
ACCEPT_TIMEOUT = 10
|
|
32
|
+
|
|
33
|
+
# localhost and 127.0.0.1, plus the bind address so that echo_demo.rb can
|
|
34
|
+
# verify the certificate against QUIC_ECHO_HOST.
|
|
35
|
+
def subject_alt_names(host)
|
|
36
|
+
names = ["DNS:localhost", "IP:127.0.0.1"]
|
|
37
|
+
entry = begin
|
|
38
|
+
IPAddr.new(host)
|
|
39
|
+
"IP:#{host}"
|
|
40
|
+
rescue IPAddr::InvalidAddressError
|
|
41
|
+
"DNS:#{host}"
|
|
42
|
+
end
|
|
43
|
+
names << entry unless names.include?(entry)
|
|
44
|
+
names.join(",")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Write a CA and a server certificate it signed (EC P-256, valid for one
|
|
48
|
+
# day), with the server's private key, into dir. Returns the three paths.
|
|
49
|
+
def generate_certificates(dir, host)
|
|
50
|
+
now = Time.now
|
|
51
|
+
|
|
52
|
+
ca_key = OpenSSL::PKey::EC.generate("prime256v1")
|
|
53
|
+
ca_name = OpenSSL::X509::Name.parse("/CN=quic-ruby echo server demo CA")
|
|
54
|
+
ca_cert = OpenSSL::X509::Certificate.new
|
|
55
|
+
ca_cert.version = 2
|
|
56
|
+
ca_cert.serial = 1
|
|
57
|
+
ca_cert.subject = ca_name
|
|
58
|
+
ca_cert.issuer = ca_name
|
|
59
|
+
ca_cert.public_key = ca_key
|
|
60
|
+
ca_cert.not_before = now - 60
|
|
61
|
+
ca_cert.not_after = now + 86_400
|
|
62
|
+
factory = OpenSSL::X509::ExtensionFactory.new(ca_cert, ca_cert)
|
|
63
|
+
ca_cert.add_extension(factory.create_extension("basicConstraints", "CA:TRUE", true))
|
|
64
|
+
ca_cert.add_extension(factory.create_extension("keyUsage", "keyCertSign,cRLSign", true))
|
|
65
|
+
ca_cert.sign(ca_key, OpenSSL::Digest.new("SHA256"))
|
|
66
|
+
|
|
67
|
+
server_key = OpenSSL::PKey::EC.generate("prime256v1")
|
|
68
|
+
server_cert = OpenSSL::X509::Certificate.new
|
|
69
|
+
server_cert.version = 2
|
|
70
|
+
server_cert.serial = 2
|
|
71
|
+
server_cert.subject = OpenSSL::X509::Name.parse("/CN=localhost")
|
|
72
|
+
server_cert.issuer = ca_name
|
|
73
|
+
server_cert.public_key = server_key
|
|
74
|
+
server_cert.not_before = now - 60
|
|
75
|
+
server_cert.not_after = now + 86_400
|
|
76
|
+
factory = OpenSSL::X509::ExtensionFactory.new(ca_cert, server_cert)
|
|
77
|
+
server_cert.add_extension(factory.create_extension("subjectAltName", subject_alt_names(host)))
|
|
78
|
+
server_cert.add_extension(factory.create_extension("extendedKeyUsage", "serverAuth"))
|
|
79
|
+
server_cert.sign(ca_key, OpenSSL::Digest.new("SHA256"))
|
|
80
|
+
|
|
81
|
+
paths = %w[ca.pem server.pem server.key].map { |name| File.join(dir, name) }
|
|
82
|
+
File.write(paths[0], ca_cert.to_pem)
|
|
83
|
+
File.write(paths[1], server_cert.to_pem)
|
|
84
|
+
File.write(paths[2], server_key.private_to_pem)
|
|
85
|
+
paths
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
Dir.mktmpdir("quic-echo-server-") do |dir|
|
|
89
|
+
ca_file, certificate_path, private_key_path = generate_certificates(dir, BIND_HOST)
|
|
90
|
+
|
|
91
|
+
sock = UDPSocket.new(Socket::AF_INET)
|
|
92
|
+
sock.bind(BIND_HOST, BIND_PORT)
|
|
93
|
+
puts "listening on #{sock.local_address.inspect_sockaddr} (UDP), ALPN #{ALPN}"
|
|
94
|
+
puts "CA certificate: #{ca_file}"
|
|
95
|
+
puts "try a verified connection with:"
|
|
96
|
+
puts " QUIC_ECHO_CA_FILE=#{ca_file} bundle exec ruby examples/echo_demo.rb"
|
|
97
|
+
puts "(without QUIC_ECHO_CA_FILE, echo_demo.rb does not verify the certificate)"
|
|
98
|
+
puts
|
|
99
|
+
|
|
100
|
+
server = QUIC::Connection::Server.accept(
|
|
101
|
+
sock: sock, certificate_path: certificate_path, private_key_path: private_key_path,
|
|
102
|
+
settings: QUIC::Settings.default.with(alpn: [ALPN])
|
|
103
|
+
)
|
|
104
|
+
puts "accepted #{server.remote_address.inspect_sockaddr}"
|
|
105
|
+
server.run
|
|
106
|
+
puts "handshake completed"
|
|
107
|
+
|
|
108
|
+
begin
|
|
109
|
+
while (stream = server.accept_stream(timeout: ACCEPT_TIMEOUT))
|
|
110
|
+
data = stream.read
|
|
111
|
+
stream.write(data, fin: true)
|
|
112
|
+
puts "stream #{stream.id}: echoed #{data.bytesize} bytes"
|
|
113
|
+
server.pump_until { stream.closed? }
|
|
114
|
+
end
|
|
115
|
+
puts "no new stream for #{ACCEPT_TIMEOUT} seconds"
|
|
116
|
+
rescue QUIC::Error::Closed
|
|
117
|
+
puts "client closed the connection"
|
|
118
|
+
rescue Errno::ECONNREFUSED
|
|
119
|
+
# The socket is connected to the client, so once the client's socket is
|
|
120
|
+
# gone without a CONNECTION_CLOSE, the ICMP port unreachable surfaces
|
|
121
|
+
# here, from sending to it.
|
|
122
|
+
puts "client went away without closing the connection"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
server.close
|
|
126
|
+
sock.close
|
|
127
|
+
end
|