Merge pull request #66 from junkurihara/develop
Closes #57 Release 0.5.0
This commit is contained in:
commit
19b02949b1
29 changed files with 704 additions and 311 deletions
126
.github/workflows/docker_build_push.yml
vendored
126
.github/workflows/docker_build_push.yml
vendored
|
|
@ -1,17 +1,49 @@
|
||||||
name: Build and Publish Docker
|
name: Build and Publish Docker
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- "develop"
|
||||||
- develop
|
- "main"
|
||||||
|
pull_request:
|
||||||
|
types: [synchronize, opened]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY_IMAGE: jqtype/rpxy
|
GHCR: ghcr.io
|
||||||
|
GHCR_IMAGE_NAME: ${{ github.repository }}
|
||||||
|
DH_REGISTRY_NAME: jqtype/rpxy
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_and_push:
|
build_and_push:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- target: "default"
|
||||||
|
dockerfile: ./docker/Dockerfile
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|
||||||
|
- target: "default-slim"
|
||||||
|
dockerfile: ./docker/Dockerfile-slim
|
||||||
|
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"
|
||||||
|
# Aliases must be used only for release builds
|
||||||
|
aliases: |
|
||||||
|
"slim"
|
||||||
|
|
||||||
|
- target: "s2n"
|
||||||
|
dockerfile: ./docker/Dockerfile
|
||||||
|
build-args: |
|
||||||
|
"CARGO_FEATURES=--no-default-features --features http3-s2n"
|
||||||
|
"ADDITIONAL_DEPS=pkg-config libssl-dev cmake libclang1 gcc g++"
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
tags-suffix: "-s2n"
|
||||||
|
# Aliases must be used only for release builds
|
||||||
|
aliases: |
|
||||||
|
"s2n"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|
@ -19,14 +51,11 @@ jobs:
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: GitHub Environment
|
|
||||||
run: echo "BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v4
|
||||||
with:
|
with:
|
||||||
images: ${{ env.REGISTRY_IMAGE }}
|
images: ${{ env.GHCR }}/${{ env.GHCR_IMAGE_NAME }}
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
|
|
@ -34,70 +63,65 @@ jobs:
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ${{ env.GHCR }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Release build and push
|
- name: Nightly build test on amd64 for pull requests
|
||||||
if: ${{ env.BRANCH == 'main' }}
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
build-args: ${{ matrix.build-args }}
|
||||||
tags: |
|
push: false
|
||||||
${{ env.REGISTRY_IMAGE }}:latest
|
build-contexts: ${{ matrix.build-contexts }}
|
||||||
file: ./docker/Dockerfile
|
file: ${{ matrix.dockerfile }}
|
||||||
cache-from: type=gha
|
cache-from: type=gha,scope=rpxy-nightly-${{ matrix.target }}
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max,scope=rpxy-nightly-${{ matrix.target }}
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
||||||
- name: Release build and push slim
|
- name: Nightly build and push from develop branch
|
||||||
if: ${{ env.BRANCH == 'main' }}
|
if: ${{ (github.ref_name == 'develop') && (github.event_name == 'push') }}
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
|
build-args: ${{ matrix.build-args }}
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
${{ env.REGISTRY_IMAGE }}:slim, ${{ env.REGISTRY_IMAGE }}:latest-slim
|
${{ env.GHCR }}/${{ env.GHCR_IMAGE_NAME }}:nightly${{ matrix.tags-suffix }}
|
||||||
build-contexts: |
|
${{ env.DH_REGISTRY_NAME }}:nightly${{ matrix.tags-suffix }}
|
||||||
messense/rust-musl-cross:amd64-musl=docker-image://messense/rust-musl-cross:x86_64-musl
|
build-contexts: ${{ matrix.build-contexts }}
|
||||||
messense/rust-musl-cross:arm64-musl=docker-image://messense/rust-musl-cross:aarch64-musl
|
file: ${{ matrix.dockerfile }}
|
||||||
file: ./docker/Dockerfile.slim
|
cache-from: type=gha,scope=rpxy-nightly-${{ matrix.target }}
|
||||||
cache-from: type=gha
|
cache-to: type=gha,mode=max,scope=rpxy-nightly-${{ matrix.target }}
|
||||||
cache-to: type=gha,mode=max
|
platforms: ${{ matrix.platforms }}
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
||||||
- name: Nightly build and push
|
- name: Release build and push from main branch
|
||||||
if: ${{ env.BRANCH == 'develop' }}
|
if: ${{ (github.ref_name == 'main') && (github.event_name == 'push') }}
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
|
build-args: ${{ matrix.build-args }}
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
${{ env.REGISTRY_IMAGE }}:nightly
|
${{ env.GHCR }}/${{ env.GHCR_IMAGE_NAME }}:latest${{ matrix.tags-suffix }}
|
||||||
file: ./docker/Dockerfile
|
${{ env.DH_REGISTRY_NAME }}:latest${{ matrix.tags-suffix }}
|
||||||
cache-from: type=gha
|
${{ env.GHCR }}/${{ env.GHCR_IMAGE_NAME }}:${{ matrix.aliases }}
|
||||||
cache-to: type=gha,mode=max
|
${{ env.DH_REGISTRY_NAME }}:${{ matrix.aliases }}
|
||||||
platforms: linux/amd64,linux/arm64
|
build-contexts: ${{ matrix.build-contexts }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
file: ${{ matrix.dockerfile }}
|
||||||
|
cache-from: type=gha,scope=rpxy-latest-${{ matrix.target }}
|
||||||
- name: Nightly build and push slim
|
cache-to: type=gha,mode=max,scope=rpxy-latest-${{ matrix.target }}
|
||||||
if: ${{ env.BRANCH == 'develop' }}
|
platforms: ${{ matrix.platforms }}
|
||||||
uses: docker/build-push-action@v4
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
${{ env.REGISTRY_IMAGE }}:nightly-slim
|
|
||||||
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
|
|
||||||
file: ./docker/Dockerfile.slim
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
|
||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -4,3 +4,6 @@
|
||||||
[submodule "quinn"]
|
[submodule "quinn"]
|
||||||
path = quinn
|
path = quinn
|
||||||
url = git@github.com:junkurihara/quinn.git
|
url = git@github.com:junkurihara/quinn.git
|
||||||
|
[submodule "s2n-quic"]
|
||||||
|
path = s2n-quic
|
||||||
|
url = git@github.com:junkurihara/s2n-quic.git
|
||||||
|
|
|
||||||
13
CHANGELOG.md
13
CHANGELOG.md
|
|
@ -1,6 +1,17 @@
|
||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
## 0.4.0 (unreleased)
|
## 0.6.0 (unreleased)
|
||||||
|
|
||||||
|
## 0.5.0
|
||||||
|
|
||||||
|
### Improvement
|
||||||
|
|
||||||
|
- Feat: `s2n-quic` with `s2n-quic-h3` is supported as QUIC and HTTP/3 library in addition to `quinn` with `h3-quinn`, related to #57.
|
||||||
|
- Feat: Publish dockerfile for `rpxy` with `s2n-quic` on both `amd64` and `arm64`.
|
||||||
|
- Feat: Start to publish docker images on `ghcr.io`
|
||||||
|
- Refactor: logs of minor improvements
|
||||||
|
|
||||||
|
## 0.4.0
|
||||||
|
|
||||||
### Improvement
|
### Improvement
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
|
|
||||||
members = ["rpxy-bin", "rpxy-lib"]
|
members = ["rpxy-bin", "rpxy-lib"]
|
||||||
exclude = ["quinn", "h3-quinn", "h3"]
|
exclude = ["quinn", "h3-quinn", "h3", "s2n-quic"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|
|
||||||
15
README.md
15
README.md
|
|
@ -12,7 +12,9 @@
|
||||||
|
|
||||||
`rpxy` [ahr-pik-see] is an implementation of simple and lightweight reverse-proxy with some additional features. The implementation is based on [`hyper`](https://github.com/hyperium/hyper), [`rustls`](https://github.com/rustls/rustls) and [`tokio`](https://github.com/tokio-rs/tokio), i.e., written in pure Rust. Our `rpxy` routes multiple host names to appropriate backend application servers while serving TLS connections.
|
`rpxy` [ahr-pik-see] is an implementation of simple and lightweight reverse-proxy with some additional features. The implementation is based on [`hyper`](https://github.com/hyperium/hyper), [`rustls`](https://github.com/rustls/rustls) and [`tokio`](https://github.com/tokio-rs/tokio), i.e., written in pure Rust. Our `rpxy` routes multiple host names to appropriate backend application servers while serving TLS connections.
|
||||||
|
|
||||||
As 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) and [`hyperium/h3`](https://github.com/hyperium/h3).
|
As 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]
|
||||||
|
|
||||||
|
[^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.
|
||||||
|
|
||||||
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).
|
||||||
|
|
||||||
|
|
@ -27,11 +29,14 @@ You can build an executable binary yourself by checking out this Git repository.
|
||||||
% git clone https://github.com/junkurihara/rust-rpxy
|
% git clone https://github.com/junkurihara/rust-rpxy
|
||||||
% cd rust-rpxy
|
% cd rust-rpxy
|
||||||
|
|
||||||
# Update submodule hyperium/h3
|
# Update submodules
|
||||||
% git submodule update --init
|
% git submodule update --init
|
||||||
|
|
||||||
# Build
|
# Build (default: QUIC and HTTP/3 is enabled using `quinn`)
|
||||||
% cargo build --release
|
% cargo build --release
|
||||||
|
|
||||||
|
# If you want to use `s2n-quic`, build as follows. You may need several additional dependencies.
|
||||||
|
% cargo build --no-default-features --features http3-s2n --release
|
||||||
```
|
```
|
||||||
|
|
||||||
Then you have an executive binary `rust-rpxy/target/release/rpxy`.
|
Then you have an executive binary `rust-rpxy/target/release/rpxy`.
|
||||||
|
|
@ -231,7 +236,9 @@ Since it is currently a work-in-progress project, we are frequently adding new o
|
||||||
|
|
||||||
## Using Docker Image
|
## Using Docker Image
|
||||||
|
|
||||||
You can also use [docker image](https://hub.docker.com/r/jqtype/rpxy) instead of directly executing the binary. There are only several docker-specific environment variables.
|
You can also use `docker` image hosted on [Docker Hub](https://hub.docker.com/r/jqtype/rpxy) and [GitHub Container Registry](https://github.com/junkurihara/rust-rpxy/pkgs/container/rust-rpxy) instead of directly executing the binary. See [`./docker/README.md`](./docker/README.md) for the differences on image tags.
|
||||||
|
|
||||||
|
There are only several docker-specific environment variables.
|
||||||
|
|
||||||
- `HOST_USER` (default: `user`): User name executing `rpxy` inside the container.
|
- `HOST_USER` (default: `user`): User name executing `rpxy` inside the container.
|
||||||
- `HOST_UID` (default: `900`): `UID` of `HOST_USER`.
|
- `HOST_UID` (default: `900`): `UID` of `HOST_USER`.
|
||||||
|
|
|
||||||
18
TODO.md
18
TODO.md
|
|
@ -1,5 +1,6 @@
|
||||||
# TODO List
|
# TODO List
|
||||||
|
|
||||||
|
- [Try in v0.6.0] **Cache option for the response with `Cache-Control: public` header directive ([#55](https://github.com/junkurihara/rust-rpxy/issues/55))**
|
||||||
- Improvement of path matcher
|
- Improvement of path matcher
|
||||||
- More flexible option for rewriting path
|
- More flexible option for rewriting path
|
||||||
- Refactoring
|
- Refactoring
|
||||||
|
|
@ -10,14 +11,6 @@
|
||||||
- upstream/upstream group: information on targeted destinations for each set of (a domain + a path)
|
- upstream/upstream group: information on targeted destinations for each set of (a domain + a path)
|
||||||
- load-balance: load balancing mod for a domain + path
|
- load-balance: load balancing mod for a domain + path
|
||||||
|
|
||||||
- Done in v0.4.0:
|
|
||||||
~~Split `rpxy` source codes into `rpxy-lib` and `rpxy-bin` to make the core part (reverse proxy) isolated from the misc part like toml file loader. This is in order to make the configuration-related part more flexible (related to [#33](https://github.com/junkurihara/rust-rpxy/issues/33))~~
|
|
||||||
|
|
||||||
- Cache option for the response with `Cache-Control: public` header directive ([#55](https://github.com/junkurihara/rust-rpxy/issues/55))
|
|
||||||
- Consideration on migrating from `quinn` and `h3-quinn` to other QUIC implementations ([#57](https://github.com/junkurihara/rust-rpxy/issues/57))
|
|
||||||
- Done in v0.4.0:
|
|
||||||
~~Benchmark with other reverse proxy implementations like Sozu ([#58](https://github.com/junkurihara/rust-rpxy/issues/58)) Currently, Sozu can work only on `amd64` format due to its HTTP message parser limitation... Since the main developer have only `arm64` (Apple M1) laptops, so we should do that on VPS?~~
|
|
||||||
|
|
||||||
- Unit tests
|
- Unit tests
|
||||||
- Options to serve custom http_error page.
|
- Options to serve custom http_error page.
|
||||||
- Prometheus metrics
|
- Prometheus metrics
|
||||||
|
|
@ -30,4 +23,13 @@
|
||||||
- Make the session-persistance option for load-balancing sophisticated. (mostly done in v0.3.0)
|
- Make the session-persistance option for load-balancing sophisticated. (mostly done in v0.3.0)
|
||||||
- add option for sticky cookie name
|
- add option for sticky cookie name
|
||||||
- add option for sticky cookie duration
|
- add option for sticky cookie duration
|
||||||
|
|
||||||
|
- Done in v0.5.0 ~~**Use `gchr.io`**~~
|
||||||
|
- Done in v0.5.0:
|
||||||
|
~~Consideration on migrating from `quinn` and `h3-quinn` to other QUIC implementations ([#57](https://github.com/junkurihara/rust-rpxy/issues/57))~~
|
||||||
|
- Done in v0.4.0:
|
||||||
|
~~Benchmark with other reverse proxy implementations like Sozu ([#58](https://github.com/junkurihara/rust-rpxy/issues/58)) Currently, Sozu can work only on `amd64` format due to its HTTP message parser limitation... Since the main developer have only `arm64` (Apple M1) laptops, so we should do that on VPS?~~
|
||||||
|
- Done in v0.4.0:
|
||||||
|
~~Split `rpxy` source codes into `rpxy-lib` and `rpxy-bin` to make the core part (reverse proxy) isolated from the misc part like toml file loader. This is in order to make the configuration-related part more flexible (related to [#33](https://github.com/junkurihara/rust-rpxy/issues/33))~~
|
||||||
|
|
||||||
- etc.
|
- etc.
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,17 @@ FROM --platform=$BUILDPLATFORM base AS builder
|
||||||
|
|
||||||
ENV CFLAGS=-Ofast
|
ENV CFLAGS=-Ofast
|
||||||
ENV BUILD_DEPS curl make ca-certificates build-essential
|
ENV BUILD_DEPS curl make ca-certificates build-essential
|
||||||
|
ENV TARGET_SUFFIX=unknown-linux-gnu
|
||||||
|
|
||||||
WORKDIR /tmp
|
WORKDIR /tmp
|
||||||
|
|
||||||
COPY . /tmp/
|
COPY . /tmp/
|
||||||
|
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
ARG CARGO_FEATURES
|
||||||
|
ENV CARGO_FEATURES ${CARGO_FEATURES}
|
||||||
|
ARG ADDITIONAL_DEPS
|
||||||
|
ENV ADDITIONAL_DEPS ${ADDITIONAL_DEPS}
|
||||||
|
|
||||||
RUN if [ $TARGETARCH = "amd64" ]; then \
|
RUN if [ $TARGETARCH = "amd64" ]; then \
|
||||||
echo "x86_64" > /arch; \
|
echo "x86_64" > /arch; \
|
||||||
|
|
@ -29,15 +34,15 @@ ENV RUSTFLAGS "-C link-arg=-s"
|
||||||
|
|
||||||
RUN update-ca-certificates 2> /dev/null || true
|
RUN update-ca-certificates 2> /dev/null || true
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -qy --no-install-recommends $BUILD_DEPS && \
|
RUN apt-get update && apt-get install -qy --no-install-recommends $BUILD_DEPS ${ADDITIONAL_DEPS} && \
|
||||||
curl -sSf https://sh.rustup.rs | bash -s -- -y --default-toolchain stable && \
|
curl -sSf https://sh.rustup.rs | bash -s -- -y --default-toolchain stable && \
|
||||||
export PATH="$HOME/.cargo/bin:$PATH" && \
|
export PATH="$HOME/.cargo/bin:$PATH" && \
|
||||||
echo "Install toolchain" && \
|
echo "Install toolchain" && \
|
||||||
rustup target add $(cat /arch)-unknown-linux-gnu &&\
|
rustup target add $(cat /arch)-${TARGET_SUFFIX} && \
|
||||||
echo "Building rpxy from source" && \
|
echo "Building rpxy from source" && \
|
||||||
cargo build --release --target=$(cat /arch)-unknown-linux-gnu && \
|
cargo build --release --target=$(cat /arch)-${TARGET_SUFFIX} ${CARGO_FEATURES} && \
|
||||||
strip --strip-all /tmp/target/$(cat /arch)-unknown-linux-gnu/release/rpxy &&\
|
strip --strip-all /tmp/target/$(cat /arch)-${TARGET_SUFFIX}/release/rpxy &&\
|
||||||
cp /tmp/target/$(cat /arch)-unknown-linux-gnu/release/rpxy /tmp/target/release/rpxy
|
cp /tmp/target/$(cat /arch)-${TARGET_SUFFIX}/release/rpxy /tmp/target/release/rpxy
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
FROM --platform=$TARGETPLATFORM base AS runner
|
FROM --platform=$TARGETPLATFORM base AS runner
|
||||||
|
|
|
||||||
19
docker/README.md
Normal file
19
docker/README.md
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Docker Images of `rpxy`
|
||||||
|
|
||||||
|
The `rpxy` docker images are hosted both on [Docker Hub](https://hub.docker.com/r/jqtype/rpxy) and [GitHub Container Registry](https://github.com/junkurihara/rust-rpxy/pkgs/container/rust-rpxy). Differences among tags are summarized as follows.
|
||||||
|
|
||||||
|
## Latest Builds
|
||||||
|
|
||||||
|
- `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-s2n`, `s2n`: Built from the `main` branch with the `http3-s2n` feature, running on Ubuntu.
|
||||||
|
|
||||||
|
## Nightly Builds
|
||||||
|
|
||||||
|
- `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-s2n`: Built from the `develop` branch with the `http3-s2n` feature, running on Ubuntu.
|
||||||
|
|
||||||
|
## Caveats
|
||||||
|
|
||||||
|
Due to some compile errors of `s2n-quic` subpackages with `musl`, `nightly-s2n-slim` or `latest-s2n-slim` are not yet provided.
|
||||||
34
docker/docker-compose-slim.yml
Normal file
34
docker/docker-compose-slim.yml
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
rpxy-rp:
|
||||||
|
image: jqtype/rpxy:slim # ghcr.io/junkurihara/rust-rpxy:slim also works
|
||||||
|
container_name: rpxy
|
||||||
|
init: true
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:8080:8080/tcp
|
||||||
|
- 127.0.0.1:8443:8443/udp
|
||||||
|
- 127.0.0.1:8443:8443/tcp
|
||||||
|
# build: # Uncomment if you build yourself
|
||||||
|
# context: ../
|
||||||
|
# additional_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
|
||||||
|
# dockerfile: ./docker/Dockerfile-slim # based on alpine and build x86_64-unknown-linux-musl
|
||||||
|
# platforms: # Choose your platforms
|
||||||
|
# - "linux/amd64"
|
||||||
|
# # - "linux/arm64"
|
||||||
|
environment:
|
||||||
|
- LOG_LEVEL=debug
|
||||||
|
- LOG_TO_FILE=true
|
||||||
|
- HOST_USER=jun
|
||||||
|
- HOST_UID=501
|
||||||
|
- HOST_GID=501
|
||||||
|
# - WATCH=true
|
||||||
|
tty: false
|
||||||
|
privileged: true
|
||||||
|
volumes:
|
||||||
|
- ./log:/rpxy/log
|
||||||
|
- ../example-certs/server.crt:/certs/server.crt:ro
|
||||||
|
- ../example-certs/server.key:/certs/server.key:ro
|
||||||
|
- ../config-example.toml:/etc/rpxy.toml:ro
|
||||||
|
|
@ -1,19 +1,23 @@
|
||||||
version: "3"
|
version: "3"
|
||||||
services:
|
services:
|
||||||
rpxy-rp:
|
rpxy-rp:
|
||||||
image: jqtype/rpxy
|
image: jqtype/rpxy:latest # ghcr.io/junkurihara/rust-rpxy:latest also works
|
||||||
container_name: rpxy
|
container_name: rpxy
|
||||||
init: true
|
init: true
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- 127.0.0.1:8080:8080
|
- 127.0.0.1:8080:8080/tcp
|
||||||
- 127.0.0.1:8443:8443
|
- 127.0.0.1:8443:8443/udp
|
||||||
build:
|
- 127.0.0.1:8443:8443/tcp
|
||||||
context: ../
|
# build: # Uncomment if you build yourself
|
||||||
dockerfile: ./docker/Dockerfile
|
# context: ../
|
||||||
platforms: # Choose your platforms
|
# args: # Uncomment when build quic-s2n version
|
||||||
- "linux/amd64"
|
# - "CARGO_FEATURES=--no-default-features --features http3-s2n"
|
||||||
# - "linux/arm64"
|
# - "ADDITIONAL_DEPS=pkg-config libssl-dev cmake libclang1 gcc g++"
|
||||||
|
# dockerfile: ./docker/Dockerfile # based on ubuntu 22.04 and build x86_64-unknown-linux-gnu
|
||||||
|
# platforms: # Choose your platforms
|
||||||
|
# - "linux/amd64"
|
||||||
|
# # - "linux/arm64"
|
||||||
environment:
|
environment:
|
||||||
- LOG_LEVEL=debug
|
- LOG_LEVEL=debug
|
||||||
- LOG_TO_FILE=true
|
- LOG_TO_FILE=true
|
||||||
|
|
|
||||||
2
quinn
2
quinn
|
|
@ -1 +1 @@
|
||||||
Subproject commit 532ba7d80405ad083fd05546fa71becbe5eff1a4
|
Subproject commit 70e14b5c26b45ee1e3d5dd64b2a184e2d6376880
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "rpxy"
|
name = "rpxy"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
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"
|
||||||
|
|
@ -12,15 +12,18 @@ publish = false
|
||||||
# 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"]
|
default = ["http3-quinn"]
|
||||||
http3 = []
|
http3-quinn = ["rpxy-lib/http3-quinn"]
|
||||||
|
http3-s2n = ["rpxy-lib/http3-s2n"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rpxy-lib = { path = "../rpxy-lib/", features = ["http3", "sticky-cookie"] }
|
rpxy-lib = { path = "../rpxy-lib/", default-features = false, features = [
|
||||||
|
"sticky-cookie",
|
||||||
|
] }
|
||||||
|
|
||||||
anyhow = "1.0.72"
|
anyhow = "1.0.72"
|
||||||
rustc-hash = "1.1.0"
|
rustc-hash = "1.1.0"
|
||||||
serde = { version = "1.0.178", default-features = false, features = ["derive"] }
|
serde = { version = "1.0.180", default-features = false, features = ["derive"] }
|
||||||
derive_builder = "0.12.0"
|
derive_builder = "0.12.0"
|
||||||
tokio = { version = "1.29.1", default-features = false, features = [
|
tokio = { version = "1.29.1", default-features = false, features = [
|
||||||
"net",
|
"net",
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ pub struct ConfigToml {
|
||||||
pub experimental: Option<Experimental>,
|
pub experimental: Option<Experimental>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
#[derive(Deserialize, Debug, Default, PartialEq, Eq, Clone)]
|
#[derive(Deserialize, Debug, Default, PartialEq, Eq, Clone)]
|
||||||
pub struct Http3Option {
|
pub struct Http3Option {
|
||||||
pub alt_svc_max_age: Option<u32>,
|
pub alt_svc_max_age: Option<u32>,
|
||||||
|
|
@ -34,7 +34,7 @@ pub struct Http3Option {
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Default, PartialEq, Eq, Clone)]
|
#[derive(Deserialize, Debug, Default, PartialEq, Eq, Clone)]
|
||||||
pub struct Experimental {
|
pub struct Experimental {
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
pub h3: Option<Http3Option>,
|
pub h3: Option<Http3Option>,
|
||||||
pub ignore_sni_consistency: Option<bool>,
|
pub ignore_sni_consistency: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
@ -128,7 +128,7 @@ impl TryInto<ProxyConfig> for &ConfigToml {
|
||||||
|
|
||||||
// experimental
|
// experimental
|
||||||
if let Some(exp) = &self.experimental {
|
if let Some(exp) = &self.experimental {
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
{
|
{
|
||||||
if let Some(h3option) = &exp.h3 {
|
if let Some(h3option) = &exp.h3 {
|
||||||
proxy_config.http3 = true;
|
proxy_config.http3 = true;
|
||||||
|
|
@ -142,10 +142,10 @@ impl TryInto<ProxyConfig> for &ConfigToml {
|
||||||
proxy_config.h3_max_concurrent_connections = x;
|
proxy_config.h3_max_concurrent_connections = x;
|
||||||
}
|
}
|
||||||
if let Some(x) = h3option.max_concurrent_bidistream {
|
if let Some(x) = h3option.max_concurrent_bidistream {
|
||||||
proxy_config.h3_max_concurrent_bidistream = x.into();
|
proxy_config.h3_max_concurrent_bidistream = x;
|
||||||
}
|
}
|
||||||
if let Some(x) = h3option.max_concurrent_unistream {
|
if let Some(x) = h3option.max_concurrent_unistream {
|
||||||
proxy_config.h3_max_concurrent_unistream = x.into();
|
proxy_config.h3_max_concurrent_unistream = x;
|
||||||
}
|
}
|
||||||
if let Some(x) = h3option.max_idle_timeout {
|
if let Some(x) = h3option.max_idle_timeout {
|
||||||
if x == 0u64 {
|
if x == 0u64 {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@ use crate::{
|
||||||
use hot_reload::{ReloaderReceiver, ReloaderService};
|
use hot_reload::{ReloaderReceiver, ReloaderService};
|
||||||
use rpxy_lib::entrypoint;
|
use rpxy_lib::entrypoint;
|
||||||
|
|
||||||
|
#[cfg(all(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
|
compile_error!("feature \"http3-quinn\" and feature \"http3-s2n\" cannot be enabled at the same time");
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
init_logger();
|
init_logger();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "rpxy-lib"
|
name = "rpxy-lib"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
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"
|
||||||
|
|
@ -12,8 +12,9 @@ publish = false
|
||||||
# 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", "sticky-cookie"]
|
default = ["http3-quinn", "sticky-cookie"]
|
||||||
http3 = ["quinn", "h3", "h3-quinn"]
|
http3-quinn = ["quinn", "h3", "h3-quinn", "socket2"]
|
||||||
|
http3-s2n = ["h3", "s2n-quic", "s2n-quic-rustls", "s2n-quic-h3"]
|
||||||
sticky-cookie = ["base64", "sha2", "chrono"]
|
sticky-cookie = ["base64", "sha2", "chrono"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
@ -63,8 +64,13 @@ quinn = { path = "../quinn/quinn", optional = true } # Tentative to support rust
|
||||||
h3 = { path = "../h3/h3/", optional = true }
|
h3 = { path = "../h3/h3/", optional = true }
|
||||||
# h3-quinn = { path = "./h3/h3-quinn/", optional = true }
|
# h3-quinn = { path = "./h3/h3-quinn/", optional = true }
|
||||||
h3-quinn = { path = "../h3-quinn/", optional = true } # Tentative to support rustls-0.21
|
h3-quinn = { path = "../h3-quinn/", optional = true } # Tentative to support rustls-0.21
|
||||||
# for UDP socket wit SO_REUSEADDR
|
# for UDP socket wit SO_REUSEADDR when h3 with quinn
|
||||||
socket2 = { version = "0.5.3", features = ["all"] }
|
socket2 = { version = "0.5.3", features = ["all"], optional = true }
|
||||||
|
s2n-quic = { path = "../s2n-quic/quic/s2n-quic/", default-features = false, features = [
|
||||||
|
"provider-tls-rustls",
|
||||||
|
], optional = true }
|
||||||
|
s2n-quic-h3 = { path = "../s2n-quic/quic/s2n-quic-h3/", optional = true }
|
||||||
|
s2n-quic-rustls = { path = "../s2n-quic/quic/s2n-quic-rustls/", optional = true }
|
||||||
|
|
||||||
# cookie handling for sticky cookie
|
# cookie handling for sticky cookie
|
||||||
chrono = { version = "0.4.26", default-features = false, features = [
|
chrono = { version = "0.4.26", default-features = false, features = [
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ pub const LOAD_CERTS_ONLY_WHEN_UPDATED: bool = true;
|
||||||
// pub const H3_REQUEST_BUF_SIZE: usize = 65_536; // 64KB // handled by quinn
|
// pub const H3_REQUEST_BUF_SIZE: usize = 65_536; // 64KB // handled by quinn
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
pub mod H3 {
|
pub mod H3 {
|
||||||
pub const ALT_SVC_MAX_AGE: u32 = 3600;
|
pub const ALT_SVC_MAX_AGE: u32 = 3600;
|
||||||
pub const REQUEST_MAX_BODY_SIZE: usize = 268_435_456; // 256MB
|
pub const REQUEST_MAX_BODY_SIZE: usize = 268_435_456; // 256MB
|
||||||
|
|
|
||||||
|
|
@ -37,14 +37,22 @@ pub enum RpxyError {
|
||||||
|
|
||||||
// #[error("Toml Deserialization Error")]
|
// #[error("Toml Deserialization Error")]
|
||||||
// TomlDe(#[from] toml::de::Error),
|
// TomlDe(#[from] toml::de::Error),
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(feature = "http3-quinn")]
|
||||||
#[error("Quic Connection Error")]
|
#[error("Quic Connection Error")]
|
||||||
QuicConn(#[from] quinn::ConnectionError),
|
QuicConn(#[from] quinn::ConnectionError),
|
||||||
|
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(feature = "http3-s2n")]
|
||||||
|
#[error("Quic Connection Error [s2n-quic]")]
|
||||||
|
QUicConn(#[from] s2n_quic::connection::Error),
|
||||||
|
|
||||||
|
#[cfg(feature = "http3-quinn")]
|
||||||
#[error("H3 Error")]
|
#[error("H3 Error")]
|
||||||
H3(#[from] h3::Error),
|
H3(#[from] h3::Error),
|
||||||
|
|
||||||
|
#[cfg(feature = "http3-s2n")]
|
||||||
|
#[error("H3 Error [s2n-quic]")]
|
||||||
|
H3(#[from] s2n_quic_h3::h3::Error),
|
||||||
|
|
||||||
#[error("rustls Connection Error")]
|
#[error("rustls Connection Error")]
|
||||||
Rustls(#[from] rustls::Error),
|
Rustls(#[from] rustls::Error),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,19 +53,19 @@ pub struct ProxyConfig {
|
||||||
// experimentals
|
// experimentals
|
||||||
pub sni_consistency: bool, // Handler
|
pub sni_consistency: bool, // Handler
|
||||||
// All need to make packet acceptor
|
// All need to make packet acceptor
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
pub http3: bool,
|
pub http3: bool,
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
pub h3_alt_svc_max_age: u32,
|
pub h3_alt_svc_max_age: u32,
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
pub h3_request_max_body_size: usize,
|
pub h3_request_max_body_size: usize,
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
pub h3_max_concurrent_bidistream: quinn::VarInt,
|
pub h3_max_concurrent_bidistream: u32,
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
pub h3_max_concurrent_unistream: quinn::VarInt,
|
pub h3_max_concurrent_unistream: u32,
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
pub h3_max_concurrent_connections: u32,
|
pub h3_max_concurrent_connections: u32,
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
pub h3_max_idle_timeout: Option<Duration>,
|
pub h3_max_idle_timeout: Option<Duration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,19 +87,19 @@ impl Default for ProxyConfig {
|
||||||
|
|
||||||
sni_consistency: true,
|
sni_consistency: true,
|
||||||
|
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
http3: false,
|
http3: false,
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
h3_alt_svc_max_age: H3::ALT_SVC_MAX_AGE,
|
h3_alt_svc_max_age: H3::ALT_SVC_MAX_AGE,
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
h3_request_max_body_size: H3::REQUEST_MAX_BODY_SIZE,
|
h3_request_max_body_size: H3::REQUEST_MAX_BODY_SIZE,
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
h3_max_concurrent_connections: H3::MAX_CONCURRENT_CONNECTIONS,
|
h3_max_concurrent_connections: H3::MAX_CONCURRENT_CONNECTIONS,
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
h3_max_concurrent_bidistream: H3::MAX_CONCURRENT_BIDISTREAM.into(),
|
h3_max_concurrent_bidistream: H3::MAX_CONCURRENT_BIDISTREAM,
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
h3_max_concurrent_unistream: H3::MAX_CONCURRENT_UNISTREAM.into(),
|
h3_max_concurrent_unistream: H3::MAX_CONCURRENT_UNISTREAM,
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
h3_max_idle_timeout: Some(Duration::from_secs(H3::MAX_IDLE_TIMEOUT)),
|
h3_max_idle_timeout: Some(Duration::from_secs(H3::MAX_IDLE_TIMEOUT)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,7 @@ where
|
||||||
remove_hop_header(headers);
|
remove_hop_header(headers);
|
||||||
add_header_entry_overwrite_if_exist(headers, "server", env!("CARGO_PKG_NAME"))?;
|
add_header_entry_overwrite_if_exist(headers, "server", env!("CARGO_PKG_NAME"))?;
|
||||||
|
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
{
|
{
|
||||||
// Manipulate ALT_SVC allowing h3 in response message only when mutual TLS is not enabled
|
// Manipulate ALT_SVC allowing h3 in response message only when mutual TLS is not enabled
|
||||||
// TODO: This is a workaround for avoiding a client authentication in HTTP/3
|
// TODO: This is a workaround for avoiding a client authentication in HTTP/3
|
||||||
|
|
@ -235,7 +235,7 @@ where
|
||||||
headers.remove(header::ALT_SVC.as_str());
|
headers.remove(header::ALT_SVC.as_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "http3"))]
|
#[cfg(not(any(feature = "http3-quinn", feature = "http3-s2n")))]
|
||||||
{
|
{
|
||||||
if let Some(port) = self.globals.proxy_config.https_port {
|
if let Some(port) = self.globals.proxy_config.https_port {
|
||||||
headers.remove(header::ALT_SVC.as_str());
|
headers.remove(header::ALT_SVC.as_str());
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,9 @@ pub mod reexports {
|
||||||
pub use rustls::{Certificate, PrivateKey};
|
pub use rustls::{Certificate, PrivateKey};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
|
compile_error!("feature \"http3-quinn\" and feature \"http3-s2n\" cannot be enabled at the same time");
|
||||||
|
|
||||||
/// Entrypoint that creates and spawns tasks of reverse proxy services
|
/// Entrypoint that creates and spawns tasks of reverse proxy services
|
||||||
pub async fn entrypoint<T>(
|
pub async fn entrypoint<T>(
|
||||||
proxy_config: &ProxyConfig,
|
proxy_config: &ProxyConfig,
|
||||||
|
|
@ -44,6 +47,7 @@ where
|
||||||
if proxy_config.https_port.is_some() {
|
if proxy_config.https_port.is_some() {
|
||||||
info!("Listen port: {} (for TLS)", proxy_config.https_port.unwrap());
|
info!("Listen port: {} (for TLS)", proxy_config.https_port.unwrap());
|
||||||
}
|
}
|
||||||
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
if proxy_config.http3 {
|
if proxy_config.http3 {
|
||||||
info!("Experimental HTTP/3.0 is enabled. Note it is still very unstable.");
|
info!("Experimental HTTP/3.0 is enabled. Note it is still very unstable.");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,10 @@ where
|
||||||
pub type SniServerCryptoMap = HashMap<ServerNameBytesExp, Arc<ServerConfig>>;
|
pub type SniServerCryptoMap = HashMap<ServerNameBytesExp, Arc<ServerConfig>>;
|
||||||
pub struct ServerCrypto {
|
pub struct ServerCrypto {
|
||||||
// For Quic/HTTP3, only servers with no client authentication
|
// For Quic/HTTP3, only servers with no client authentication
|
||||||
|
#[cfg(feature = "http3-quinn")]
|
||||||
pub inner_global_no_client_auth: Arc<ServerConfig>,
|
pub inner_global_no_client_auth: Arc<ServerConfig>,
|
||||||
|
#[cfg(feature = "http3-s2n")]
|
||||||
|
pub inner_global_no_client_auth: s2n_quic_rustls::Server,
|
||||||
// For TLS over TCP/HTTP2 and 1.1, map of SNI to server_crypto for all given servers
|
// For TLS over TCP/HTTP2 and 1.1, map of SNI to server_crypto for all given servers
|
||||||
pub inner_local_map: Arc<SniServerCryptoMap>,
|
pub inner_local_map: Arc<SniServerCryptoMap>,
|
||||||
}
|
}
|
||||||
|
|
@ -68,7 +71,22 @@ impl TryInto<Arc<ServerCrypto>> for &ServerCryptoBase {
|
||||||
type Error = anyhow::Error;
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
fn try_into(self) -> Result<Arc<ServerCrypto>, Self::Error> {
|
fn try_into(self) -> Result<Arc<ServerCrypto>, Self::Error> {
|
||||||
let mut resolver_global = ResolvesServerCertUsingSni::new();
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
|
let server_crypto_global = self.build_server_crypto_global()?;
|
||||||
|
let server_crypto_local_map: SniServerCryptoMap = self.build_server_crypto_local_map()?;
|
||||||
|
|
||||||
|
Ok(Arc::new(ServerCrypto {
|
||||||
|
#[cfg(feature = "http3-quinn")]
|
||||||
|
inner_global_no_client_auth: Arc::new(server_crypto_global),
|
||||||
|
#[cfg(feature = "http3-s2n")]
|
||||||
|
inner_global_no_client_auth: server_crypto_global,
|
||||||
|
inner_local_map: Arc::new(server_crypto_local_map),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerCryptoBase {
|
||||||
|
fn build_server_crypto_local_map(&self) -> Result<SniServerCryptoMap, ReloaderError<ServerCryptoBase>> {
|
||||||
let mut server_crypto_local_map: SniServerCryptoMap = HashMap::default();
|
let mut server_crypto_local_map: SniServerCryptoMap = HashMap::default();
|
||||||
|
|
||||||
for (server_name_bytes_exp, certs_and_keys) in self.inner.iter() {
|
for (server_name_bytes_exp, certs_and_keys) in self.inner.iter() {
|
||||||
|
|
@ -93,16 +111,7 @@ impl TryInto<Arc<ServerCrypto>> for &ServerCryptoBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
// add client certificate if specified
|
// add client certificate if specified
|
||||||
if certs_and_keys.client_ca_certs.is_none() {
|
if certs_and_keys.client_ca_certs.is_some() {
|
||||||
// aggregated server config for no client auth server for http3
|
|
||||||
if let Err(e) = resolver_global.add(server_name.as_str(), certified_key) {
|
|
||||||
error!(
|
|
||||||
"{}: Failed to read some certificates and keys {}",
|
|
||||||
server_name.as_str(),
|
|
||||||
e
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// add client certificate if specified
|
// add client certificate if specified
|
||||||
match certs_and_keys.parse_client_ca_certs() {
|
match certs_and_keys.parse_client_ca_certs() {
|
||||||
Ok((owned_trust_anchors, _subject_key_ids)) => {
|
Ok((owned_trust_anchors, _subject_key_ids)) => {
|
||||||
|
|
@ -120,14 +129,14 @@ impl TryInto<Arc<ServerCrypto>> for &ServerCryptoBase {
|
||||||
|
|
||||||
let mut server_config_local = if client_ca_roots_local.is_empty() {
|
let mut server_config_local = if client_ca_roots_local.is_empty() {
|
||||||
// with no client auth, enable http1.1 -- 3
|
// with no client auth, enable http1.1 -- 3
|
||||||
#[cfg(not(feature = "http3"))]
|
#[cfg(not(any(feature = "http3-quinn", feature = "http3-s2n")))]
|
||||||
{
|
{
|
||||||
ServerConfig::builder()
|
ServerConfig::builder()
|
||||||
.with_safe_defaults()
|
.with_safe_defaults()
|
||||||
.with_no_client_auth()
|
.with_no_client_auth()
|
||||||
.with_cert_resolver(Arc::new(resolver_local))
|
.with_cert_resolver(Arc::new(resolver_local))
|
||||||
}
|
}
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
{
|
{
|
||||||
let mut sc = ServerConfig::builder()
|
let mut sc = ServerConfig::builder()
|
||||||
.with_safe_defaults()
|
.with_safe_defaults()
|
||||||
|
|
@ -150,6 +159,33 @@ impl TryInto<Arc<ServerCrypto>> for &ServerCryptoBase {
|
||||||
|
|
||||||
server_crypto_local_map.insert(server_name_bytes_exp.to_owned(), Arc::new(server_config_local));
|
server_crypto_local_map.insert(server_name_bytes_exp.to_owned(), Arc::new(server_config_local));
|
||||||
}
|
}
|
||||||
|
Ok(server_crypto_local_map)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "http3-quinn")]
|
||||||
|
fn build_server_crypto_global(&self) -> Result<ServerConfig, ReloaderError<ServerCryptoBase>> {
|
||||||
|
let mut resolver_global = ResolvesServerCertUsingSni::new();
|
||||||
|
|
||||||
|
for (server_name_bytes_exp, certs_and_keys) in self.inner.iter() {
|
||||||
|
let server_name: String = server_name_bytes_exp.try_into()?;
|
||||||
|
|
||||||
|
// Parse server certificates and private keys
|
||||||
|
let Ok(certified_key): Result<CertifiedKey, _> = certs_and_keys.parse_server_certs_and_keys() else {
|
||||||
|
warn!("Failed to add certificate for {}", server_name);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if certs_and_keys.client_ca_certs.is_none() {
|
||||||
|
// aggregated server config for no client auth server for http3
|
||||||
|
if let Err(e) = resolver_global.add(server_name.as_str(), certified_key) {
|
||||||
|
error!(
|
||||||
|
"{}: Failed to read some certificates and keys {}",
|
||||||
|
server_name.as_str(),
|
||||||
|
e
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//////////////
|
//////////////
|
||||||
let mut server_crypto_global = ServerConfig::builder()
|
let mut server_crypto_global = ServerConfig::builder()
|
||||||
|
|
@ -159,23 +195,82 @@ impl TryInto<Arc<ServerCrypto>> for &ServerCryptoBase {
|
||||||
|
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
|
|
||||||
#[cfg(feature = "http3")]
|
|
||||||
{
|
|
||||||
server_crypto_global.alpn_protocols = vec![
|
server_crypto_global.alpn_protocols = vec![
|
||||||
b"h3".to_vec(),
|
b"h3".to_vec(),
|
||||||
b"hq-29".to_vec(), // TODO: remove later?
|
b"hq-29".to_vec(), // TODO: remove later?
|
||||||
b"h2".to_vec(),
|
b"h2".to_vec(),
|
||||||
b"http/1.1".to_vec(),
|
b"http/1.1".to_vec(),
|
||||||
];
|
];
|
||||||
}
|
Ok(server_crypto_global)
|
||||||
#[cfg(not(feature = "http3"))]
|
|
||||||
{
|
|
||||||
server_crypto_global.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Arc::new(ServerCrypto {
|
#[cfg(feature = "http3-s2n")]
|
||||||
inner_global_no_client_auth: Arc::new(server_crypto_global),
|
fn build_server_crypto_global(&self) -> Result<s2n_quic_rustls::Server, ReloaderError<ServerCryptoBase>> {
|
||||||
inner_local_map: Arc::new(server_crypto_local_map),
|
let mut resolver_global = s2n_quic_rustls::rustls::server::ResolvesServerCertUsingSni::new();
|
||||||
}))
|
|
||||||
|
for (server_name_bytes_exp, certs_and_keys) in self.inner.iter() {
|
||||||
|
let server_name: String = server_name_bytes_exp.try_into()?;
|
||||||
|
|
||||||
|
// Parse server certificates and private keys
|
||||||
|
let Ok(certified_key) = parse_server_certs_and_keys_s2n(certs_and_keys) else {
|
||||||
|
warn!("Failed to add certificate for {}", server_name);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if certs_and_keys.client_ca_certs.is_none() {
|
||||||
|
// aggregated server config for no client auth server for http3
|
||||||
|
if let Err(e) = resolver_global.add(server_name.as_str(), certified_key) {
|
||||||
|
error!(
|
||||||
|
"{}: Failed to read some certificates and keys {}",
|
||||||
|
server_name.as_str(),
|
||||||
|
e
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
let alpn = vec![
|
||||||
|
b"h3".to_vec(),
|
||||||
|
b"hq-29".to_vec(), // TODO: remove later?
|
||||||
|
b"h2".to_vec(),
|
||||||
|
b"http/1.1".to_vec(),
|
||||||
|
];
|
||||||
|
let server_crypto_global = s2n_quic::provider::tls::rustls::Server::builder()
|
||||||
|
.with_cert_resolver(Arc::new(resolver_global))
|
||||||
|
.map_err(|e| anyhow::anyhow!(e))?
|
||||||
|
.with_application_protocols(alpn.iter())
|
||||||
|
.map_err(|e| anyhow::anyhow!(e))?
|
||||||
|
.build()
|
||||||
|
.map_err(|e| anyhow::anyhow!(e))?;
|
||||||
|
Ok(server_crypto_global)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "http3-s2n")]
|
||||||
|
/// This is workaround for the version difference between rustls and s2n-quic-rustls
|
||||||
|
fn parse_server_certs_and_keys_s2n(
|
||||||
|
certs_and_keys: &CertsAndKeys,
|
||||||
|
) -> Result<s2n_quic_rustls::rustls::sign::CertifiedKey, anyhow::Error> {
|
||||||
|
let signing_key = certs_and_keys
|
||||||
|
.cert_keys
|
||||||
|
.iter()
|
||||||
|
.find_map(|k| {
|
||||||
|
let s2n_private_key = s2n_quic_rustls::PrivateKey(k.0.clone());
|
||||||
|
if let Ok(sk) = s2n_quic_rustls::rustls::sign::any_supported_type(&s2n_private_key) {
|
||||||
|
Some(sk)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok_or_else(|| {
|
||||||
|
std::io::Error::new(
|
||||||
|
std::io::ErrorKind::InvalidInput,
|
||||||
|
"Unable to find a valid certificate and key",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let certs: Vec<_> = certs_and_keys
|
||||||
|
.certs
|
||||||
|
.iter()
|
||||||
|
.map(|c| s2n_quic_rustls::rustls::Certificate(c.0.clone()))
|
||||||
|
.collect();
|
||||||
|
Ok(s2n_quic_rustls::rustls::sign::CertifiedKey::new(certs, signing_key))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
mod crypto_service;
|
mod crypto_service;
|
||||||
mod proxy_client_cert;
|
mod proxy_client_cert;
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
mod proxy_h3;
|
mod proxy_h3;
|
||||||
mod proxy_main;
|
mod proxy_main;
|
||||||
|
#[cfg(feature = "http3-quinn")]
|
||||||
|
mod proxy_quic_quinn;
|
||||||
|
#[cfg(feature = "http3-s2n")]
|
||||||
|
mod proxy_quic_s2n;
|
||||||
mod proxy_tls;
|
mod proxy_tls;
|
||||||
mod socket;
|
mod socket;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
use super::Proxy;
|
use super::Proxy;
|
||||||
use crate::{certs::CryptoSource, error::*, log::*, utils::ServerNameBytesExp};
|
use crate::{certs::CryptoSource, error::*, log::*, utils::ServerNameBytesExp};
|
||||||
use bytes::{Buf, Bytes};
|
use bytes::{Buf, Bytes};
|
||||||
use h3::{quic::BidiStream, server::RequestStream};
|
#[cfg(feature = "http3-quinn")]
|
||||||
|
use h3::{quic::BidiStream, quic::Connection as ConnectionQuic, server::RequestStream};
|
||||||
use hyper::{client::connect::Connect, Body, Request, Response};
|
use hyper::{client::connect::Connect, Body, Request, Response};
|
||||||
|
#[cfg(feature = "http3-s2n")]
|
||||||
|
use s2n_quic_h3::h3::{self, quic::BidiStream, quic::Connection as ConnectionQuic, server::RequestStream};
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use tokio::time::{timeout, Duration};
|
use tokio::time::{timeout, Duration};
|
||||||
|
|
||||||
|
|
@ -11,16 +14,19 @@ where
|
||||||
T: Connect + Clone + Sync + Send + 'static,
|
T: Connect + Clone + Sync + Send + 'static,
|
||||||
U: CryptoSource + Clone + Sync + Send + 'static,
|
U: CryptoSource + Clone + Sync + Send + 'static,
|
||||||
{
|
{
|
||||||
pub(super) async fn connection_serve_h3(
|
pub(super) async fn connection_serve_h3<C>(
|
||||||
self,
|
self,
|
||||||
conn: quinn::Connecting,
|
quic_connection: C,
|
||||||
tls_server_name: ServerNameBytesExp,
|
tls_server_name: ServerNameBytesExp,
|
||||||
) -> Result<()> {
|
client_addr: SocketAddr,
|
||||||
let client_addr = conn.remote_address();
|
) -> Result<()>
|
||||||
|
where
|
||||||
match conn.await {
|
C: ConnectionQuic<Bytes>,
|
||||||
Ok(new_conn) => {
|
<C as ConnectionQuic<Bytes>>::BidiStream: BidiStream<Bytes> + Send + 'static,
|
||||||
let mut h3_conn = h3::server::Connection::<_, bytes::Bytes>::new(h3_quinn::Connection::new(new_conn)).await?;
|
<<C as ConnectionQuic<Bytes>>::BidiStream as BidiStream<Bytes>>::RecvStream: Send,
|
||||||
|
<<C as ConnectionQuic<Bytes>>::BidiStream as BidiStream<Bytes>>::SendStream: Send,
|
||||||
|
{
|
||||||
|
let mut h3_conn = h3::server::Connection::<_, Bytes>::new(quic_connection).await?;
|
||||||
info!(
|
info!(
|
||||||
"QUIC/HTTP3 connection established from {:?} {:?}",
|
"QUIC/HTTP3 connection established from {:?} {:?}",
|
||||||
client_addr, tls_server_name
|
client_addr, tls_server_name
|
||||||
|
|
@ -68,12 +74,6 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
warn!("QUIC accepting connection failed: {:?}", err);
|
|
||||||
return Err(RpxyError::QuicConn(err));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
124
rpxy-lib/src/proxy/proxy_quic_quinn.rs
Normal file
124
rpxy-lib/src/proxy/proxy_quic_quinn.rs
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
use super::socket::bind_udp_socket;
|
||||||
|
use super::{
|
||||||
|
crypto_service::{ServerCrypto, ServerCryptoBase},
|
||||||
|
proxy_main::Proxy,
|
||||||
|
};
|
||||||
|
use crate::{certs::CryptoSource, error::*, log::*, utils::BytesName};
|
||||||
|
use hot_reload::ReloaderReceiver;
|
||||||
|
use hyper::client::connect::Connect;
|
||||||
|
use quinn::{crypto::rustls::HandshakeData, Endpoint, ServerConfig as QuicServerConfig, TransportConfig};
|
||||||
|
use rustls::ServerConfig;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
impl<T, U> Proxy<T, U>
|
||||||
|
where
|
||||||
|
T: Connect + Clone + Sync + Send + 'static,
|
||||||
|
U: CryptoSource + Clone + Sync + Send + 'static,
|
||||||
|
{
|
||||||
|
pub(super) async fn listener_service_h3(
|
||||||
|
&self,
|
||||||
|
mut server_crypto_rx: ReloaderReceiver<ServerCryptoBase>,
|
||||||
|
) -> Result<()> {
|
||||||
|
info!("Start UDP proxy serving with HTTP/3 request for configured host names [quinn]");
|
||||||
|
// first set as null config server
|
||||||
|
let rustls_server_config = ServerConfig::builder()
|
||||||
|
.with_safe_default_cipher_suites()
|
||||||
|
.with_safe_default_kx_groups()
|
||||||
|
.with_protocol_versions(&[&rustls::version::TLS13])?
|
||||||
|
.with_no_client_auth()
|
||||||
|
.with_cert_resolver(Arc::new(rustls::server::ResolvesServerCertUsingSni::new()));
|
||||||
|
|
||||||
|
let mut transport_config_quic = TransportConfig::default();
|
||||||
|
transport_config_quic
|
||||||
|
.max_concurrent_bidi_streams(self.globals.proxy_config.h3_max_concurrent_bidistream.into())
|
||||||
|
.max_concurrent_uni_streams(self.globals.proxy_config.h3_max_concurrent_unistream.into())
|
||||||
|
.max_idle_timeout(
|
||||||
|
self
|
||||||
|
.globals
|
||||||
|
.proxy_config
|
||||||
|
.h3_max_idle_timeout
|
||||||
|
.map(|v| quinn::IdleTimeout::try_from(v).unwrap()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut server_config_h3 = QuicServerConfig::with_crypto(Arc::new(rustls_server_config));
|
||||||
|
server_config_h3.transport = Arc::new(transport_config_quic);
|
||||||
|
server_config_h3.concurrent_connections(self.globals.proxy_config.h3_max_concurrent_connections);
|
||||||
|
|
||||||
|
// To reuse address
|
||||||
|
let udp_socket = bind_udp_socket(&self.listening_on)?;
|
||||||
|
let runtime = quinn::default_runtime()
|
||||||
|
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "No async runtime found"))?;
|
||||||
|
let endpoint = Endpoint::new(
|
||||||
|
quinn::EndpointConfig::default(),
|
||||||
|
Some(server_config_h3),
|
||||||
|
udp_socket,
|
||||||
|
runtime,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut server_crypto: Option<Arc<ServerCrypto>> = None;
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
new_conn = endpoint.accept() => {
|
||||||
|
if server_crypto.is_none() || new_conn.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut conn: quinn::Connecting = new_conn.unwrap();
|
||||||
|
let Ok(hsd) = conn.handshake_data().await else {
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(hsd_downcast) = hsd.downcast::<HandshakeData>() else {
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
let Some(new_server_name) = hsd_downcast.server_name else {
|
||||||
|
warn!("HTTP/3 no SNI is given");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
debug!(
|
||||||
|
"HTTP/3 connection incoming (SNI {:?})",
|
||||||
|
new_server_name
|
||||||
|
);
|
||||||
|
// TODO: server_nameをここで出してどんどん深く投げていくのは効率が悪い。connecting -> connectionsの後でいいのでは?
|
||||||
|
// TODO: 通常のTLSと同じenumか何かにまとめたい
|
||||||
|
let self_clone = self.clone();
|
||||||
|
self.globals.runtime_handle.spawn(async move {
|
||||||
|
let client_addr = conn.remote_address();
|
||||||
|
let quic_connection = match conn.await {
|
||||||
|
Ok(new_conn) => {
|
||||||
|
info!("New connection established");
|
||||||
|
h3_quinn::Connection::new(new_conn)
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
warn!("QUIC accepting connection failed: {:?}", e);
|
||||||
|
return Err(RpxyError::QuicConn(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Timeout is based on underlying quic
|
||||||
|
if let Err(e) = self_clone.connection_serve_h3(quic_connection, new_server_name.to_server_name_vec(), client_addr).await {
|
||||||
|
warn!("QUIC or HTTP/3 connection failed: {}", e);
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ = server_crypto_rx.changed() => {
|
||||||
|
if server_crypto_rx.borrow().is_none() {
|
||||||
|
error!("Reloader is broken");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let cert_keys_map = server_crypto_rx.borrow().clone().unwrap();
|
||||||
|
|
||||||
|
server_crypto = (&cert_keys_map).try_into().ok();
|
||||||
|
let Some(inner) = server_crypto.clone() else {
|
||||||
|
error!("Failed to update server crypto for h3");
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
endpoint.set_server_config(Some(QuicServerConfig::with_crypto(inner.clone().inner_global_no_client_auth.clone())));
|
||||||
|
|
||||||
|
}
|
||||||
|
else => break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endpoint.wait_idle().await;
|
||||||
|
Ok(()) as Result<()>
|
||||||
|
}
|
||||||
|
}
|
||||||
135
rpxy-lib/src/proxy/proxy_quic_s2n.rs
Normal file
135
rpxy-lib/src/proxy/proxy_quic_s2n.rs
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
use super::{
|
||||||
|
crypto_service::{ServerCrypto, ServerCryptoBase},
|
||||||
|
proxy_main::Proxy,
|
||||||
|
};
|
||||||
|
use crate::{certs::CryptoSource, error::*, log::*, utils::BytesName};
|
||||||
|
use hot_reload::ReloaderReceiver;
|
||||||
|
use hyper::client::connect::Connect;
|
||||||
|
use s2n_quic::provider;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
impl<T, U> Proxy<T, U>
|
||||||
|
where
|
||||||
|
T: Connect + Clone + Sync + Send + 'static,
|
||||||
|
U: CryptoSource + Clone + Sync + Send + 'static,
|
||||||
|
{
|
||||||
|
pub(super) async fn listener_service_h3(
|
||||||
|
&self,
|
||||||
|
mut server_crypto_rx: ReloaderReceiver<ServerCryptoBase>,
|
||||||
|
) -> Result<()> {
|
||||||
|
info!("Start UDP proxy serving with HTTP/3 request for configured host names [s2n-quic]");
|
||||||
|
|
||||||
|
// initially wait for receipt
|
||||||
|
let mut server_crypto: Option<Arc<ServerCrypto>> = {
|
||||||
|
let _ = server_crypto_rx.changed().await;
|
||||||
|
let sc = self.receive_server_crypto(server_crypto_rx.clone())?;
|
||||||
|
Some(sc)
|
||||||
|
};
|
||||||
|
|
||||||
|
// event loop
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
v = self.serve_connection(&server_crypto) => {
|
||||||
|
if let Err(e) = v {
|
||||||
|
error!("Quic connection event loop illegally shutdown [s2n-quic] {e}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = server_crypto_rx.changed() => {
|
||||||
|
server_crypto = match self.receive_server_crypto(server_crypto_rx.clone()) {
|
||||||
|
Ok(sc) => Some(sc),
|
||||||
|
Err(e) => {
|
||||||
|
error!("{e}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else => break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive_server_crypto(&self, server_crypto_rx: ReloaderReceiver<ServerCryptoBase>) -> Result<Arc<ServerCrypto>> {
|
||||||
|
let cert_keys_map = server_crypto_rx.borrow().clone().ok_or_else(|| {
|
||||||
|
error!("Reloader is broken");
|
||||||
|
RpxyError::Other(anyhow!("Reloader is broken"))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let server_crypto: Option<Arc<ServerCrypto>> = (&cert_keys_map).try_into().ok();
|
||||||
|
server_crypto.ok_or_else(|| {
|
||||||
|
error!("Failed to update server crypto for h3 [s2n-quic]");
|
||||||
|
RpxyError::Other(anyhow!("Failed to update server crypto for h3 [s2n-quic]"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn serve_connection(&self, server_crypto: &Option<Arc<ServerCrypto>>) -> Result<()> {
|
||||||
|
// setup UDP socket
|
||||||
|
let io = provider::io::tokio::Builder::default()
|
||||||
|
.with_receive_address(self.listening_on)?
|
||||||
|
.with_reuse_port()?
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
// setup limits
|
||||||
|
let mut limits = provider::limits::Limits::default()
|
||||||
|
.with_max_open_local_bidirectional_streams(self.globals.proxy_config.h3_max_concurrent_bidistream as u64)
|
||||||
|
.map_err(|e| anyhow!(e))?
|
||||||
|
.with_max_open_remote_bidirectional_streams(self.globals.proxy_config.h3_max_concurrent_bidistream as u64)
|
||||||
|
.map_err(|e| anyhow!(e))?
|
||||||
|
.with_max_open_local_unidirectional_streams(self.globals.proxy_config.h3_max_concurrent_unistream as u64)
|
||||||
|
.map_err(|e| anyhow!(e))?
|
||||||
|
.with_max_open_remote_unidirectional_streams(self.globals.proxy_config.h3_max_concurrent_unistream as u64)
|
||||||
|
.map_err(|e| anyhow!(e))?
|
||||||
|
.with_max_active_connection_ids(self.globals.proxy_config.h3_max_concurrent_connections as u64)
|
||||||
|
.map_err(|e| anyhow!(e))?;
|
||||||
|
limits = if let Some(v) = self.globals.proxy_config.h3_max_idle_timeout {
|
||||||
|
limits.with_max_idle_timeout(v).map_err(|e| anyhow!(e))?
|
||||||
|
} else {
|
||||||
|
limits
|
||||||
|
};
|
||||||
|
|
||||||
|
// setup tls
|
||||||
|
let Some(server_crypto) = server_crypto else {
|
||||||
|
warn!("No server crypto is given [s2n-quic]");
|
||||||
|
return Err(RpxyError::Other(anyhow!("No server crypto is given [s2n-quic]")));
|
||||||
|
};
|
||||||
|
let tls = server_crypto.inner_global_no_client_auth.clone();
|
||||||
|
|
||||||
|
let mut server = s2n_quic::Server::builder()
|
||||||
|
.with_tls(tls)
|
||||||
|
.map_err(|e| anyhow::anyhow!(e))?
|
||||||
|
.with_io(io)
|
||||||
|
.map_err(|e| anyhow!(e))?
|
||||||
|
.with_limits(limits)
|
||||||
|
.map_err(|e| anyhow!(e))?
|
||||||
|
.start()
|
||||||
|
.map_err(|e| anyhow!(e))?;
|
||||||
|
|
||||||
|
// quic event loop. this immediately cancels when crypto is updated by tokio::select!
|
||||||
|
while let Some(new_conn) = server.accept().await {
|
||||||
|
debug!("New QUIC connection established");
|
||||||
|
let Ok(Some(new_server_name)) = new_conn.server_name() else {
|
||||||
|
warn!("HTTP/3 no SNI is given");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
debug!("HTTP/3 connection incoming (SNI {:?})", new_server_name);
|
||||||
|
let self_clone = self.clone();
|
||||||
|
|
||||||
|
self.globals.runtime_handle.spawn(async move {
|
||||||
|
let client_addr = new_conn.remote_addr()?;
|
||||||
|
let quic_connection = s2n_quic_h3::Connection::new(new_conn);
|
||||||
|
// Timeout is based on underlying quic
|
||||||
|
if let Err(e) = self_clone
|
||||||
|
.connection_serve_h3(quic_connection, new_server_name.to_server_name_vec(), client_addr)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
warn!("QUIC or HTTP/3 connection failed: {}", e);
|
||||||
|
};
|
||||||
|
Ok(()) as Result<()>
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
#[cfg(feature = "http3")]
|
|
||||||
use super::socket::bind_udp_socket;
|
|
||||||
use super::{
|
use super::{
|
||||||
crypto_service::{CryptoReloader, ServerCrypto, ServerCryptoBase, SniServerCryptoMap},
|
crypto_service::{CryptoReloader, ServerCrypto, ServerCryptoBase, SniServerCryptoMap},
|
||||||
proxy_main::{LocalExecutor, Proxy},
|
proxy_main::{LocalExecutor, Proxy},
|
||||||
|
|
@ -8,10 +6,6 @@ use super::{
|
||||||
use crate::{certs::CryptoSource, constants::*, error::*, log::*, utils::BytesName};
|
use crate::{certs::CryptoSource, constants::*, error::*, log::*, utils::BytesName};
|
||||||
use hot_reload::{ReloaderReceiver, ReloaderService};
|
use hot_reload::{ReloaderReceiver, ReloaderService};
|
||||||
use hyper::{client::connect::Connect, server::conn::Http};
|
use hyper::{client::connect::Connect, server::conn::Http};
|
||||||
#[cfg(feature = "http3")]
|
|
||||||
use quinn::{crypto::rustls::HandshakeData, Endpoint, ServerConfig as QuicServerConfig, TransportConfig};
|
|
||||||
#[cfg(feature = "http3")]
|
|
||||||
use rustls::ServerConfig;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::time::{timeout, Duration};
|
use tokio::time::{timeout, Duration};
|
||||||
|
|
||||||
|
|
@ -105,99 +99,6 @@ where
|
||||||
Ok(()) as Result<()>
|
Ok(()) as Result<()>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "http3")]
|
|
||||||
async fn listener_service_h3(&self, mut server_crypto_rx: ReloaderReceiver<ServerCryptoBase>) -> Result<()> {
|
|
||||||
info!("Start UDP proxy serving with HTTP/3 request for configured host names");
|
|
||||||
// first set as null config server
|
|
||||||
let rustls_server_config = ServerConfig::builder()
|
|
||||||
.with_safe_default_cipher_suites()
|
|
||||||
.with_safe_default_kx_groups()
|
|
||||||
.with_protocol_versions(&[&rustls::version::TLS13])?
|
|
||||||
.with_no_client_auth()
|
|
||||||
.with_cert_resolver(Arc::new(rustls::server::ResolvesServerCertUsingSni::new()));
|
|
||||||
|
|
||||||
let mut transport_config_quic = TransportConfig::default();
|
|
||||||
transport_config_quic
|
|
||||||
.max_concurrent_bidi_streams(self.globals.proxy_config.h3_max_concurrent_bidistream)
|
|
||||||
.max_concurrent_uni_streams(self.globals.proxy_config.h3_max_concurrent_unistream)
|
|
||||||
.max_idle_timeout(
|
|
||||||
self
|
|
||||||
.globals
|
|
||||||
.proxy_config
|
|
||||||
.h3_max_idle_timeout
|
|
||||||
.map(|v| quinn::IdleTimeout::try_from(v).unwrap()),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut server_config_h3 = QuicServerConfig::with_crypto(Arc::new(rustls_server_config));
|
|
||||||
server_config_h3.transport = Arc::new(transport_config_quic);
|
|
||||||
server_config_h3.concurrent_connections(self.globals.proxy_config.h3_max_concurrent_connections);
|
|
||||||
|
|
||||||
// To reuse address
|
|
||||||
let udp_socket = bind_udp_socket(&self.listening_on)?;
|
|
||||||
let runtime = quinn::default_runtime()
|
|
||||||
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "No async runtime found"))?;
|
|
||||||
let endpoint = Endpoint::new(
|
|
||||||
quinn::EndpointConfig::default(),
|
|
||||||
Some(server_config_h3),
|
|
||||||
udp_socket,
|
|
||||||
runtime,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut server_crypto: Option<Arc<ServerCrypto>> = None;
|
|
||||||
loop {
|
|
||||||
tokio::select! {
|
|
||||||
new_conn = endpoint.accept() => {
|
|
||||||
if server_crypto.is_none() || new_conn.is_none() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let mut conn: quinn::Connecting = new_conn.unwrap();
|
|
||||||
let Ok(hsd) = conn.handshake_data().await else {
|
|
||||||
continue
|
|
||||||
};
|
|
||||||
|
|
||||||
let Ok(hsd_downcast) = hsd.downcast::<HandshakeData>() else {
|
|
||||||
continue
|
|
||||||
};
|
|
||||||
let Some(new_server_name) = hsd_downcast.server_name else {
|
|
||||||
warn!("HTTP/3 no SNI is given");
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
debug!(
|
|
||||||
"HTTP/3 connection incoming (SNI {:?})",
|
|
||||||
new_server_name
|
|
||||||
);
|
|
||||||
// TODO: server_nameをここで出してどんどん深く投げていくのは効率が悪い。connecting -> connectionsの後でいいのでは?
|
|
||||||
// TODO: 通常のTLSと同じenumか何かにまとめたい
|
|
||||||
let fut = self.clone().connection_serve_h3(conn, new_server_name.to_server_name_vec());
|
|
||||||
self.globals.runtime_handle.spawn(async move {
|
|
||||||
// Timeout is based on underlying quic
|
|
||||||
if let Err(e) = fut.await {
|
|
||||||
warn!("QUIC or HTTP/3 connection failed: {}", e)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_ = server_crypto_rx.changed() => {
|
|
||||||
if server_crypto_rx.borrow().is_none() {
|
|
||||||
error!("Reloader is broken");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let cert_keys_map = server_crypto_rx.borrow().clone().unwrap();
|
|
||||||
|
|
||||||
server_crypto = (&cert_keys_map).try_into().ok();
|
|
||||||
let Some(inner) = server_crypto.clone() else {
|
|
||||||
error!("Failed to update server crypto for h3");
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
endpoint.set_server_config(Some(QuicServerConfig::with_crypto(inner.clone().inner_global_no_client_auth.clone())));
|
|
||||||
|
|
||||||
}
|
|
||||||
else => break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
endpoint.wait_idle().await;
|
|
||||||
Ok(()) as Result<()>
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn start_with_tls(self, server: Http<LocalExecutor>) -> Result<()> {
|
pub async fn start_with_tls(self, server: Http<LocalExecutor>) -> Result<()> {
|
||||||
let (cert_reloader_service, cert_reloader_rx) = ReloaderService::<CryptoReloader<U>, ServerCryptoBase>::new(
|
let (cert_reloader_service, cert_reloader_rx) = ReloaderService::<CryptoReloader<U>, ServerCryptoBase>::new(
|
||||||
&self.globals.clone(),
|
&self.globals.clone(),
|
||||||
|
|
@ -207,7 +108,7 @@ where
|
||||||
.await
|
.await
|
||||||
.map_err(|e| anyhow::anyhow!(e))?;
|
.map_err(|e| anyhow::anyhow!(e))?;
|
||||||
|
|
||||||
#[cfg(not(feature = "http3"))]
|
#[cfg(not(any(feature = "http3-quinn", feature = "http3-s2n")))]
|
||||||
{
|
{
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_= cert_reloader_service.start() => {
|
_= cert_reloader_service.start() => {
|
||||||
|
|
@ -223,7 +124,7 @@ where
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
|
||||||
{
|
{
|
||||||
if self.globals.proxy_config.http3 {
|
if self.globals.proxy_config.http3 {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::{error::*, log::*};
|
use crate::{error::*, log::*};
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(feature = "http3-quinn")]
|
||||||
use socket2::{Domain, Protocol, Socket, Type};
|
use socket2::{Domain, Protocol, Socket, Type};
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(feature = "http3-quinn")]
|
||||||
use std::net::UdpSocket;
|
use std::net::UdpSocket;
|
||||||
use tokio::net::TcpSocket;
|
use tokio::net::TcpSocket;
|
||||||
|
|
||||||
|
|
@ -23,7 +23,7 @@ pub(super) fn bind_tcp_socket(listening_on: &SocketAddr) -> Result<TcpSocket> {
|
||||||
Ok(tcp_socket)
|
Ok(tcp_socket)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(feature = "http3-quinn")]
|
||||||
/// Bind UDP socket to the given `SocketAddr`, and returns the UDP socket with `SO_REUSEADDR` and `SO_REUSEPORT` options.
|
/// Bind UDP socket to the given `SocketAddr`, and returns the UDP socket with `SO_REUSEADDR` and `SO_REUSEPORT` options.
|
||||||
/// This option is required to re-bind the socket address when the proxy instance is reconstructed.
|
/// This option is required to re-bind the socket address when the proxy instance is reconstructed.
|
||||||
pub(super) fn bind_udp_socket(listening_on: &SocketAddr) -> Result<UdpSocket> {
|
pub(super) fn bind_udp_socket(listening_on: &SocketAddr) -> Result<UdpSocket> {
|
||||||
|
|
|
||||||
1
s2n-quic
Submodule
1
s2n-quic
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 8ef0a6b66a856dc9f34ce18159c617ac29154cc7
|
||||||
Loading…
Add table
Add a link
Reference in a new issue