commit
3984de45fc
23 changed files with 168 additions and 604 deletions
30
.github/workflows/release.yml
vendored
30
.github/workflows/release.yml
vendored
|
|
@ -34,6 +34,16 @@ jobs:
|
||||||
platform: linux/arm64
|
platform: linux/arm64
|
||||||
tags-suffix: "-slim"
|
tags-suffix: "-slim"
|
||||||
|
|
||||||
|
- target: "musl"
|
||||||
|
build-feature: "-slim-pq"
|
||||||
|
platform: linux/amd64
|
||||||
|
tags-suffix: "-slim-pq"
|
||||||
|
|
||||||
|
- target: "musl"
|
||||||
|
build-feature: "-slim-pq"
|
||||||
|
platform: linux/arm64
|
||||||
|
tags-suffix: "-slim-pq"
|
||||||
|
|
||||||
- target: "gnu"
|
- target: "gnu"
|
||||||
build-feature: "-s2n"
|
build-feature: "-s2n"
|
||||||
platform: linux/amd64
|
platform: linux/amd64
|
||||||
|
|
@ -44,6 +54,26 @@ jobs:
|
||||||
platform: linux/arm64
|
platform: linux/arm64
|
||||||
tags-suffix: "-s2n"
|
tags-suffix: "-s2n"
|
||||||
|
|
||||||
|
- target: "gnu"
|
||||||
|
build-feature: "-pq"
|
||||||
|
platform: linux/amd64
|
||||||
|
tags-suffix: "-pq"
|
||||||
|
|
||||||
|
- target: "gnu"
|
||||||
|
build-feature: "-pq"
|
||||||
|
platform: linux/arm64
|
||||||
|
tags-suffix: "-pq"
|
||||||
|
|
||||||
|
- target: "gnu"
|
||||||
|
build-feature: "-s2n-pq"
|
||||||
|
platform: linux/amd64
|
||||||
|
tags-suffix: "-s2n-pq"
|
||||||
|
|
||||||
|
- target: "gnu"
|
||||||
|
build-feature: "-s2n-pq"
|
||||||
|
platform: linux/arm64
|
||||||
|
tags-suffix: "-s2n-pq"
|
||||||
|
|
||||||
- target: "gnu"
|
- target: "gnu"
|
||||||
build-feature: "-webpki-roots"
|
build-feature: "-webpki-roots"
|
||||||
platform: linux/amd64
|
platform: linux/amd64
|
||||||
|
|
|
||||||
37
.github/workflows/release_docker.yml
vendored
37
.github/workflows/release_docker.yml
vendored
|
|
@ -30,6 +30,17 @@ jobs:
|
||||||
jqtype/rpxy:latest
|
jqtype/rpxy:latest
|
||||||
ghcr.io/junkurihara/rust-rpxy:latest
|
ghcr.io/junkurihara/rust-rpxy:latest
|
||||||
|
|
||||||
|
- target: "default-pq"
|
||||||
|
dockerfile: ./docker/Dockerfile
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
build-args: |
|
||||||
|
"CARGO_FEATURES=--no-default-features --features=http3-quinn,cache,rustls-backend,acme,post-quantum"
|
||||||
|
tags-suffix: "-pq"
|
||||||
|
# Aliases must be used only for release builds
|
||||||
|
aliases: |
|
||||||
|
jqtype/rpxy:pq
|
||||||
|
ghcr.io/junkurihara/rust-rpxy:pq
|
||||||
|
|
||||||
- target: "default-slim"
|
- target: "default-slim"
|
||||||
dockerfile: ./docker/Dockerfile-slim
|
dockerfile: ./docker/Dockerfile-slim
|
||||||
build-contexts: |
|
build-contexts: |
|
||||||
|
|
@ -42,6 +53,20 @@ jobs:
|
||||||
jqtype/rpxy:slim
|
jqtype/rpxy:slim
|
||||||
ghcr.io/junkurihara/rust-rpxy:slim
|
ghcr.io/junkurihara/rust-rpxy:slim
|
||||||
|
|
||||||
|
- target: "default-slim-pq"
|
||||||
|
dockerfile: ./docker/Dockerfile-slim
|
||||||
|
build-args: |
|
||||||
|
"CARGO_FEATURES=--no-default-features --features=http3-quinn,cache,rustls-backend,acme,post-quantum"
|
||||||
|
build-contexts: |
|
||||||
|
messense/rust-musl-cross:amd64-musl=docker-image://messense/rust-musl-cross:x86_64-musl
|
||||||
|
messense/rust-musl-cross:arm64-musl=docker-image://messense/rust-musl-cross:aarch64-musl
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
tags-suffix: "-slim-pq"
|
||||||
|
# Aliases must be used only for release builds
|
||||||
|
aliases: |
|
||||||
|
jqtype/rpxy:slim-pq
|
||||||
|
ghcr.io/junkurihara/rust-rpxy:slim-pq
|
||||||
|
|
||||||
- target: "s2n"
|
- target: "s2n"
|
||||||
dockerfile: ./docker/Dockerfile
|
dockerfile: ./docker/Dockerfile
|
||||||
build-args: |
|
build-args: |
|
||||||
|
|
@ -54,6 +79,18 @@ jobs:
|
||||||
jqtype/rpxy:s2n
|
jqtype/rpxy:s2n
|
||||||
ghcr.io/junkurihara/rust-rpxy:s2n
|
ghcr.io/junkurihara/rust-rpxy:s2n
|
||||||
|
|
||||||
|
- target: "s2n-pq"
|
||||||
|
dockerfile: ./docker/Dockerfile
|
||||||
|
build-args: |
|
||||||
|
"CARGO_FEATURES=--no-default-features --features=http3-s2n,cache,rustls-backend,acme,post-quantum"
|
||||||
|
"ADDITIONAL_DEPS=pkg-config libssl-dev cmake libclang1 gcc g++"
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
tags-suffix: "-s2n-pq"
|
||||||
|
# Aliases must be used only for release builds
|
||||||
|
aliases: |
|
||||||
|
jqtype/rpxy:s2n-pq
|
||||||
|
ghcr.io/junkurihara/rust-rpxy:s2n-pq
|
||||||
|
|
||||||
- target: "webpki-roots"
|
- target: "webpki-roots"
|
||||||
dockerfile: ./docker/Dockerfile
|
dockerfile: ./docker/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|
|
||||||
4
.gitmodules
vendored
4
.gitmodules
vendored
|
|
@ -4,3 +4,7 @@
|
||||||
[submodule "submodules/rustls-acme"]
|
[submodule "submodules/rustls-acme"]
|
||||||
path = submodules/rustls-acme
|
path = submodules/rustls-acme
|
||||||
url = git@github.com:junkurihara/rustls-acme.git
|
url = git@github.com:junkurihara/rustls-acme.git
|
||||||
|
[submodule "submodules/s2n-quic"]
|
||||||
|
path = submodules/s2n-quic
|
||||||
|
url = git@github.com:junkurihara/s2n-quic.git
|
||||||
|
branch = rustls-pq
|
||||||
|
|
|
||||||
4
.rustfmt.toml
Normal file
4
.rustfmt.toml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
edition = "2021"
|
||||||
|
newline_style = "Unix"
|
||||||
|
tab_spaces = 2
|
||||||
|
max_width = 130
|
||||||
10
CHANGELOG.md
10
CHANGELOG.md
|
|
@ -2,6 +2,16 @@
|
||||||
|
|
||||||
## 0.10.0 (Unreleased)
|
## 0.10.0 (Unreleased)
|
||||||
|
|
||||||
|
## 0.9.3
|
||||||
|
|
||||||
|
### Improvement
|
||||||
|
|
||||||
|
- Feat: Support post-quantum `X25519Kyber768Draft00` for incoming and outgoing TLS initiation. This is non-default feature [feature: `post-quantum`].
|
||||||
|
- Feat: emit WARN messages if there exist unused and unsupported options specified in configuration file.
|
||||||
|
- Docs: `rpxy.io` is now available for the official website of `rpxy`.
|
||||||
|
- Refactor: lots of minor improvements
|
||||||
|
- Deps
|
||||||
|
|
||||||
## 0.9.2
|
## 0.9.2
|
||||||
|
|
||||||
### Improvement
|
### Improvement
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.9.2"
|
version = "0.9.3"
|
||||||
authors = ["Jun Kurihara"]
|
authors = ["Jun Kurihara"]
|
||||||
homepage = "https://github.com/junkurihara/rust-rpxy"
|
homepage = "https://github.com/junkurihara/rust-rpxy"
|
||||||
repository = "https://github.com/junkurihara/rust-rpxy"
|
repository = "https://github.com/junkurihara/rust-rpxy"
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,12 @@
|
||||||
|
|
||||||
[^pure_rust]: Doubtfully can be claimed to be written in pure Rust since current `rpxy` is based on `aws-lc-rs` for cryptographic operations.
|
[^pure_rust]: Doubtfully can be claimed to be written in pure Rust since current `rpxy` is based on `aws-lc-rs` for cryptographic operations.
|
||||||
|
|
||||||
By default, `rpxy` provides the *TLS connection sanitization* by correctly binding a certificate used to establish a secure channel with the backend application. Specifically, it always keeps the consistency between the given SNI (server name indication) in `ClientHello` of the underlying TLS and the domain name given by the overlaid HTTP HOST header (or URL in Request line) [^1]. Additionally, as a somewhat unstable feature, our `rpxy` can handle the brand-new HTTP/3 connection thanks to [`quinn`](https://github.com/quinn-rs/quinn), [`s2n-quic`](https://github.com/aws/s2n-quic) and [`hyperium/h3`](https://github.com/hyperium/h3).[^h3lib] Furthermore, `rpxy` supports the automatic issuance and renewal of certificates via [TLS-ALPN-01 (RFC8737)](https://www.rfc-editor.org/rfc/rfc8737) of [ACME protocol (RFC8555)](https://www.rfc-editor.org/rfc/rfc8555) thanks to [`rustls-acme`](https://github.com/FlorianUekermann/rustls-acme).
|
By default, `rpxy` provides the *TLS connection sanitization* by correctly binding a certificate used to establish a secure channel with the backend application. Specifically, it always keeps the consistency between the given SNI (server name indication) in `ClientHello` of the underlying TLS and the domain name given by the overlaid HTTP HOST header (or URL in Request line) [^1]. Additionally, as a somewhat unstable feature, our `rpxy` can handle the brand-new HTTP/3 connection thanks to [`quinn`](https://github.com/quinn-rs/quinn), [`s2n-quic`](https://github.com/aws/s2n-quic) and [`hyperium/h3`](https://github.com/hyperium/h3).[^h3lib] Furthermore, `rpxy` supports the automatic issuance and renewal of certificates via [TLS-ALPN-01 (RFC8737)](https://www.rfc-editor.org/rfc/rfc8737) of [ACME protocol (RFC8555)](https://www.rfc-editor.org/rfc/rfc8555) thanks to [`rustls-acme`](https://github.com/FlorianUekermann/rustls-acme), and the hybridized post-quantum key exchange [`X25519Kyber768Draft00`](https://datatracker.ietf.org/doc/draft-tls-westerbaan-xyber768d00/)[^kyber] for TLS incoming and outgoing initiation thanks to [`rustls-post-quantum`](https://docs.rs/rustls-post-quantum/latest/rustls_post_quantum/).
|
||||||
|
|
||||||
[^h3lib]: HTTP/3 libraries are mutually exclusive. You need to explicitly specify `s2n-quic` with `--no-default-features` flag. Also note that if you build `rpxy` with `s2n-quic`, then it requires `openssl` just for building the package.
|
[^h3lib]: HTTP/3 libraries are mutually exclusive. You need to explicitly specify `s2n-quic` with `--no-default-features` flag. Also note that if you build `rpxy` with `s2n-quic`, then it requires `openssl` just for building the package.
|
||||||
|
|
||||||
|
[^kyber]: This is not yet a default feature. You need to specify `--features post-quantum` when building `rpxy`. Also note that `X25519Kyber768Draft00` is a draft version yet this is widely used on the Internet. We will update the feature when the newest version (`X25519MLKEM768` in [`ECDHE-MLKEM`](https://www.ietf.org/archive/id/draft-kwiatkowski-tls-ecdhe-mlkem-02.html)) is available.
|
||||||
|
|
||||||
This project is still *work-in-progress*. But it is already working in some production environments and serves a number of domain names. Furthermore it *significantly outperforms* NGINX and Caddy, e.g., *1.5x faster than NGINX*, in the setting of a very simple HTTP reverse-proxy scenario (See [`bench`](./bench/) directory).
|
This project is still *work-in-progress*. But it is already working in some production environments and serves a number of domain names. Furthermore it *significantly outperforms* NGINX and Caddy, e.g., *1.5x faster than NGINX*, in the setting of a very simple HTTP reverse-proxy scenario (See [`bench`](./bench/) directory).
|
||||||
|
|
||||||
[^1]: We should note that NGINX doesn't guarantee such a consistency by default. To this end, you have to add `if` statement in the configuration file in NGINX.
|
[^1]: We should note that NGINX doesn't guarantee such a consistency by default. To this end, you have to add `if` statement in the configuration file in NGINX.
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ listen_port = 8080
|
||||||
listen_port_tls = 8443
|
listen_port_tls = 8443
|
||||||
|
|
||||||
# Optional. If you listen on a custom port like 8443 but redirect with firewall to 443
|
# Optional. If you listen on a custom port like 8443 but redirect with firewall to 443
|
||||||
# When you specify this, the server sends a redirection response 301 with specified port to the client for plaintext http request.
|
# When you specify this, the server uses this port in an "Alt-SVC" header for e.g. indicating support for HTTP/3 and also sends a redirection response 301 with specified port to the client for plaintext http request
|
||||||
# Otherwise, the server sends 301 with the same port as `listen_port_tls`.
|
# Otherwise, the server sends Alt-SVC and 301 with the same port as `listen_port_tls`.
|
||||||
# https_redirection_port = 443
|
# https_redirection_port = 443
|
||||||
|
|
||||||
# Optional for h2 and http1.1
|
# Optional for h2 and http1.1
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,14 @@ Differences among tags are summarized as follows.
|
||||||
- `latest`: Built from the `main` branch with default features, running on Ubuntu.
|
- `latest`: Built from the `main` branch with default features, running on Ubuntu.
|
||||||
- `latest-slim`, `slim`: Built by `musl` from the `main` branch with default features, running on Alpine.
|
- `latest-slim`, `slim`: Built by `musl` from the `main` branch with default features, running on Alpine.
|
||||||
- `latest-s2n`, `s2n`: Built from the `main` branch with the `http3-s2n` feature, running on Ubuntu.
|
- `latest-s2n`, `s2n`: Built from the `main` branch with the `http3-s2n` feature, running on Ubuntu.
|
||||||
|
- `*-pq`: Built with the `post-quantum` feature. This feature supports the post-quantum key exchange using `rustls-post-quantum` crate.
|
||||||
|
|
||||||
### Nightly Builds
|
### Nightly Builds
|
||||||
|
|
||||||
- `nightly`: Built from the `develop` branch with default features, running on Ubuntu.
|
- `nightly`: Built from the `develop` branch with default features, running on Ubuntu.
|
||||||
- `nightly-slim`: Built by `musl` from the `develop` branch with default features, running on Alpine.
|
- `nightly-slim`: Built by `musl` from the `develop` branch with default features, running on Alpine.
|
||||||
- `nightly-s2n`: Built from the `develop` branch with the `http3-s2n` feature, running on Ubuntu.
|
- `nightly-s2n`: Built from the `develop` branch with the `http3-s2n` feature, running on Ubuntu.
|
||||||
|
- `*-pq`: Built with the `post-quantum` feature. This feature supports the hybridized post-quantum key exchange using `rustls-post-quantum` crate.
|
||||||
|
|
||||||
## Caveats
|
## Caveats
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,13 @@ readme.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
publish.workspace = true
|
publish.workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
post-quantum = ["rustls-post-quantum"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
url = { version = "2.5.2" }
|
url = { version = "2.5.2" }
|
||||||
rustc-hash = "2.0.0"
|
rustc-hash = "2.0.0"
|
||||||
thiserror = "1.0.64"
|
thiserror = "1.0.66"
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
async-trait = "0.1.83"
|
async-trait = "0.1.83"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
|
|
@ -21,7 +24,7 @@ aws-lc-rs = { version = "1.10.0", default-features = false, features = [
|
||||||
"aws-lc-sys",
|
"aws-lc-sys",
|
||||||
] }
|
] }
|
||||||
blocking = "1.6.1"
|
blocking = "1.6.1"
|
||||||
rustls = { version = "0.23.14", default-features = false, features = [
|
rustls = { version = "0.23.16", default-features = false, features = [
|
||||||
"std",
|
"std",
|
||||||
"aws_lc_rs",
|
"aws_lc_rs",
|
||||||
] }
|
] }
|
||||||
|
|
@ -29,6 +32,7 @@ rustls-platform-verifier = { version = "0.3.4" }
|
||||||
rustls-acme = { path = "../submodules/rustls-acme/", default-features = false, features = [
|
rustls-acme = { path = "../submodules/rustls-acme/", default-features = false, features = [
|
||||||
"aws-lc-rs",
|
"aws-lc-rs",
|
||||||
] }
|
] }
|
||||||
tokio = { version = "1.40.0", default-features = false }
|
rustls-post-quantum = { version = "0.1.0", optional = true }
|
||||||
|
tokio = { version = "1.41.0", default-features = false }
|
||||||
tokio-util = { version = "0.7.12", default-features = false }
|
tokio-util = { version = "0.7.12", default-features = false }
|
||||||
tokio-stream = { version = "0.1.16", default-features = false }
|
tokio-stream = { version = "0.1.16", default-features = false }
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,11 @@ impl AcmeManager {
|
||||||
domains: &[String],
|
domains: &[String],
|
||||||
runtime_handle: Handle,
|
runtime_handle: Handle,
|
||||||
) -> Result<Self, RpxyAcmeError> {
|
) -> Result<Self, RpxyAcmeError> {
|
||||||
|
#[cfg(not(feature = "post-quantum"))]
|
||||||
// Install aws_lc_rs as default crypto provider for rustls
|
// Install aws_lc_rs as default crypto provider for rustls
|
||||||
let _ = rustls::crypto::CryptoProvider::install_default(rustls::crypto::aws_lc_rs::default_provider());
|
let _ = rustls::crypto::CryptoProvider::install_default(rustls::crypto::aws_lc_rs::default_provider());
|
||||||
|
#[cfg(feature = "post-quantum")]
|
||||||
|
let _ = rustls::crypto::CryptoProvider::install_default(rustls_post_quantum::provider());
|
||||||
|
|
||||||
let acme_registry_dir = acme_registry_dir
|
let acme_registry_dir = acme_registry_dir
|
||||||
.map(|v| v.to_ascii_lowercase())
|
.map(|v| v.to_ascii_lowercase())
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ publish.workspace = true
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
# default = ["http3-quinn", "cache", "rustls-backend", "acme", "post-quantum"]
|
||||||
|
# default = ["http3-s2n", "cache", "rustls-backend", "acme", "post-quantum"]
|
||||||
default = ["http3-quinn", "cache", "rustls-backend", "acme"]
|
default = ["http3-quinn", "cache", "rustls-backend", "acme"]
|
||||||
# default = ["http3-s2n", "cache", "rustls-backend", "acme"]
|
# default = ["http3-s2n", "cache", "rustls-backend", "acme"]
|
||||||
http3-quinn = ["rpxy-lib/http3-quinn"]
|
http3-quinn = ["rpxy-lib/http3-quinn"]
|
||||||
|
|
@ -22,6 +24,7 @@ rustls-backend = ["rpxy-lib/rustls-backend"]
|
||||||
webpki-roots = ["rpxy-lib/webpki-roots"]
|
webpki-roots = ["rpxy-lib/webpki-roots"]
|
||||||
cache = ["rpxy-lib/cache"]
|
cache = ["rpxy-lib/cache"]
|
||||||
acme = ["rpxy-lib/acme", "rpxy-acme"]
|
acme = ["rpxy-lib/acme", "rpxy-acme"]
|
||||||
|
post-quantum = ["rpxy-lib/post-quantum"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rpxy-lib = { path = "../rpxy-lib/", default-features = false, features = [
|
rpxy-lib = { path = "../rpxy-lib/", default-features = false, features = [
|
||||||
|
|
@ -29,10 +32,10 @@ rpxy-lib = { path = "../rpxy-lib/", default-features = false, features = [
|
||||||
] }
|
] }
|
||||||
|
|
||||||
mimalloc = { version = "*", default-features = false }
|
mimalloc = { version = "*", default-features = false }
|
||||||
anyhow = "1.0.89"
|
anyhow = "1.0.91"
|
||||||
rustc-hash = "2.0.0"
|
rustc-hash = "2.0.0"
|
||||||
serde = { version = "1.0.210", default-features = false, features = ["derive"] }
|
serde = { version = "1.0.214", default-features = false, features = ["derive"] }
|
||||||
tokio = { version = "1.40.0", default-features = false, features = [
|
tokio = { version = "1.41.0", default-features = false, features = [
|
||||||
"net",
|
"net",
|
||||||
"rt-multi-thread",
|
"rt-multi-thread",
|
||||||
"time",
|
"time",
|
||||||
|
|
@ -47,6 +50,7 @@ futures-util = { version = "0.3.31", default-features = false }
|
||||||
clap = { version = "4.5.20", features = ["std", "cargo", "wrap_help"] }
|
clap = { version = "4.5.20", features = ["std", "cargo", "wrap_help"] }
|
||||||
toml = { version = "0.8.19", default-features = false, features = ["parse"] }
|
toml = { version = "0.8.19", default-features = false, features = ["parse"] }
|
||||||
hot_reload = "0.1.6"
|
hot_reload = "0.1.6"
|
||||||
|
serde_ignored = "0.1.10"
|
||||||
|
|
||||||
# logging
|
# logging
|
||||||
tracing = { version = "0.1.40" }
|
tracing = { version = "0.1.40" }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
constants::*,
|
constants::*,
|
||||||
error::{anyhow, ensure},
|
error::{anyhow, ensure},
|
||||||
|
log::warn,
|
||||||
};
|
};
|
||||||
use rpxy_lib::{reexports::Uri, AppConfig, ProxyConfig, ReverseProxyConfig, TlsConfig, UpstreamUri};
|
use rpxy_lib::{reexports::Uri, AppConfig, ProxyConfig, ReverseProxyConfig, TlsConfig, UpstreamUri};
|
||||||
use rustc_hash::FxHashMap as HashMap;
|
use rustc_hash::FxHashMap as HashMap;
|
||||||
|
|
@ -229,7 +230,21 @@ impl ConfigToml {
|
||||||
pub fn new(config_file: &str) -> std::result::Result<Self, anyhow::Error> {
|
pub fn new(config_file: &str) -> std::result::Result<Self, anyhow::Error> {
|
||||||
let config_str = fs::read_to_string(config_file)?;
|
let config_str = fs::read_to_string(config_file)?;
|
||||||
|
|
||||||
toml::from_str(&config_str).map_err(|e| anyhow!(e))
|
// Check unused fields during deserialization
|
||||||
|
let t = toml::de::Deserializer::new(&config_str);
|
||||||
|
let mut unused = rustc_hash::FxHashSet::default();
|
||||||
|
|
||||||
|
let res = serde_ignored::deserialize(t, |path| {
|
||||||
|
unused.insert(path.to_string());
|
||||||
|
})
|
||||||
|
.map_err(|e| anyhow!(e));
|
||||||
|
|
||||||
|
if !unused.is_empty() {
|
||||||
|
let str = unused.iter().fold(String::new(), |acc, x| acc + x + "\n");
|
||||||
|
warn!("Configuration file contains unsupported fields. Check typos:\n{}", str);
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,16 +12,17 @@ publish.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["http3"]
|
default = ["http3"]
|
||||||
|
post-quantum = ["rustls-post-quantum"]
|
||||||
http3 = []
|
http3 = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rustc-hash = { version = "2.0.0" }
|
rustc-hash = { version = "2.0.0" }
|
||||||
tracing = { version = "0.1.40" }
|
tracing = { version = "0.1.40" }
|
||||||
derive_builder = { version = "0.20.2" }
|
derive_builder = { version = "0.20.2" }
|
||||||
thiserror = { version = "1.0.64" }
|
thiserror = { version = "1.0.66" }
|
||||||
hot_reload = { version = "0.1.6" }
|
hot_reload = { version = "0.1.6" }
|
||||||
async-trait = { version = "0.1.83" }
|
async-trait = { version = "0.1.83" }
|
||||||
rustls = { version = "0.23.14", default-features = false, features = [
|
rustls = { version = "0.23.16", default-features = false, features = [
|
||||||
"std",
|
"std",
|
||||||
"aws_lc_rs",
|
"aws_lc_rs",
|
||||||
] }
|
] }
|
||||||
|
|
@ -30,10 +31,11 @@ rustls-webpki = { version = "0.102.8", default-features = false, features = [
|
||||||
"std",
|
"std",
|
||||||
"aws_lc_rs",
|
"aws_lc_rs",
|
||||||
] }
|
] }
|
||||||
|
rustls-post-quantum = { version = "0.1.0", optional = true }
|
||||||
x509-parser = { version = "0.16.0" }
|
x509-parser = { version = "0.16.0" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "1.40.0", default-features = false, features = [
|
tokio = { version = "1.41.0", default-features = false, features = [
|
||||||
"rt-multi-thread",
|
"rt-multi-thread",
|
||||||
"macros",
|
"macros",
|
||||||
] }
|
] }
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ mod log {
|
||||||
use crate::{error::*, log::*, reloader_service::DynCryptoSource};
|
use crate::{error::*, log::*, reloader_service::DynCryptoSource};
|
||||||
use hot_reload::{ReloaderReceiver, ReloaderService};
|
use hot_reload::{ReloaderReceiver, ReloaderService};
|
||||||
use rustc_hash::FxHashMap as HashMap;
|
use rustc_hash::FxHashMap as HashMap;
|
||||||
use rustls::crypto::{aws_lc_rs, CryptoProvider};
|
use rustls::crypto::CryptoProvider;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/* ------------------------------------------------ */
|
/* ------------------------------------------------ */
|
||||||
|
|
@ -44,8 +44,11 @@ where
|
||||||
T: CryptoSource<Error = RpxyCertError> + Send + Sync + Clone + 'static,
|
T: CryptoSource<Error = RpxyCertError> + Send + Sync + Clone + 'static,
|
||||||
{
|
{
|
||||||
info!("Building certificate reloader service");
|
info!("Building certificate reloader service");
|
||||||
|
#[cfg(not(feature = "post-quantum"))]
|
||||||
// Install aws_lc_rs as default crypto provider for rustls
|
// Install aws_lc_rs as default crypto provider for rustls
|
||||||
let _ = CryptoProvider::install_default(aws_lc_rs::default_provider());
|
let _ = CryptoProvider::install_default(rustls::crypto::aws_lc_rs::default_provider());
|
||||||
|
#[cfg(feature = "post-quantum")]
|
||||||
|
let _ = CryptoProvider::install_default(rustls_post_quantum::provider());
|
||||||
|
|
||||||
let source = crypto_source_map
|
let source = crypto_source_map
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
||||||
|
|
@ -179,7 +179,10 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_server_crypto_base_try_into() {
|
async fn test_server_crypto_base_try_into() {
|
||||||
|
#[cfg(not(feature = "post-quantum"))]
|
||||||
let _ = CryptoProvider::install_default(rustls::crypto::aws_lc_rs::default_provider());
|
let _ = CryptoProvider::install_default(rustls::crypto::aws_lc_rs::default_provider());
|
||||||
|
#[cfg(feature = "post-quantum")]
|
||||||
|
let _ = CryptoProvider::install_default(rustls_post_quantum::provider());
|
||||||
|
|
||||||
let mut server_crypto_base = ServerCryptoBase::default();
|
let mut server_crypto_base = ServerCryptoBase::default();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,14 +28,20 @@ native-tls-backend = ["hyper-tls"]
|
||||||
rustls-backend = ["hyper-rustls"]
|
rustls-backend = ["hyper-rustls"]
|
||||||
webpki-roots = ["rustls-backend", "hyper-rustls/webpki-tokio"]
|
webpki-roots = ["rustls-backend", "hyper-rustls/webpki-tokio"]
|
||||||
acme = ["dep:rpxy-acme"]
|
acme = ["dep:rpxy-acme"]
|
||||||
|
post-quantum = [
|
||||||
|
"rustls-post-quantum",
|
||||||
|
"rpxy-acme/post-quantum",
|
||||||
|
"rpxy-certs/post-quantum",
|
||||||
|
"s2n-quic-rustls/post-quantum",
|
||||||
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rustc-hash = "2.0.0"
|
rustc-hash = "2.0.0"
|
||||||
bytes = "1.7.2"
|
bytes = "1.8.0"
|
||||||
derive_builder = "0.20.2"
|
derive_builder = "0.20.2"
|
||||||
futures = { version = "0.3.31", features = ["alloc", "async-await"] }
|
futures = { version = "0.3.31", features = ["alloc", "async-await"] }
|
||||||
tokio = { version = "1.40.0", default-features = false, features = [
|
tokio = { version = "1.41.0", default-features = false, features = [
|
||||||
"net",
|
"net",
|
||||||
"rt-multi-thread",
|
"rt-multi-thread",
|
||||||
"time",
|
"time",
|
||||||
|
|
@ -44,18 +50,18 @@ tokio = { version = "1.40.0", default-features = false, features = [
|
||||||
"fs",
|
"fs",
|
||||||
] }
|
] }
|
||||||
tokio-util = { version = "0.7.12", default-features = false }
|
tokio-util = { version = "0.7.12", default-features = false }
|
||||||
pin-project-lite = "0.2.14"
|
pin-project-lite = "0.2.15"
|
||||||
async-trait = "0.1.83"
|
async-trait = "0.1.83"
|
||||||
|
|
||||||
# Error handling
|
# Error handling
|
||||||
anyhow = "1.0.89"
|
anyhow = "1.0.91"
|
||||||
thiserror = "1.0.64"
|
thiserror = "1.0.66"
|
||||||
|
|
||||||
# http for both server and client
|
# http for both server and client
|
||||||
http = "1.1.0"
|
http = "1.1.0"
|
||||||
http-body-util = "0.1.2"
|
http-body-util = "0.1.2"
|
||||||
hyper = { version = "1.4.1", default-features = false }
|
hyper = { version = "1.5.0", default-features = false }
|
||||||
hyper-util = { version = "0.1.9", features = ["full"] }
|
hyper-util = { version = "0.1.10", features = ["full"] }
|
||||||
futures-util = { version = "0.3.31", default-features = false }
|
futures-util = { version = "0.3.31", default-features = false }
|
||||||
futures-channel = { version = "0.3.31", default-features = false }
|
futures-channel = { version = "0.3.31", default-features = false }
|
||||||
|
|
||||||
|
|
@ -74,7 +80,8 @@ hyper-rustls = { version = "0.27.3", default-features = false, features = [
|
||||||
# tls and cert management for server
|
# tls and cert management for server
|
||||||
rpxy-certs = { path = "../rpxy-certs/", default-features = false }
|
rpxy-certs = { path = "../rpxy-certs/", default-features = false }
|
||||||
hot_reload = "0.1.6"
|
hot_reload = "0.1.6"
|
||||||
rustls = { version = "0.23.14", default-features = false }
|
rustls = { version = "0.23.16", default-features = false }
|
||||||
|
rustls-post-quantum = { version = "0.1.0", optional = true }
|
||||||
tokio-rustls = { version = "0.26.0", features = ["early-data"] }
|
tokio-rustls = { version = "0.26.0", features = ["early-data"] }
|
||||||
|
|
||||||
# acme
|
# acme
|
||||||
|
|
@ -87,14 +94,14 @@ tracing = { version = "0.1.40" }
|
||||||
quinn = { version = "0.11.5", optional = true }
|
quinn = { version = "0.11.5", optional = true }
|
||||||
h3 = { version = "0.0.6", features = ["tracing"], optional = true }
|
h3 = { version = "0.0.6", features = ["tracing"], optional = true }
|
||||||
h3-quinn = { version = "0.0.7", optional = true }
|
h3-quinn = { version = "0.0.7", optional = true }
|
||||||
s2n-quic-h3 = { path = "../submodules/s2n-quic-h3/", features = [
|
s2n-quic = { version = "1.48.0", path = "../submodules/s2n-quic/quic/s2n-quic/", default-features = false, features = [
|
||||||
"tracing",
|
|
||||||
], optional = true }
|
|
||||||
s2n-quic = { version = "1.47.0", default-features = false, features = [
|
|
||||||
"provider-tls-rustls",
|
"provider-tls-rustls",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
s2n-quic-core = { version = "0.47.0", default-features = false, optional = true }
|
s2n-quic-core = { version = "0.48.0", path = "../submodules/s2n-quic/quic/s2n-quic-core", default-features = false, optional = true }
|
||||||
s2n-quic-rustls = { version = "0.47.0", optional = true }
|
s2n-quic-rustls = { version = "0.48.0", path = "../submodules/s2n-quic/quic/s2n-quic-rustls", optional = true }
|
||||||
|
s2n-quic-h3 = { path = "../submodules/s2n-quic/quic/s2n-quic-h3/", features = [
|
||||||
|
"tracing",
|
||||||
|
], optional = true }
|
||||||
##########
|
##########
|
||||||
# for UDP socket wit SO_REUSEADDR when h3 with quinn
|
# for UDP socket wit SO_REUSEADDR when h3 with quinn
|
||||||
socket2 = { version = "0.5.7", features = ["all"], optional = true }
|
socket2 = { version = "0.5.7", features = ["all"], optional = true }
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ use crate::{
|
||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
use hot_reload::ReloaderReceiver;
|
use hot_reload::ReloaderReceiver;
|
||||||
use rpxy_certs::ServerCryptoBase;
|
use rpxy_certs::ServerCryptoBase;
|
||||||
use rustls::crypto::{aws_lc_rs, CryptoProvider};
|
use rustls::crypto::CryptoProvider;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
|
|
@ -101,8 +101,13 @@ pub async fn entrypoint(
|
||||||
info!("Cache is disabled")
|
info!("Cache is disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "post-quantum"))]
|
||||||
// Install aws_lc_rs as default crypto provider for rustls
|
// Install aws_lc_rs as default crypto provider for rustls
|
||||||
let _ = CryptoProvider::install_default(aws_lc_rs::default_provider());
|
let _ = CryptoProvider::install_default(rustls::crypto::aws_lc_rs::default_provider());
|
||||||
|
#[cfg(feature = "post-quantum")]
|
||||||
|
let _ = CryptoProvider::install_default(rustls_post_quantum::provider());
|
||||||
|
#[cfg(feature = "post-quantum")]
|
||||||
|
info!("Post-quantum crypto provider is installed");
|
||||||
|
|
||||||
// 1. build backends, and make it contained in Arc
|
// 1. build backends, and make it contained in Arc
|
||||||
let app_manager = Arc::new(backend::BackendAppManager::try_from(app_config_list)?);
|
let app_manager = Arc::new(backend::BackendAppManager::try_from(app_config_list)?);
|
||||||
|
|
|
||||||
1
submodules/s2n-quic
Submodule
1
submodules/s2n-quic
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit ffeaac1eb32589599c9be357f2273a2824741c7d
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "s2n-quic-h3"
|
|
||||||
# this in an unpublished internal crate so the version should not be changed
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["AWS s2n"]
|
|
||||||
edition = "2021"
|
|
||||||
rust-version = "1.71"
|
|
||||||
license = "Apache-2.0"
|
|
||||||
# this contains an http3 implementation for testing purposes and should not be published
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
bytes = { version = "1", default-features = false }
|
|
||||||
futures = { version = "0.3", default-features = false }
|
|
||||||
h3 = { version = "0.0.6", features = ["tracing"] }
|
|
||||||
# s2n-quic = { path = "../s2n-quic" }
|
|
||||||
# s2n-quic-core = { path = "../s2n-quic-core" }
|
|
||||||
s2n-quic = { version = "1.47.0" }
|
|
||||||
s2n-quic-core = { version = "0.47.0" }
|
|
||||||
tracing = { version = "0.1.40", optional = true }
|
|
||||||
|
|
||||||
[features]
|
|
||||||
tracing = ["dep:tracing"]
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
# s2n-quic-h3
|
|
||||||
|
|
||||||
This is an internal crate used by [s2n-quic](https://github.com/aws/s2n-quic) written as a proof of concept for implementing HTTP3 on top of s2n-quic. The API is not currently stable and should not be used directly.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
This project is licensed under the [Apache-2.0 License][license-url].
|
|
||||||
|
|
||||||
[license-badge]: https://img.shields.io/badge/license-apache-blue.svg
|
|
||||||
[license-url]: https://aws.amazon.com/apache-2-0/
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
mod s2n_quic;
|
|
||||||
|
|
||||||
pub use self::s2n_quic::*;
|
|
||||||
pub use h3;
|
|
||||||
|
|
@ -1,532 +0,0 @@
|
||||||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use bytes::{Buf, Bytes};
|
|
||||||
use core::task::ready;
|
|
||||||
use h3::quic::{self, Error, StreamId, WriteBuf};
|
|
||||||
use s2n_quic::stream::{BidirectionalStream, ReceiveStream};
|
|
||||||
use s2n_quic_core::varint::VarInt;
|
|
||||||
use std::{
|
|
||||||
convert::TryInto,
|
|
||||||
fmt::{self, Display},
|
|
||||||
sync::Arc,
|
|
||||||
task::{self, Poll},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "tracing")]
|
|
||||||
use tracing::instrument;
|
|
||||||
|
|
||||||
pub struct Connection {
|
|
||||||
conn: s2n_quic::connection::Handle,
|
|
||||||
bidi_acceptor: s2n_quic::connection::BidirectionalStreamAcceptor,
|
|
||||||
recv_acceptor: s2n_quic::connection::ReceiveStreamAcceptor,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Connection {
|
|
||||||
pub fn new(new_conn: s2n_quic::Connection) -> Self {
|
|
||||||
let (handle, acceptor) = new_conn.split();
|
|
||||||
let (bidi, recv) = acceptor.split();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
conn: handle,
|
|
||||||
bidi_acceptor: bidi,
|
|
||||||
recv_acceptor: recv,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ConnectionError(s2n_quic::connection::Error);
|
|
||||||
|
|
||||||
impl std::error::Error for ConnectionError {}
|
|
||||||
|
|
||||||
impl fmt::Display for ConnectionError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
self.0.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for ConnectionError {
|
|
||||||
fn is_timeout(&self) -> bool {
|
|
||||||
matches!(self.0, s2n_quic::connection::Error::IdleTimerExpired { .. })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn err_code(&self) -> Option<u64> {
|
|
||||||
match self.0 {
|
|
||||||
s2n_quic::connection::Error::Application { error, .. } => Some(error.into()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<s2n_quic::connection::Error> for ConnectionError {
|
|
||||||
fn from(e: s2n_quic::connection::Error) -> Self {
|
|
||||||
Self(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> quic::Connection<B> for Connection
|
|
||||||
where
|
|
||||||
B: Buf,
|
|
||||||
{
|
|
||||||
type RecvStream = RecvStream;
|
|
||||||
type OpenStreams = OpenStreams;
|
|
||||||
type AcceptError = ConnectionError;
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
|
|
||||||
fn poll_accept_recv(
|
|
||||||
&mut self,
|
|
||||||
cx: &mut task::Context<'_>,
|
|
||||||
) -> Poll<Result<Option<Self::RecvStream>, Self::AcceptError>> {
|
|
||||||
let recv = match ready!(self.recv_acceptor.poll_accept_receive_stream(cx))? {
|
|
||||||
Some(x) => x,
|
|
||||||
None => return Poll::Ready(Ok(None)),
|
|
||||||
};
|
|
||||||
Poll::Ready(Ok(Some(Self::RecvStream::new(recv))))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
|
|
||||||
fn poll_accept_bidi(
|
|
||||||
&mut self,
|
|
||||||
cx: &mut task::Context<'_>,
|
|
||||||
) -> Poll<Result<Option<Self::BidiStream>, Self::AcceptError>> {
|
|
||||||
let (recv, send) = match ready!(self.bidi_acceptor.poll_accept_bidirectional_stream(cx))? {
|
|
||||||
Some(x) => x.split(),
|
|
||||||
None => return Poll::Ready(Ok(None)),
|
|
||||||
};
|
|
||||||
Poll::Ready(Ok(Some(Self::BidiStream {
|
|
||||||
send: Self::SendStream::new(send),
|
|
||||||
recv: Self::RecvStream::new(recv),
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn opener(&self) -> Self::OpenStreams {
|
|
||||||
OpenStreams {
|
|
||||||
conn: self.conn.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> quic::OpenStreams<B> for Connection
|
|
||||||
where
|
|
||||||
B: Buf,
|
|
||||||
{
|
|
||||||
type BidiStream = BidiStream<B>;
|
|
||||||
type SendStream = SendStream<B>;
|
|
||||||
type OpenError = ConnectionError;
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
|
|
||||||
fn poll_open_bidi(
|
|
||||||
&mut self,
|
|
||||||
cx: &mut task::Context<'_>,
|
|
||||||
) -> Poll<Result<Self::BidiStream, Self::OpenError>> {
|
|
||||||
let stream = ready!(self.conn.poll_open_bidirectional_stream(cx))?;
|
|
||||||
Ok(stream.into()).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
|
|
||||||
fn poll_open_send(
|
|
||||||
&mut self,
|
|
||||||
cx: &mut task::Context<'_>,
|
|
||||||
) -> Poll<Result<Self::SendStream, Self::OpenError>> {
|
|
||||||
let stream = ready!(self.conn.poll_open_send_stream(cx))?;
|
|
||||||
Ok(stream.into()).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
|
|
||||||
fn close(&mut self, code: h3::error::Code, _reason: &[u8]) {
|
|
||||||
self.conn.close(
|
|
||||||
code.value()
|
|
||||||
.try_into()
|
|
||||||
.expect("s2n-quic supports error codes up to 2^62-1"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct OpenStreams {
|
|
||||||
conn: s2n_quic::connection::Handle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> quic::OpenStreams<B> for OpenStreams
|
|
||||||
where
|
|
||||||
B: Buf,
|
|
||||||
{
|
|
||||||
type BidiStream = BidiStream<B>;
|
|
||||||
type SendStream = SendStream<B>;
|
|
||||||
type OpenError = ConnectionError;
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
|
|
||||||
fn poll_open_bidi(
|
|
||||||
&mut self,
|
|
||||||
cx: &mut task::Context<'_>,
|
|
||||||
) -> Poll<Result<Self::BidiStream, Self::OpenError>> {
|
|
||||||
let stream = ready!(self.conn.poll_open_bidirectional_stream(cx))?;
|
|
||||||
Ok(stream.into()).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
|
|
||||||
fn poll_open_send(
|
|
||||||
&mut self,
|
|
||||||
cx: &mut task::Context<'_>,
|
|
||||||
) -> Poll<Result<Self::SendStream, Self::OpenError>> {
|
|
||||||
let stream = ready!(self.conn.poll_open_send_stream(cx))?;
|
|
||||||
Ok(stream.into()).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
|
|
||||||
fn close(&mut self, code: h3::error::Code, _reason: &[u8]) {
|
|
||||||
self.conn.close(
|
|
||||||
code.value()
|
|
||||||
.try_into()
|
|
||||||
.unwrap_or_else(|_| VarInt::MAX.into()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for OpenStreams {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
conn: self.conn.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct BidiStream<B>
|
|
||||||
where
|
|
||||||
B: Buf,
|
|
||||||
{
|
|
||||||
send: SendStream<B>,
|
|
||||||
recv: RecvStream,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> quic::BidiStream<B> for BidiStream<B>
|
|
||||||
where
|
|
||||||
B: Buf,
|
|
||||||
{
|
|
||||||
type SendStream = SendStream<B>;
|
|
||||||
type RecvStream = RecvStream;
|
|
||||||
|
|
||||||
fn split(self) -> (Self::SendStream, Self::RecvStream) {
|
|
||||||
(self.send, self.recv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> quic::RecvStream for BidiStream<B>
|
|
||||||
where
|
|
||||||
B: Buf,
|
|
||||||
{
|
|
||||||
type Buf = Bytes;
|
|
||||||
type Error = ReadError;
|
|
||||||
|
|
||||||
fn poll_data(
|
|
||||||
&mut self,
|
|
||||||
cx: &mut task::Context<'_>,
|
|
||||||
) -> Poll<Result<Option<Self::Buf>, Self::Error>> {
|
|
||||||
self.recv.poll_data(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stop_sending(&mut self, error_code: u64) {
|
|
||||||
self.recv.stop_sending(error_code)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn recv_id(&self) -> StreamId {
|
|
||||||
self.recv.recv_id()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> quic::SendStream<B> for BidiStream<B>
|
|
||||||
where
|
|
||||||
B: Buf,
|
|
||||||
{
|
|
||||||
type Error = SendStreamError;
|
|
||||||
|
|
||||||
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
||||||
self.send.poll_ready(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_finish(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
||||||
self.send.poll_finish(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reset(&mut self, reset_code: u64) {
|
|
||||||
self.send.reset(reset_code)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_data<D: Into<WriteBuf<B>>>(&mut self, data: D) -> Result<(), Self::Error> {
|
|
||||||
self.send.send_data(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_id(&self) -> StreamId {
|
|
||||||
self.send.send_id()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> From<BidirectionalStream> for BidiStream<B>
|
|
||||||
where
|
|
||||||
B: Buf,
|
|
||||||
{
|
|
||||||
fn from(bidi: BidirectionalStream) -> Self {
|
|
||||||
let (recv, send) = bidi.split();
|
|
||||||
BidiStream {
|
|
||||||
send: send.into(),
|
|
||||||
recv: recv.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RecvStream {
|
|
||||||
stream: s2n_quic::stream::ReceiveStream,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RecvStream {
|
|
||||||
fn new(stream: s2n_quic::stream::ReceiveStream) -> Self {
|
|
||||||
Self { stream }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl quic::RecvStream for RecvStream {
|
|
||||||
type Buf = Bytes;
|
|
||||||
type Error = ReadError;
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
|
|
||||||
fn poll_data(
|
|
||||||
&mut self,
|
|
||||||
cx: &mut task::Context<'_>,
|
|
||||||
) -> Poll<Result<Option<Self::Buf>, Self::Error>> {
|
|
||||||
let buf = ready!(self.stream.poll_receive(cx))?;
|
|
||||||
Ok(buf).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
|
|
||||||
fn stop_sending(&mut self, error_code: u64) {
|
|
||||||
let _ = self.stream.stop_sending(
|
|
||||||
s2n_quic::application::Error::new(error_code)
|
|
||||||
.expect("s2n-quic supports error codes up to 2^62-1"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
|
|
||||||
fn recv_id(&self) -> StreamId {
|
|
||||||
self.stream.id().try_into().expect("invalid stream id")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ReceiveStream> for RecvStream {
|
|
||||||
fn from(recv: ReceiveStream) -> Self {
|
|
||||||
RecvStream::new(recv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ReadError(s2n_quic::stream::Error);
|
|
||||||
|
|
||||||
impl std::error::Error for ReadError {}
|
|
||||||
|
|
||||||
impl fmt::Display for ReadError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
self.0.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ReadError> for Arc<dyn Error> {
|
|
||||||
fn from(e: ReadError) -> Self {
|
|
||||||
Arc::new(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<s2n_quic::stream::Error> for ReadError {
|
|
||||||
fn from(e: s2n_quic::stream::Error) -> Self {
|
|
||||||
Self(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for ReadError {
|
|
||||||
fn is_timeout(&self) -> bool {
|
|
||||||
matches!(
|
|
||||||
self.0,
|
|
||||||
s2n_quic::stream::Error::ConnectionError {
|
|
||||||
error: s2n_quic::connection::Error::IdleTimerExpired { .. },
|
|
||||||
..
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn err_code(&self) -> Option<u64> {
|
|
||||||
match self.0 {
|
|
||||||
s2n_quic::stream::Error::ConnectionError {
|
|
||||||
error: s2n_quic::connection::Error::Application { error, .. },
|
|
||||||
..
|
|
||||||
} => Some(error.into()),
|
|
||||||
s2n_quic::stream::Error::StreamReset { error, .. } => Some(error.into()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SendStream<B: Buf> {
|
|
||||||
stream: s2n_quic::stream::SendStream,
|
|
||||||
chunk: Option<Bytes>,
|
|
||||||
buf: Option<WriteBuf<B>>, // TODO: Replace with buf: PhantomData<B>
|
|
||||||
// after https://github.com/hyperium/h3/issues/78 is resolved
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> SendStream<B>
|
|
||||||
where
|
|
||||||
B: Buf,
|
|
||||||
{
|
|
||||||
fn new(stream: s2n_quic::stream::SendStream) -> SendStream<B> {
|
|
||||||
Self {
|
|
||||||
stream,
|
|
||||||
chunk: None,
|
|
||||||
buf: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> quic::SendStream<B> for SendStream<B>
|
|
||||||
where
|
|
||||||
B: Buf,
|
|
||||||
{
|
|
||||||
type Error = SendStreamError;
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
|
|
||||||
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
||||||
loop {
|
|
||||||
// try to flush the current chunk if we have one
|
|
||||||
if let Some(chunk) = self.chunk.as_mut() {
|
|
||||||
ready!(self.stream.poll_send(chunk, cx))?;
|
|
||||||
|
|
||||||
// s2n-quic will take the whole chunk on send, even if it exceeds the limits
|
|
||||||
debug_assert!(chunk.is_empty());
|
|
||||||
self.chunk = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to take the next chunk from the WriteBuf
|
|
||||||
if let Some(ref mut data) = self.buf {
|
|
||||||
let len = data.chunk().len();
|
|
||||||
|
|
||||||
// if the write buf is empty, then clear it and break
|
|
||||||
if len == 0 {
|
|
||||||
self.buf = None;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy the first chunk from WriteBuf and prepare it to flush
|
|
||||||
let chunk = data.copy_to_bytes(len);
|
|
||||||
self.chunk = Some(chunk);
|
|
||||||
|
|
||||||
// loop back around to flush the chunk
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we didn't have either a chunk or WriteBuf, then we're ready
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Poll::Ready(Ok(()))
|
|
||||||
|
|
||||||
// TODO: Replace with following after https://github.com/hyperium/h3/issues/78 is resolved
|
|
||||||
// self.available_bytes = ready!(self.stream.poll_send_ready(cx))?;
|
|
||||||
// Poll::Ready(Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
|
|
||||||
fn send_data<D: Into<WriteBuf<B>>>(&mut self, data: D) -> Result<(), Self::Error> {
|
|
||||||
if self.buf.is_some() {
|
|
||||||
return Err(Self::Error::NotReady);
|
|
||||||
}
|
|
||||||
self.buf = Some(data.into());
|
|
||||||
Ok(())
|
|
||||||
|
|
||||||
// TODO: Replace with following after https://github.com/hyperium/h3/issues/78 is resolved
|
|
||||||
// let mut data = data.into();
|
|
||||||
// while self.available_bytes > 0 && data.has_remaining() {
|
|
||||||
// let len = data.chunk().len();
|
|
||||||
// let chunk = data.copy_to_bytes(len);
|
|
||||||
// self.stream.send_data(chunk)?;
|
|
||||||
// self.available_bytes = self.available_bytes.saturating_sub(len);
|
|
||||||
// }
|
|
||||||
// Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
|
|
||||||
fn poll_finish(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
||||||
// ensure all chunks are flushed to the QUIC stream before finishing
|
|
||||||
ready!(self.poll_ready(cx))?;
|
|
||||||
self.stream.finish()?;
|
|
||||||
Ok(()).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
|
|
||||||
fn reset(&mut self, reset_code: u64) {
|
|
||||||
let _ = self
|
|
||||||
.stream
|
|
||||||
.reset(reset_code.try_into().unwrap_or_else(|_| VarInt::MAX.into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))]
|
|
||||||
fn send_id(&self) -> StreamId {
|
|
||||||
self.stream.id().try_into().expect("invalid stream id")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> From<s2n_quic::stream::SendStream> for SendStream<B>
|
|
||||||
where
|
|
||||||
B: Buf,
|
|
||||||
{
|
|
||||||
fn from(send: s2n_quic::stream::SendStream) -> Self {
|
|
||||||
SendStream::new(send)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum SendStreamError {
|
|
||||||
Write(s2n_quic::stream::Error),
|
|
||||||
NotReady,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for SendStreamError {}
|
|
||||||
|
|
||||||
impl Display for SendStreamError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{self:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<s2n_quic::stream::Error> for SendStreamError {
|
|
||||||
fn from(e: s2n_quic::stream::Error) -> Self {
|
|
||||||
Self::Write(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for SendStreamError {
|
|
||||||
fn is_timeout(&self) -> bool {
|
|
||||||
matches!(
|
|
||||||
self,
|
|
||||||
Self::Write(s2n_quic::stream::Error::ConnectionError {
|
|
||||||
error: s2n_quic::connection::Error::IdleTimerExpired { .. },
|
|
||||||
..
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn err_code(&self) -> Option<u64> {
|
|
||||||
match self {
|
|
||||||
Self::Write(s2n_quic::stream::Error::StreamReset { error, .. }) => {
|
|
||||||
Some((*error).into())
|
|
||||||
}
|
|
||||||
Self::Write(s2n_quic::stream::Error::ConnectionError {
|
|
||||||
error: s2n_quic::connection::Error::Application { error, .. },
|
|
||||||
..
|
|
||||||
}) => Some((*error).into()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<SendStreamError> for Arc<dyn Error> {
|
|
||||||
fn from(e: SendStreamError) -> Self {
|
|
||||||
Arc::new(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue