Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ permissions:
pull-requests: write

env:
FEATURES: "async,ffi,qlog"
FEATURES: "async,ffi,qlog,rpk"
Comment thread
cjpatton marked this conversation as resolved.
RUSTFLAGS: "-D warnings"
RUSTDOCFLAGS: "--cfg docsrs"
RUSTTOOLCHAIN: "nightly"
Expand Down Expand Up @@ -68,12 +68,15 @@ jobs:
# duplicate builds for PRs created from internal branches.
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
# `quiche-fuzz` calls `RAND_reset_for_fuzzing`, which BoringSSL only
# exports when `BORINGSSL_UNSAFE_DETERMINISTIC_MODE` is defined.
# `boring-sys` doesn't expose a feature for that, but cmake-rs honors
# `CFLAGS`/`CXXFLAGS`, so inject the defines there.
# exports when built with `FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION`.
# BoringSSL 5.x consolidated the older
# `BORINGSSL_UNSAFE_{DETERMINISTIC,FUZZER}_MODE` defines into this
# single switch. `boring-sys` doesn't expose a feature for it, but
# cmake-rs (via cc-rs) honors `CFLAGS`/`CXXFLAGS`, so inject the
# define there.
env:
CFLAGS: "-DBORINGSSL_UNSAFE_DETERMINISTIC_MODE -DBORINGSSL_UNSAFE_FUZZER_MODE"
CXXFLAGS: "-DBORINGSSL_UNSAFE_DETERMINISTIC_MODE -DBORINGSSL_UNSAFE_FUZZER_MODE"
CFLAGS: "-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION"
CXXFLAGS: "-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION"
steps:
- name: Checkout sources
uses: actions/checkout@v4
Expand Down
85 changes: 75 additions & 10 deletions .github/workflows/stable.yml
Comment thread
cjpatton marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ permissions:
pull-requests: write

env:
DEFAULT_OPTIONS: "--features=async,ffi,qlog --workspace"
DEFAULT_OPTIONS: "--features=async,ffi,qlog,rpk --workspace"
# Used by `quiche_multiarch`, which can't link `boring` for foreign targets.
NO_BORING_OPTIONS: "--features=ffi,qlog --workspace --exclude h3i --exclude tokio-quiche"
RUSTFLAGS: "-D warnings"
Expand All @@ -20,6 +20,10 @@ concurrency:
jobs:
quiche:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
boring_version: ["latest", "4"]
# Only run on "pull_request" event for external PRs. This is to avoid
# duplicate builds for PRs created from internal branches.
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
Expand All @@ -38,6 +42,20 @@ jobs:
sudo apt-get update
sudo apt-get install libexpat1-dev libfreetype6-dev libfontconfig1-dev

- name: Maybe pin boring to MSRV (4.19.0)
if: matrix.boring_version == '4'
run: |
# Pin boring to the minimum supported 4.x. cargo's resolver
# normally picks the highest match (5.x); generate the
# lockfile first, then downgrade to the floor of the
# workspace dep range (`>= 4.19, < 6`) via `--precise`.
# Pinning to the floor (rather than the latest 4.x) catches
# accidental use of APIs added in later 4.x point releases.
# `quiche/src/build.rs` and `tokio-quiche/build.rs` then read
# the lockfile and flip `cfg(boring_v4)` on.
cargo generate-lockfile
cargo update -p boring --precise 4.19.0

- name: Unused dependency check
uses: bnjbvr/cargo-machete@main

Expand Down Expand Up @@ -130,11 +148,21 @@ jobs:
matrix:
target: ["x86_64-pc-windows-msvc", "i686-pc-windows-msvc", "x86_64-pc-windows-gnu", "i686-pc-windows-gnu"]
include:
# i686 (32-bit) BoringSSL builds require SSE2 explicitly. The
# toolchain file in BoringSSL sets `-msse2`, but cmake-rs
# passes `-DCMAKE_C_FLAGS=...` on the command line which
# overrides that, so we re-add it here.
- target: "i686-pc-windows-msvc"
cxxflags: "-msse2"
- target: "i686-pc-windows-gnu"
cflags: "-mlong-double-64" # bindgen detects that Rust doesn't support 80-bit long double
# `-mlong-double-64`: bindgen detects that Rust doesn't
# support 80-bit long double.
# `-msse2`: same reason as i686-pc-windows-msvc above.
cflags: "-mlong-double-64 -msse2"
cxxflags: "-mlong-double-64 -msse2"
env:
CFLAGS: ${{ matrix.cflags }}
CXXFLAGS: ${{ matrix.cflags }}
CXXFLAGS: ${{ matrix.cxxflags }}
BINDGEN_EXTRA_CLANG_ARGS: ${{ matrix.cflags }}
# Only run on "pull_request" event for external PRs. This is to avoid
# duplicate builds for PRs created from internal branches.
Expand All @@ -149,18 +177,55 @@ jobs:
toolchain: ${{ env.RUSTTOOLCHAIN }}
targets: ${{ matrix.target }}

# The windows-2022 image preinstalls MSYS2 (gcc 14.2.0) at
# `C:\msys64` but doesn't put it on PATH. For x86_64-pc-windows-gnu
# we rely on these preinstalled tools and just point the C/C++
# toolchain at them, matching `boring`'s own CI workflow.
- name: Set up MinGW for 64 bit
if: matrix.target == 'x86_64-pc-windows-gnu'
uses: bwoodsend/setup-winlibs-action@v1.10
with:
tag: 12.2.0-16.0.0-10.0.0-msvcrt-r5

shell: bash
run: |
echo >> "$GITHUB_ENV" CC=gcc
echo >> "$GITHUB_ENV" CXX=g++
echo >> "$GITHUB_ENV" 'C_INCLUDE_PATH=C:\msys64\usr\include'
echo >> "$GITHUB_ENV" 'CPLUS_INCLUDE_PATH=C:\msys64\usr\include'
echo >> "$GITHUB_ENV" 'LIBRARY_PATH=C:\msys64\usr\lib'

# For i686 we install a fresh 32-bit MSYS2 toolchain (and a
# 32-bit cmake from MSYS2, since the system cmake is 64-bit and
# picks up the wrong `gcc`). Mirrors `boring`'s CI workflow.
- name: Set up MinGW for 32 bit
if: matrix.target == 'i686-pc-windows-gnu'
uses: bwoodsend/setup-winlibs-action@v1.10
uses: msys2/setup-msys2@v2
id: msys2
with:
architecture: i686
tag: 12.2.0-16.0.0-10.0.0-msvcrt-r5
msystem: MINGW32
path-type: inherit
install: >-
mingw-w64-i686-gcc
mingw-w64-i686-cmake

- name: Set up 32-bit MSYS2 env vars
if: matrix.target == 'i686-pc-windows-gnu'
shell: bash
run: |
MSYS_ROOT='${{ steps.msys2.outputs.msys2-location }}'
test -d "$MSYS_ROOT\\mingw32\\bin"
echo >> $GITHUB_PATH "$MSYS_ROOT\\mingw32\\bin"
echo >> $GITHUB_PATH "$MSYS_ROOT\\usr\\bin"
echo >> $GITHUB_ENV CC="$MSYS_ROOT\\mingw32\\bin\\gcc"
echo >> $GITHUB_ENV CXX="$MSYS_ROOT\\mingw32\\bin\\g++"
echo >> $GITHUB_ENV AR="$MSYS_ROOT\\mingw32\\bin\\ar"
echo >> $GITHUB_ENV CFLAGS="$CFLAGS -I$MSYS_ROOT\\mingw32\\include"
echo >> $GITHUB_ENV CXXFLAGS="$CXXFLAGS -I$MSYS_ROOT\\mingw32\\include"
echo >> $GITHUB_ENV BINDGEN_EXTRA_CLANG_ARGS="$BINDGEN_EXTRA_CLANG_ARGS -I$MSYS_ROOT\\mingw32\\include"
echo >> $GITHUB_ENV LIBRARY_PATH="$MSYS_ROOT\\mingw32\\lib"
echo >> $GITHUB_ENV LDFLAGS="-L$MSYS_ROOT\\mingw32\\lib"
# Force cmake-rs to use the MinGW Makefiles generator instead
# of MSYS Makefiles; cmake-rs's default for windows-gnu picks
# the latter, which then can't find `cl.exe` (cmake's default
# Windows compiler) and fails configuration.
echo >> $GITHUB_ENV "CMAKE_GENERATOR=MinGW Makefiles"

