Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b3ac7a4
Implement callbacks to configure the sockets used as listeners
DrSloth Mar 13, 2025
5d488ad
Run just fmt
DrSloth Mar 14, 2025
710788a
Execute workspace hack and add feature flags to the configure socket …
DrSloth Mar 14, 2025
9dd70fb
Make ConfigureSocketCallback Send
DrSloth Mar 18, 2025
d929ed1
Make ConfigureSocketCallback also Sync
DrSloth Mar 18, 2025
a7d66e7
fix(http): feature gate
lennartkloock Mar 19, 2025
890d7ef
fix(http): udp socket
lennartkloock Mar 19, 2025
202a9d1
fix(http): `IPV6_V6ONLY` flag
lennartkloock Mar 19, 2025
f6cd705
docs: add changelog file
lennartkloock Mar 19, 2025
64e2f8f
Change shebangs in Justfile from '/bin/bash/' to '/usr/bin/env bash'
DrSloth Mar 20, 2025
e6efd14
Auto merge of https://github.com/ScuffleCloud/scuffle/pull/410 - chan…
scuffle-brawl[bot] Mar 20, 2025
0e3b2a7
Implement callbacks to configure the sockets used as listeners
DrSloth Mar 13, 2025
9609aca
Run just fmt
DrSloth Mar 14, 2025
79ce58f
Execute workspace hack and add feature flags to the configure socket …
DrSloth Mar 14, 2025
1b9ea7e
Make ConfigureSocketCallback Send
DrSloth Mar 18, 2025
97cc48e
Make ConfigureSocketCallback also Sync
DrSloth Mar 18, 2025
8b8dc97
fix(http): feature gate
lennartkloock Mar 19, 2025
c48e43f
fix(http): udp socket
lennartkloock Mar 19, 2025
0f484c8
fix(http): `IPV6_V6ONLY` flag
lennartkloock Mar 19, 2025
358c557
docs: add changelog file
lennartkloock Mar 19, 2025
0739b17
Change the ConfigureSocketCallback to a CreateSocketCallback and star…
DrSloth Mar 24, 2025
08559d9
Adapt unit test
DrSloth Mar 24, 2025
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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions changes.d/pr-407.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[[scuffle-http]]
category = "feat"
description = "add ability to configure sockets using callbacks"
authors = ["@DrSloth", "@lennartkloock"]
1 change: 1 addition & 0 deletions crates/http/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ futures = { version = "0.3.31", default-features = false, features = ["alloc"]}
bon = "3.3.2"
pin-project-lite = "0.2.16"
scuffle-context.workspace = true
socket2 = "0.5.8"

# HTTP parsing
http = "1.2.0"
Expand Down
24 changes: 23 additions & 1 deletion crates/http/src/backend/h3/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use tracing::Instrument;
use utils::copy_response_body;

use crate::error::Error;
use crate::server::ConfigureSocketCallback;
use crate::service::{HttpService, HttpServiceFactory};

pub mod body;
Expand All @@ -36,6 +37,8 @@ pub struct Http3Backend<F> {
/// Use `[::]` for a dual-stack listener.
/// For example, use `[::]:80` to bind to port 80 on both IPv4 and IPv6.
bind: SocketAddr,
/// Callback to configure socket
configure_sock: Option<ConfigureSocketCallback>,
/// rustls config.
///
/// Use this field to set the server into TLS mode.
Expand Down Expand Up @@ -67,7 +70,26 @@ where
let server_config = h3_quinn::quinn::ServerConfig::with_crypto(Arc::new(crypto));

// Bind the UDP socket
let socket = std::net::UdpSocket::bind(self.bind)?;
let socket = {
let mut sock = socket2::Socket::new(
match self.bind {
SocketAddr::V4(_) => socket2::Domain::IPV4,
SocketAddr::V6(_) => socket2::Domain::IPV6,
},
socket2::Type::DGRAM,
Some(socket2::Protocol::UDP),
)?;

sock.set_nonblocking(true)?;

if let Some(cfg_fn) = self.configure_sock.as_ref() {
Comment thread
philipch07 marked this conversation as resolved.
Outdated
sock = cfg_fn.call(sock)?;
}

sock.bind(&socket2::SockAddr::from(self.bind))?;

std::net::UdpSocket::from(sock)
};

// Runtime for the quinn endpoint
let runtime = h3_quinn::quinn::default_runtime().ok_or_else(|| io::Error::other("no async runtime found"))?;
Expand Down
25 changes: 24 additions & 1 deletion crates/http/src/backend/hyper/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use scuffle_context::ContextFutExt;
use tracing::Instrument;

use crate::error::Error;
use crate::server::ConfigureSocketCallback;
use crate::service::{HttpService, HttpServiceFactory};

mod handler;
Expand All @@ -33,6 +34,8 @@ pub struct HyperBackend<F> {
/// Use `[::]` for a dual-stack listener.
/// For example, use `[::]:80` to bind to port 80 on both IPv4 and IPv6.
bind: SocketAddr,
/// Callback to configure socket
configure_sock: Option<ConfigureSocketCallback>,
/// rustls config.
///
/// Use this field to set the server into TLS mode.
Expand Down Expand Up @@ -79,7 +82,27 @@ where
}

// We have to create an std listener first because the tokio listener isn't clonable
let listener = tokio::net::TcpListener::bind(self.bind).await?.into_std()?;
let listener = {

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.

I suspect this might not work on windows. Previously when adding windows support we found that if the listener was constructed outside of tokio it would block the eventloop even if non-blocking was set to true. We didnt investigate this further than that, but perhaps this might be a good time to understand why this behaviour was the case when using std::net::TcpListener::bind

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.

I have run just fmt now. I haven't had this problem on windows yet at least when using the socket2 crate but my time spent in windows is not a lot.

let mut sock = socket2::Socket::new(
match self.bind {
SocketAddr::V4(_) => socket2::Domain::IPV4,
SocketAddr::V6(_) => socket2::Domain::IPV6,
},
socket2::Type::STREAM,
Some(socket2::Protocol::TCP),
)?;

sock.set_nonblocking(true)?;

if let Some(cfg_fn) = self.configure_sock.as_ref() {
Comment thread
philipch07 marked this conversation as resolved.
Outdated
sock = cfg_fn.call(sock)?;
}

sock.bind(&socket2::SockAddr::from(self.bind))?;
sock.listen(128)?;

std::net::TcpListener::from(sock)
};

#[cfg(feature = "tls-rustls")]
let tls_acceptor = self
Expand Down
2 changes: 1 addition & 1 deletion crates/http/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ pub mod service;

pub use http;
pub use http::Response;
pub use server::{HttpServer, HttpServerBuilder};
pub use server::{ConfigureSocketCallback, HttpServer, HttpServerBuilder};

/// An incoming request.
pub type IncomingRequest = http::Request<body::IncomingBody>;
Expand Down
38 changes: 38 additions & 0 deletions crates/http/src/server.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::fmt::Debug;
use std::net::SocketAddr;
use std::sync::Arc;

use crate::error::Error;
use crate::service::{HttpService, HttpServiceFactory};
Expand Down Expand Up @@ -40,6 +41,14 @@ pub struct HttpServer<F> {
#[cfg(feature = "http3")]
#[cfg_attr(docsrs, doc(cfg(feature = "http3")))]
enable_http3: bool,
/// Callback to configure socket used for http1 and http2
#[cfg(any(feature = "http1", feature = "http2"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
configure_h12_sock: Option<ConfigureSocketCallback>,

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.

can we make bon use an Into impl for this and then impl this for any Fn<...>

/// Callback to configure socket used for http3
#[cfg(feature = "http3")]
#[cfg_attr(docsrs, doc(cfg(feature = "http3")))]
configure_h3_sock: Option<ConfigureSocketCallback>,
/// rustls config.
///
/// Use this field to set the server into TLS mode.
Expand Down Expand Up @@ -213,6 +222,7 @@ where
.service_factory(self.service_factory)
.bind(self.bind)
.rustls_config(_rustls_config)
.maybe_configure_sock(self.configure_h3_sock.clone())
.build();

return backend.run().await;
Expand All @@ -224,6 +234,7 @@ where
.worker_tasks(self.worker_tasks)
.service_factory(self.service_factory)
.bind(self.bind)
.maybe_configure_sock(self.configure_h12_sock.clone())
.rustls_config(_rustls_config);

#[cfg(feature = "http1")]
Expand All @@ -241,6 +252,7 @@ where
.worker_tasks(self.worker_tasks)
.service_factory(self.service_factory.clone())
.bind(self.bind)
.maybe_configure_sock(self.configure_h12_sock.clone())
.rustls_config(_rustls_config.clone());

#[cfg(feature = "http1")]
Expand All @@ -256,6 +268,7 @@ where
.worker_tasks(self.worker_tasks)
.service_factory(self.service_factory)
.bind(self.bind)
.maybe_configure_sock(self.configure_h3_sock.clone())
.rustls_config(_rustls_config)
.build()
.run();
Expand Down Expand Up @@ -283,6 +296,7 @@ where
.ctx(self.ctx)
.worker_tasks(self.worker_tasks)
.service_factory(self.service_factory)
.maybe_configure_sock(self.configure_h12_sock.clone())
.bind(self.bind);

#[cfg(feature = "http1")]
Expand All @@ -297,3 +311,27 @@ where
Ok(())
}
}

