aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/tor-dircommon/src/dir_plugin_backend.rs2
-rw-r--r--crates/tor-dirserver/Cargo.toml7
-rw-r--r--crates/tor-dirserver/src/http.rs93
-rw-r--r--crates/tor-dirserver/src/mirror.rs41
4 files changed, 139 insertions, 4 deletions
diff --git a/crates/tor-dircommon/src/dir_plugin_backend.rs b/crates/tor-dircommon/src/dir_plugin_backend.rs
index 3ac4521d8..8b19cc1b8 100644
--- a/crates/tor-dircommon/src/dir_plugin_backend.rs
+++ b/crates/tor-dircommon/src/dir_plugin_backend.rs
@@ -9,7 +9,7 @@
pub use http;
/// An object that knows how to handle one or more kinds of directory requests.
-pub trait DirBackendPlugin {
+pub trait DirBackendPlugin: Send + Sync + 'static {
/// Handle a GET request.
///
/// Returns an http Response if the request is recognized,
diff --git a/crates/tor-dirserver/Cargo.toml b/crates/tor-dirserver/Cargo.toml
index b1080d4cd..0b744f74d 100644
--- a/crates/tor-dirserver/Cargo.toml
+++ b/crates/tor-dirserver/Cargo.toml
@@ -24,6 +24,9 @@ full = [
"tor-llcrypto/full",
"tor-checkable/full",
]
+experimental = ["dir-plugin-backend"]
+dir-plugin-backend = ["__is_experimental"]
+__is_experimental = []
[dependencies]
bytes = "1.10.1"
@@ -33,6 +36,7 @@ futures = "0.3.31"
hex = "0.4.3"
http = "1.3.1"
http-body = "1.0.1"
+http-body-util = "0.1.3"
hyper = { version = "1.7.0", default-features = false, features = ["server", "http1"] }
hyper-util = { version = "0.1.16", features = ["full"] }
lzma-rs = "0.3.0"
@@ -51,7 +55,7 @@ tokio-util = { version = "0.7.16", features = ["compat"] }
tor-basic-utils = { version = "0.45.0", path = "../tor-basic-utils" }
tor-checkable = { path = "../tor-checkable", version = "0.45.0" }
tor-dirclient = { version = "0.45.0", path = "../tor-dirclient" }
-tor-dircommon = { version = "0.45.0", path = "../tor-dircommon" }
+tor-dircommon = { version = "0.45.0", path = "../tor-dircommon", features = ["experimental"] }
tor-error = { version = "0.45.0", path = "../tor-error" }
tor-llcrypto = { version = "0.45.0", path = "../tor-llcrypto" }
tor-netdoc = { version = "0.45.0", path = "../tor-netdoc", features = ["full", "experimental"] }
@@ -61,7 +65,6 @@ weak-table = "0.4.0"
zstd = "0.13.3"
[dev-dependencies]
-http-body-util = "0.1.3"
lazy_static = "1.5.0"
tempfile = "3.23.0"
tokio-stream = { version = "0.1.17", features = ["full"] }
diff --git a/crates/tor-dirserver/src/http.rs b/crates/tor-dirserver/src/http.rs
index ce53a4fe5..eded32780 100644
--- a/crates/tor-dirserver/src/http.rs
+++ b/crates/tor-dirserver/src/http.rs
@@ -6,6 +6,8 @@
use cache::StoreCache;
use r2d2::Pool;
use r2d2_sqlite::SqliteConnectionManager;
+#[cfg(feature = "dir-plugin-backend")]
+use tor_dircommon::dir_plugin_backend::DirBackendPlugin;
use tor_error::internal;
use std::{
@@ -36,6 +38,11 @@ use tokio::{
};
use tracing::warn;
+#[cfg(feature = "dir-plugin-backend")]
+use http_body_util::Full;
+#[cfg(feature = "dir-plugin-backend")]
+use std::io::Cursor;
+
use crate::database::{self, ContentEncoding, DocumentId, sql};
mod cache;
@@ -140,6 +147,92 @@ impl HttpServer {
Self { endpoints, pool }
}
+ /// Bluntly launches an HTTP server only serving from the given backend.
+ ///
+ /// Absolutely not suited for anything in production as it comes with
+ /// various limitations. Primarily intended as an intermediate abstraction
+ /// for relay development.
+ #[cfg(feature = "dir-plugin-backend")]
+ pub(crate) async fn serve_backend<I, S, E, B>(
+ mut listener: I,
+ backend: B,
+ ) -> Result<(), tor_error::Bug>
+ where
+ I: Stream<Item = Result<S, E>> + Unpin,
+ S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
+ E: std::error::Error,
+ B: DirBackendPlugin,
+ {
+ // Creates a failing HTTP resposne while satisfying the hyper requirements.
+ let failure = |code| -> _ {
+ Response::builder()
+ .status(code)
+ .body(Default::default())
+ .expect("response builder should not fail")
+ };
+
+ // We need to wrap the backend as an Arc, as the value would otherwise
+ // not live long enough.
+ let backend = Arc::new(backend);
+ let mut tasks: JoinSet<Result<(), hyper::Error>> = JoinSet::new();
+ loop {
+ tokio::select! {
+ res = listener.next() => match res {
+ // Connection successfully accepted.
+ Some(Ok(s)) => {
+ let stream = TokioIo::new(s);
+
+ // Two Arc clones required. First is to be able to run
+ // this in an endless loop and second one is required
+ // because hyper requires the function to be Fn, i.e.
+ // meaning it may not capture from it's surrounding
+ // state.
+ let backend = backend.clone();
+ let service = service_fn(move |requ: Request<Incoming>| {
+ let backend = backend.clone();
+ async move {
+ if requ.method() != Method::GET {
+ warn!("Unsupported method: {}", requ.method());
+ // dir-spec does not allow StatusCode::METHOD_NOT_ALLOWED.
+ return Ok(failure(StatusCode::BAD_REQUEST));
+ }
+ if !requ.body().is_end_stream() {
+ warn!("HTTP GET with non-empty body?");
+ return Ok(failure(StatusCode::BAD_REQUEST));
+ }
+ // Convert Request::<Incoming> to Request::<()>.
+ let requ = requ.map(|_| ());
+
+ // Convert the Box<[u8]> to something hyper accepts.
+ backend
+ .get(&requ)
+ .map(|resp| resp.map(|body| Full::new(Cursor::new(body))))
+ }
+ });
+ tasks.spawn(http1::Builder::new().serve_connection(stream, service));
+ },
+
+ // There has been an error in accepting the connection.
+ Some(Err(e)) => {
+ warn!("listener accept failure: {e}");
+ continue;
+ }
+
+ // This should not happen due to ownership.
+ None => return Err(internal!("listener was closed externally?")),
+ },
+
+ // A hyper task we monitored in our tasks has exiteed.
+ Some(res) = tasks.join_next() => match res {
+ Ok(Ok(())) => {},
+ Ok(Err(e)) => warn!("client task encountered an error: {e}"),
+ Err(e) => warn!("client task exited ungracefully: {e}"),
+ },
+
+ }
+ }
+ }
+
/// Runs the server endlessly in the current task.
///
/// This function does not fail, because all errors that could potentially
diff --git a/crates/tor-dirserver/src/mirror.rs b/crates/tor-dirserver/src/mirror.rs
index 5c26ae4d2..f82024757 100644
--- a/crates/tor-dirserver/src/mirror.rs
+++ b/crates/tor-dirserver/src/mirror.rs
@@ -52,6 +52,9 @@ use tor_dircommon::{
config::{DirTolerance, DownloadScheduleConfig},
};
+#[cfg(feature = "dir-plugin-backend")]
+use tor_dircommon::dir_plugin_backend::DirBackendPlugin;
+
mod operation;
/// Core data type of a directory mirror.
@@ -87,6 +90,43 @@ pub struct DirMirror {
tolerance: DirTolerance,
}
+/// Insecure [`DirMirror`] abstraction providing a custom backend.
+///
+/// Intended for relay development as a medium-term abstraction.
+#[cfg(feature = "dir-plugin-backend")]
+#[non_exhaustive]
+pub struct DirMirrorWithBackend<B> {
+ /// The original [`DirMirror`].
+ mirror: DirMirror,
+ /// The backend to use for handling requests instead.
+ backend: B,
+}
+
+#[cfg(feature = "dir-plugin-backend")]
+impl<B: DirBackendPlugin> DirMirrorWithBackend<B> {
+ /// Creates a new [`DirMirrorWithBackend`] from a given [`DirMirror`] and
+ /// a given [`DirBackendPlugin`].
+ pub fn new(mirror: DirMirror, backend: B) -> Self {
+ Self { mirror, backend }
+ }
+
+ /// Consumes this [`DirMirror`] by running endlessly in the current task.
+ ///
+ /// Be aware of the limitations and also see [`DirMirror::serve()`].
+ pub async fn serve<S, T, E>(self, listener: S) -> Result<(), Infallible>
+ where
+ S: Stream<Item = Result<T, E>> + Unpin,
+ T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
+ E: std::error::Error,
+ {
+ let res = crate::http::HttpServer::serve_backend(listener, self.backend).await;
+ if let Err(e) = res {
+ tracing::error!("HTTP backend failed unexpectedly: {e}");
+ }
+ Ok(())
+ }
+}
+
impl DirMirror {
/// Creates a new [`DirMirror`] with a given set of configuration options.
///
@@ -152,7 +192,6 @@ impl DirMirror {
// the stream over to DirMirror::serve().
//
// See https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/4222#note_3437135
- #[allow(clippy::unused_async)] // TODO
pub async fn serve<S, T, E>(self, mut listener: S) -> Result<(), Infallible>
where
S: Stream<Item = Result<T, E>> + Unpin,