- name: Install dependencies
uses: crazy-max/ghaction-chocolatey@v3
Expand Down
16 changes: 15 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ mem_forget = "deny"
unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(capture_keylogs)',
'cfg(test_invalid_len_compilation_fail)',
'cfg(boring_v4)',
] }

[workspace.metadata.release]
Expand All @@ -43,7 +44,20 @@ publish = false
[workspace.dependencies]
anyhow = { version = "1" }
assert_matches = { version = "1" }
boring = { version = "4.3" }
# Accept both `boring` 4.x (>= 4.19, the MSRV for the APIs we use)
# and `boring` 5.x. The active version is picked by cargo's resolver
# based on the lockfile / downstream constraints; quiche's `build.rs`
# detects which one was selected and emits `cfg(boring_v4)` for the
# legacy 4.x case so source code can switch on it. The `boring_v4`
# cfg is meant to be a transitional flag; new code should live in the
# `not(boring_v4)` arm so the cfg can be retired by simply deleting
# the legacy arms once internal users have moved off 4.x. See
# `quiche/src/build.rs` and the `boringssl-boring-crate` feature in
# `quiche/Cargo.toml`.
#
# `boring-sys` uses `links = "boringssl"`, so only one major version
# can ever be in the dep graph at a time.
boring = { version = ">=4.19, <6" }
buffer-pool = { version = "0.2.1", path = "./buffer-pool" }
bytes = { version = "1.11.1" }
crossbeam = { version = "0.8.1", default-features = false }
Expand Down
12 changes: 12 additions & 0 deletions Cross.toml
Comment thread
cjpatton marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,15 @@ pre-build = [
"ln -sf /usr/bin/gcc /usr/local/bin/i686-linux-gnu-gcc",
"ln -sf /usr/bin/g++ /usr/local/bin/i686-linux-gnu-g++",
]

# BoringSSL's x86 assembly requires SSE2. `boring-sys`'s cmake build
# doesn't add `-msse2` for i686 targets (its 32-bit-toolchain.cmake
# sets it as a CACHE STRING, but cmake-rs's `-DCMAKE_C_FLAGS=...` on
# the command line wins), so inject it via the target-scoped
# `CFLAGS`/`CXXFLAGS` env vars. `cc-rs` (and through it, `cmake-rs`)
# honors these and passes them along to BoringSSL's cmake build.
[target.i686-unknown-linux-gnu.env]
passthrough = [
"CFLAGS_i686_unknown_linux_gnu=-msse2 -mfpmath=sse",
"CXXFLAGS_i686_unknown_linux_gnu=-msse2 -mfpmath=sse",
]
16 changes: 16 additions & 0 deletions quiche/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,22 @@ workspace = true
default = ["boringssl-boring-crate"]

# Use the BoringSSL library provided by the boring crate.
#
# Both `boring` 4.x (>= 4.19) and 5.x are supported. Cargo's resolver
# picks the version based on the lockfile and downstream constraints;
# `build.rs` detects which major version was selected at compile time
# and emits a `cfg(boring_v4)` flag for the legacy 4.x case. The two
# majors differ in their default TLS curve list (5.x advertises
# post-quantum key shares) and in their Rust API for raw public
# keys; see `tokio-quiche/src/settings/config.rs` for the per-version
# code. `boring_v4` is a transitional cfg meant to be retired once
# internal users have moved off 4.x.
#
# Downstream code that depends on `boring` directly (e.g. to construct
# an `SslContextBuilder` for `Config::with_boring_ssl_ctx_builder`)
# will resolve to the same `boring` version as quiche, since
# `boring-sys` uses `links = "boringssl"` and cargo allows only one
# copy in the dep graph.
boringssl-boring-crate = ["boring", "foreign-types-shared"]