/// A callback used to configure a socket2 instance.
///
/// This can be used to tweak options on the TCP/UDP layer
#[derive(Clone)]
pub struct ConfigureSocketCallback(Arc<dyn Fn(socket2::Socket) -> std::io::Result<socket2::Socket> + Send + Sync>);

impl ConfigureSocketCallback {
/// Create a new `ConfigureSocketCallback` from the given callback function.
pub fn new<F: Fn(socket2::Socket) -> std::io::Result<socket2::Socket> + 'static + Send + Sync>(f: F) -> Self {
Self(Arc::new(f))
}

/// Create a new `ConfigureSocketCallback` from the given callback function.
pub fn call(&self, sock: socket2::Socket) -> std::io::Result<socket2::Socket> {
(self.0)(sock)
}
}

impl std::fmt::Debug for ConfigureSocketCallback {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "ConfigureSocketCallback ")

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.

extra space here

}
}
12 changes: 12 additions & 0 deletions crates/workspace-hack/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ quinn-proto = { version = "0.11", default-features = false, features = ["log", "
quinn-udp = { version = "0.5", default-features = false, features = ["log"] }
rustls = { version = "0.23", default-features = false, features = ["ring"] }
rustls-webpki = { version = "0.102", default-features = false, features = ["aws_lc_rs", "ring", "std"] }
socket2 = { version = "0.5", default-features = false, features = ["all"] }
stable_deref_trait = { version = "1" }
tokio-rustls = { version = "0.26", default-features = false, features = ["aws_lc_rs", "logging", "ring", "tls12"] }
tower = { version = "0.5", default-features = false, features = ["timeout"] }
Expand All @@ -159,6 +160,7 @@ quinn-proto = { version = "0.11", default-features = false, features = ["log", "
quinn-udp = { version = "0.5", default-features = false, features = ["log"] }
rustls = { version = "0.23", default-features = false, features = ["ring"] }
rustls-webpki = { version = "0.102", default-features = false, features = ["aws_lc_rs", "ring", "std"] }
socket2 = { version = "0.5", default-features = false, features = ["all"] }
stable_deref_trait = { version = "1" }
tokio-rustls = { version = "0.26", default-features = false, features = ["aws_lc_rs", "logging", "ring", "tls12"] }
tower = { version = "0.5", default-features = false, features = ["timeout"] }
Expand All @@ -178,6 +180,7 @@ quinn-proto = { version = "0.11", default-features = false, features = ["log", "
quinn-udp = { version = "0.5", default-features = false, features = ["log"] }
rustls = { version = "0.23", default-features = false, features = ["ring"] }
rustls-webpki = { version = "0.102", default-features = false, features = ["aws_lc_rs", "ring", "std"] }
socket2 = { version = "0.5", default-features = false, features = ["all"] }
stable_deref_trait = { version = "1" }
tokio-rustls = { version = "0.26", default-features = false, features = ["aws_lc_rs", "logging", "ring", "tls12"] }
tower = { version = "0.5", default-features = false, features = ["timeout"] }
Expand All @@ -198,6 +201,7 @@ quinn-proto = { version = "0.11", default-features = false, features = ["log", "
quinn-udp = { version = "0.5", default-features = false, features = ["log"] }
rustls = { version = "0.23", default-features = false, features = ["ring"] }
rustls-webpki = { version = "0.102", default-features = false, features = ["aws_lc_rs", "ring", "std"] }
socket2 = { version = "0.5", default-features = false, features = ["all"] }
stable_deref_trait = { version = "1" }
tokio-rustls = { version = "0.26", default-features = false, features = ["aws_lc_rs", "logging", "ring", "tls12"] }
tower = { version = "0.5", default-features = false, features = ["timeout"] }
Expand All @@ -218,6 +222,7 @@ quinn-proto = { version = "0.11", default-features = false, features = ["log", "
quinn-udp = { version = "0.5", default-features = false, features = ["log"] }
rustls = { version = "0.23", default-features = false, features = ["ring"] }
rustls-webpki = { version = "0.102", default-features = false, features = ["aws_lc_rs", "ring", "std"] }
socket2 = { version = "0.5", default-features = false, features = ["all"] }
stable_deref_trait = { version = "1" }
tokio-rustls = { version = "0.26", default-features = false, features = ["aws_lc_rs", "logging", "ring", "tls12"] }
tower = { version = "0.5", default-features = false, features = ["timeout"] }
Expand All @@ -239,6 +244,7 @@ quinn-proto = { version = "0.11", default-features = false, features = ["log", "
quinn-udp = { version = "0.5", default-features = false, features = ["log"] }
rustls = { version = "0.23", default-features = false, features = ["ring"] }
rustls-webpki = { version = "0.102", default-features = false, features = ["aws_lc_rs", "ring", "std"] }
socket2 = { version = "0.5", default-features = false, features = ["all"] }
stable_deref_trait = { version = "1" }
tokio-rustls = { version = "0.26", default-features = false, features = ["aws_lc_rs", "logging", "ring", "tls12"] }
tower = { version = "0.5", default-features = false, features = ["timeout"] }
Expand All @@ -259,6 +265,7 @@ quinn-proto = { version = "0.11", default-features = false, features = ["log", "
quinn-udp = { version = "0.5", default-features = false, features = ["log"] }
rustls = { version = "0.23", default-features = false, features = ["ring"] }
rustls-webpki = { version = "0.102", default-features = false, features = ["aws_lc_rs", "ring", "std"] }
socket2 = { version = "0.5", default-features = false, features = ["all"] }
stable_deref_trait = { version = "1" }
tokio-rustls = { version = "0.26", default-features = false, features = ["aws_lc_rs", "logging", "ring", "tls12"] }
tower = { version = "0.5", default-features = false, features = ["timeout"] }
Expand All @@ -280,6 +287,7 @@ quinn-proto = { version = "0.11", default-features = false, features = ["log", "
quinn-udp = { version = "0.5", default-features = false, features = ["log"] }
rustls = { version = "0.23", default-features = false, features = ["ring"] }
rustls-webpki = { version = "0.102", default-features = false, features = ["aws_lc_rs", "ring", "std"] }
socket2 = { version = "0.5", default-features = false, features = ["all"] }
stable_deref_trait = { version = "1" }
tokio-rustls = { version = "0.26", default-features = false, features = ["aws_lc_rs", "logging", "ring", "tls12"] }
tower = { version = "0.5", default-features = false, features = ["timeout"] }
Expand All @@ -295,6 +303,7 @@ quinn-proto = { version = "0.11", default-features = false, features = ["log", "
quinn-udp = { version = "0.5", default-features = false, features = ["log"] }
rustls = { version = "0.23", default-features = false, features = ["ring"] }
rustls-webpki = { version = "0.102", default-features = false, features = ["aws_lc_rs", "ring", "std"] }
socket2 = { version = "0.5", default-features = false, features = ["all"] }
tokio-rustls = { version = "0.26", default-features = false, features = ["aws_lc_rs", "logging", "ring", "tls12"] }
tokio-stream = { version = "0.1", features = ["sync"] }
tower = { version = "0.5", default-features = false, features = ["timeout"] }
Expand All @@ -312,6 +321,7 @@ quinn-proto = { version = "0.11", default-features = false, features = ["log", "
quinn-udp = { version = "0.5", default-features = false, features = ["log"] }
rustls = { version = "0.23", default-features = false, features = ["ring"] }
rustls-webpki = { version = "0.102", default-features = false, features = ["aws_lc_rs", "ring", "std"] }
socket2 = { version = "0.5", default-features = false, features = ["all"] }
tokio-rustls = { version = "0.26", default-features = false, features = ["aws_lc_rs", "logging", "ring", "tls12"] }
tokio-stream = { version = "0.1", features = ["sync"] }
tower = { version = "0.5", default-features = false, features = ["timeout"] }
Expand All @@ -328,6 +338,7 @@ quinn-proto = { version = "0.11", default-features = false, features = ["log", "
quinn-udp = { version = "0.5", default-features = false, features = ["log"] }
rustls = { version = "0.23", default-features = false, features = ["ring"] }
rustls-webpki = { version = "0.102", default-features = false, features = ["aws_lc_rs", "ring", "std"] }
socket2 = { version = "0.5", default-features = false, features = ["all"] }
tokio-rustls = { version = "0.26", default-features = false, features = ["aws_lc_rs", "logging", "ring", "tls12"] }
tokio-stream = { version = "0.1", features = ["sync"] }
tower = { version = "0.5", default-features = false, features = ["timeout"] }
Expand All @@ -345,6 +356,7 @@ quinn-proto = { version = "0.11", default-features = false, features = ["log", "
quinn-udp = { version = "0.5", default-features = false, features = ["log"] }
rustls = { version = "0.23", default-features = false, features = ["ring"] }
rustls-webpki = { version = "0.102", default-features = false, features = ["aws_lc_rs", "ring", "std"] }
socket2 = { version = "0.5", default-features = false, features = ["all"] }
tokio-rustls = { version = "0.26", default-features = false, features = ["aws_lc_rs", "logging", "ring", "tls12"] }
tokio-stream = { version = "0.1", features = ["sync"] }
tower = { version = "0.5", default-features = false, features = ["timeout"] }
Expand Down