# Allow client connections to provide a custom DCID when initiating a
Expand Down
8 changes: 4 additions & 4 deletions quiche/examples/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,16 @@ LIBS = $(LIB_DIR)/libquiche.a -lev -ldl -pthread -lm
all: client server http3-client http3-server

client: client.c $(INCLUDE_DIR)/quiche.h $(LIB_DIR)/libquiche.a
$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ $(INCS) $(LIBS)
$(CXX) $(CFLAGS) $(LDFLAGS) -x c $< -x none -o $@ $(INCS) $(LIBS)

server: server.c $(INCLUDE_DIR)/quiche.h $(LIB_DIR)/libquiche.a
$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ $(INCS) $(LIBS)
$(CXX) $(CFLAGS) $(LDFLAGS) -x c $< -x none -o $@ $(INCS) $(LIBS)

http3-client: http3-client.c $(INCLUDE_DIR)/quiche.h $(LIB_DIR)/libquiche.a
$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ $(INCS) $(LIBS)
$(CXX) $(CFLAGS) $(LDFLAGS) -x c $< -x none -o $@ $(INCS) $(LIBS)

http3-server: http3-server.c $(INCLUDE_DIR)/quiche.h $(LIB_DIR)/libquiche.a
$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ $(INCS) $(LIBS)
$(CXX) $(CFLAGS) $(LDFLAGS) -x c $< -x none -o $@ $(INCS) $(LIBS)

$(LIB_DIR)/libquiche.a: $(shell find $(SOURCE_DIR) -type f -name '*.rs')
cd .. && cargo build --target-dir $(BUILD_DIR) --features ffi
Expand Down
3 changes: 3 additions & 0 deletions quiche/include/quiche.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ int quiche_config_load_verify_locations_from_file(quiche_config *config,
int quiche_config_load_verify_locations_from_directory(quiche_config *config,
const char *path);

// Configures the TLS curve preference list (colon-separated, e.g. "X25519MLKEM768:X25519:P-256:P-384").
int quiche_config_set_curves_list(quiche_config *config, const char *curves);

// Configures whether to verify the peer's certificate.
void quiche_config_verify_peer(quiche_config *config, bool v);

Expand Down
88 changes: 88 additions & 0 deletions quiche/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,83 @@ Cflags: -I${{includedir}}
out_file.write_all(output.as_bytes()).unwrap();
}

/// Returns true if cargo resolved `boring` to a 4.x version.
///
/// Walks up from `OUT_DIR` looking for a `Cargo.lock`, then scans it
/// for the `boring` package. We use `Cargo.lock` rather than shelling
/// out to `cargo metadata` because (a) the lockfile is guaranteed to
/// exist at this point in the build, (b) parsing it is cheap and has
/// no extra dependencies, and (c) it avoids re-entering cargo from a
/// build script.
fn detect_boring_v4() -> bool {
Comment thread
ghedo marked this conversation as resolved.
Outdated
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another idea 😅 Could we check boring_sys::BORINGSSL_API_VERSION during build? It looks like v4 and v5 use different versions:
https://docs.rs/boring-sys/4.22.0/boring_sys/constant.BORINGSSL_API_VERSION.html
https://docs.rs/boring-sys/5.1.0/boring_sys/constant.BORINGSSL_API_VERSION.html

and since I don't think we'll ever update BoringSSL in v4 again BORINGSSL_API_VERSION <= 21.

Would require adding boring-sys as build dependency as well (optional in quiche and gated on boringssl-boring-crate), but not entirely sure it would work properly...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about differences in the boring API itself? E.g., in v4 the RPK API is different than in v5. While it's true that BORINGSSL_API_VERSION is sufficient for detecting differences between v4 and v5, this may not be true for v6, or even v5.2.x. It seems like detecting the crate version is strictly more robust, since it always has the information that we need.

let Some(lockfile) = find_cargo_lock() else {
// No lockfile (shouldn't happen in normal cargo builds, but
// be conservative). Assume 5.x — the default and forward-
// looking version. Downstream can fix this by generating a
// lockfile (`cargo generate-lockfile`).
println!(
"cargo:warning=quiche: Cargo.lock not found; assuming boring 5.x"
);
return false;
};

println!("cargo:rerun-if-changed={}", lockfile.display());

let contents = match std::fs::read_to_string(&lockfile) {
Ok(s) => s,
Err(e) => {
println!(
"cargo:warning=quiche: failed to read {}: {e}; assuming boring 5.x",
lockfile.display(),
);
return false;
},
};

// The lockfile is TOML but a regex-light scan is enough: find a
// `[[package]]` whose `name = "boring"` (not "boring-sys") and
// read its `version`.
let mut in_boring = false;
for line in contents.lines() {
let line = line.trim();
if line == "[[package]]" {
in_boring = false;
continue;
}
if line == "name = \"boring\"" {
in_boring = true;
continue;
}
if in_boring {
if let Some(rest) = line.strip_prefix("version = \"") {
let version = rest.trim_end_matches('"');
let major = version.split('.').next().unwrap_or("");
return major == "4";
}
}
}

// `boring` not present in the lockfile (e.g.
// `boringssl-boring-crate` is off). Doesn't matter what we return
// since the `cfg` won't be observed.
false
}

fn find_cargo_lock() -> Option<std::path::PathBuf> {
// Start from `CARGO_MANIFEST_DIR` and walk up. Cargo guarantees
// the lockfile lives at the workspace root, which is an ancestor
// of the manifest dir.
let manifest_dir =
std::path::PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR")?);
for dir in manifest_dir.ancestors() {
let candidate = dir.join("Cargo.lock");
if candidate.is_file() {
return Some(candidate);
}
}
None
}

fn target_dir_path() -> std::path::PathBuf {
let out_dir = std::env::var("OUT_DIR").unwrap();
let out_dir = std::path::Path::new(&out_dir);
Expand All @@ -44,7 +121,18 @@ fn target_dir_path() -> std::path::PathBuf {
}

fn main() {
// Emit `cfg(boring_v4)` if boring version 4.x is detected. This is used to
// pick which APIs to expect and to guide test expectations. (Larger post
// quantum key shares are enabled by default in boring 5.x but not boring
// 4.x.)
//
// The cfg is always registered (even when the backend feature is
// off) so rustc doesn't warn about unknown cfg names.
println!("cargo::rustc-check-cfg=cfg(boring_v4)");
if cfg!(feature = "boringssl-boring-crate") {
if detect_boring_v4() {
Comment thread
cjpatton marked this conversation as resolved.
Outdated
println!("cargo:rustc-cfg=boring_v4");
}
println!("cargo:rustc-link-lib=static=ssl");
println!("cargo:rustc-link-lib=static=crypto");
}
Expand Down
13 changes: 13 additions & 0 deletions quiche/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,19 @@ pub extern "C" fn quiche_config_load_verify_locations_from_directory(
}
}

#[no_mangle]
pub extern "C" fn quiche_config_set_curves_list(
config: &mut Config, curves: *const c_char,
) -> c_int {
let curves = unsafe { ffi::CStr::from_ptr(curves).to_str().unwrap() };

match config.set_curves_list(curves) {
Ok(_) => 0,

Err(e) => e.to_c() as c_int,
}
}

#[no_mangle]
pub extern "C" fn quiche_config_verify_peer(config: &mut Config, v: bool) {
config.verify_peer(v);
Expand Down
5 changes: 4 additions & 1 deletion quiche/src/h3/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3651,7 +3651,10 @@ mod tests {
fn h3_handshake_0rtt() {
let mut buf = [0; 65535];

let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();
// Test drives the handshake one Initial at a time; requires a
// single-Initial ClientHello.
let mut config =
crate::test_utils::config_no_pq(crate::PROTOCOL_VERSION).unwrap();
config
.load_cert_chain_from_pem_file("examples/cert.crt")
.unwrap();
Expand Down
Loading
Loading