├── src ├── config.rs ├── macros.rs ├── flected │ ├── opt.rs │ ├── mod.rs │ ├── file.rs │ ├── routes.rs │ ├── body.rs │ └── routes │ │ ├── index.rs │ │ └── paths.rs ├── vealed │ ├── mod.rs │ ├── opt.rs │ └── forwarder.rs ├── layed │ ├── config.rs │ ├── backoff.rs │ ├── magic.rs │ ├── heartbeat.rs │ ├── opt.rs │ ├── mod.rs │ ├── stream.rs │ ├── client.rs │ └── server.rs ├── directed │ ├── mod.rs │ ├── opt.rs │ ├── routes.rs │ └── redir.rs ├── future.rs ├── body.rs ├── transmitted │ ├── path.rs │ ├── opt.rs │ ├── mod.rs │ └── routes.rs ├── websocket.rs ├── tcp.rs ├── err.rs ├── main.rs ├── opt.rs └── http.rs ├── README.md ├── .gitignore ├── LICENSE ├── Cargo.toml ├── .github └── workflows │ └── ci.yml └── Cargo.lock /src/config.rs: -------------------------------------------------------------------------------- 1 | pub const COPY_BUFFER_SIZE: usize = 64 * 1024; 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # re 2 | 3 | Network utility toolkit. re{layed,flected,directed,transmitted,vealed} 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # These are backup files generated by rustfmt 6 | **/*.rs.bk 7 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | macro_rules! case { 3 | ( $name:ident : $body:expr ) => { 4 | #[test] 5 | fn $name() { 6 | $body 7 | } 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /src/flected/opt.rs: -------------------------------------------------------------------------------- 1 | use clap::Args; 2 | use std::net::SocketAddr; 3 | 4 | /// Temporarily upload and serve files from memory 5 | #[derive(Args, Debug)] 6 | pub struct Options { 7 | /// Socket address to listen on 8 | pub listen: SocketAddr, 9 | } 10 | -------------------------------------------------------------------------------- /src/vealed/mod.rs: -------------------------------------------------------------------------------- 1 | mod forwarder; 2 | pub mod opt; 3 | 4 | pub async fn main(options: opt::Options) -> Result<(), std::io::Error> { 5 | let opt::Options { listen, to } = options; 6 | 7 | forwarder::run(listen, &to).await?; 8 | 9 | Ok(()) 10 | } 11 | -------------------------------------------------------------------------------- /src/vealed/opt.rs: -------------------------------------------------------------------------------- 1 | use crate::opt::SocketAddrsFromDns; 2 | use clap::Args; 3 | use std::net::SocketAddr; 4 | 5 | /// Forward TCP connections somewhere else 6 | #[derive(Args, Debug)] 7 | pub struct Options { 8 | /// Socket address to listen on 9 | pub listen: SocketAddr, 10 | 11 | /// Address to forward connections to 12 | #[arg(short, long)] 13 | pub to: SocketAddrsFromDns, 14 | } 15 | -------------------------------------------------------------------------------- /src/flected/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::err::Error; 2 | use crate::flected::routes::respond_to_request; 3 | use crate::http; 4 | 5 | mod body; 6 | mod file; 7 | pub mod opt; 8 | mod routes; 9 | 10 | pub async fn main(options: opt::Options) -> Result<(), Error> { 11 | let opt::Options { listen } = options; 12 | 13 | http::run_simple_server(listen, Default::default(), respond_to_request).await?; 14 | 15 | Ok(()) 16 | } 17 | -------------------------------------------------------------------------------- /src/layed/config.rs: -------------------------------------------------------------------------------- 1 | use std::ops::RangeInclusive; 2 | use std::time::Duration; 3 | 4 | pub const QUEUE_TIMEOUT: Duration = Duration::from_secs(60); 5 | pub const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(5); 6 | pub const HEARTBEAT_TIMEOUT: Duration = Duration::from_secs(10); 7 | 8 | pub const SERVER_ACCEPT_BACKOFF_SECS: RangeInclusive = 1..=64; 9 | pub const CLIENT_BACKOFF_SECS: RangeInclusive = 1..=64; 10 | -------------------------------------------------------------------------------- /src/directed/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::directed::redir::Rules; 2 | use crate::directed::routes::{State, respond_to_request}; 3 | use crate::err::Error; 4 | use crate::http; 5 | 6 | pub mod opt; 7 | mod redir; 8 | mod routes; 9 | 10 | pub async fn main(options: opt::Options) -> Result<(), Error> { 11 | let opt::Options { listen, from, to } = options; 12 | 13 | let state = State { 14 | client: http::make_client()?, 15 | rules: Rules::zip(from, to)?, 16 | }; 17 | 18 | http::run_simple_server(listen, state, respond_to_request).await?; 19 | 20 | Ok(()) 21 | } 22 | -------------------------------------------------------------------------------- /src/future.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | 3 | /// Like futures::future::select_ok, but evaluates each future sequentially, instead of in parallel. 4 | pub async fn first_ok(iter: impl IntoIterator) -> Result 5 | where 6 | F: Future>, 7 | { 8 | let mut last_error = None; 9 | for fut in iter { 10 | match fut.await { 11 | Ok(x) => return Ok(x), 12 | Err(e) => last_error = Some(e), 13 | } 14 | } 15 | match last_error { 16 | Some(e) => Err(e), 17 | None => panic!("select_ok: no elements"), 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/body.rs: -------------------------------------------------------------------------------- 1 | use futures::TryStreamExt; 2 | use http_body_util::StreamBody; 3 | use http_body_util::combinators::BoxBody; 4 | use http_body_util::{BodyExt, Empty}; 5 | use hyper::body::{Body, Bytes, Frame}; 6 | use std::io; 7 | use tokio::fs::File; 8 | use tokio_util::io::ReaderStream; 9 | 10 | pub fn empty() -> BoxBody { 11 | Empty::::new() 12 | .map_err(|never| match never {}) 13 | .boxed() 14 | } 15 | 16 | pub fn from_file(file: File) -> impl Body { 17 | let stream = ReaderStream::with_capacity(file, 64 * 1024); 18 | StreamBody::new(stream.map_ok(Frame::data)) 19 | } 20 | -------------------------------------------------------------------------------- /src/layed/backoff.rs: -------------------------------------------------------------------------------- 1 | use std::ops::RangeInclusive; 2 | 3 | pub struct Backoff { 4 | value: u8, 5 | min: u8, 6 | max: u8, 7 | } 8 | 9 | impl Backoff { 10 | pub fn new(range: RangeInclusive) -> Self { 11 | Backoff { 12 | value: *range.start(), 13 | min: *range.start(), 14 | max: *range.end(), 15 | } 16 | } 17 | 18 | pub fn next(&mut self) -> u8 { 19 | let old_value = self.value; 20 | self.value = old_value.saturating_mul(2).min(self.max); 21 | old_value 22 | } 23 | 24 | pub fn reset(&mut self) { 25 | self.value = self.min; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/transmitted/path.rs: -------------------------------------------------------------------------------- 1 | use hyper::Uri; 2 | use hyper::http::uri::InvalidUri; 3 | use std::borrow::Cow; 4 | use std::str::FromStr; 5 | 6 | pub fn extract_uri_from_path(uri: &Uri) -> Option)>> { 7 | let path = uri.path_and_query()?.as_str().strip_prefix('/')?; 8 | 9 | let unparsed = if path.starts_with("https:") || path.starts_with("http:") { 10 | Cow::Borrowed(path) 11 | } else { 12 | // if no protocol provided, default to https: 13 | Cow::Owned(format!("https://{}", path)) 14 | }; 15 | 16 | Some(match Uri::from_str(&unparsed) { 17 | Ok(u) => Ok(u), 18 | Err(e) => Err((e, unparsed)), 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /src/layed/magic.rs: -------------------------------------------------------------------------------- 1 | use crate::layed::config::HANDSHAKE_TIMEOUT; 2 | use std::io; 3 | use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; 4 | use tokio::time::timeout; 5 | 6 | const MAGIC: [u8; 1] = [42]; 7 | 8 | pub async fn read_from(mut reader: impl AsyncRead + Unpin) -> Result<(), io::Error> { 9 | let mut buf = [0; 1]; 10 | timeout(HANDSHAKE_TIMEOUT, reader.read_exact(&mut buf)).await??; 11 | match buf { 12 | MAGIC => Ok(()), 13 | _ => Err(io::ErrorKind::InvalidData.into()), 14 | } 15 | } 16 | 17 | pub async fn write_to(mut writer: impl AsyncWrite + Unpin) -> Result<(), io::Error> { 18 | writer.write_all(&MAGIC).await?; 19 | writer.flush().await?; 20 | Ok(()) 21 | } 22 | -------------------------------------------------------------------------------- /src/websocket.rs: -------------------------------------------------------------------------------- 1 | use http::Uri; 2 | use std::io; 3 | use tokio::io::{AsyncRead, AsyncWrite}; 4 | use tokio::net::TcpListener; 5 | use tokio_tungstenite::{accept_async_with_config, connect_async_with_config}; 6 | 7 | use crate::tcp; 8 | 9 | pub async fn connect(url: &Uri) -> Result, io::Error> { 10 | let set_nodelay = true; 11 | let (stream, _) = connect_async_with_config(url, None, set_nodelay) 12 | .await 13 | .map_err(io::Error::other)?; 14 | 15 | Ok(stream.into_inner()) 16 | } 17 | 18 | pub async fn accept( 19 | listener: &mut TcpListener, 20 | ) -> Result, io::Error> { 21 | let stream = tcp::accept(listener).await?; 22 | 23 | let stream = accept_async_with_config(stream, None) 24 | .await 25 | .map_err(io::Error::other)?; 26 | 27 | Ok(stream.into_inner()) 28 | } 29 | -------------------------------------------------------------------------------- /src/tcp.rs: -------------------------------------------------------------------------------- 1 | use crate::err::{AppliesTo, IoErrorExt}; 2 | use crate::future::first_ok; 3 | use std::io; 4 | use std::net::SocketAddr; 5 | use tokio::net::{TcpListener, TcpStream}; 6 | 7 | pub async fn connect(addrs: &[SocketAddr]) -> Result { 8 | let stream = first_ok(addrs.iter().map(TcpStream::connect)).await?; 9 | stream.set_nodelay(true)?; 10 | Ok(stream) 11 | } 12 | 13 | pub async fn accept(listener: &mut TcpListener) -> Result { 14 | loop { 15 | match listener.accept().await { 16 | Ok((stream, _addr)) => { 17 | stream.set_nodelay(true)?; 18 | return Ok(stream); 19 | } 20 | Err(e) => match e.applies_to() { 21 | AppliesTo::Connection => log::debug!("Aborted connection dropped: {}", e), 22 | AppliesTo::Listener => return Err(e), 23 | }, 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/transmitted/opt.rs: -------------------------------------------------------------------------------- 1 | use clap::Args; 2 | use std::net::SocketAddr; 3 | 4 | #[derive(Args, Debug)] 5 | #[clap( 6 | about = "Simple CORS proxy", 7 | long_about = "Simple CORS proxy 8 | 9 | Clients should provide the target URL in the request path. 10 | Examples: 11 | - GET http://localhost:8080/http://example.com/test.html 12 | - POST http://localhost:8080/example.com/test.html (defaults to https)" 13 | )] 14 | pub struct Options { 15 | /// Socket address to listen on 16 | pub listen: SocketAddr, 17 | 18 | #[command(flatten)] 19 | pub key: KeyOptions, 20 | } 21 | 22 | #[derive(Args, Debug)] 23 | #[group(required = true, multiple = false)] 24 | pub struct KeyOptions { 25 | /// Secret key that clients must provide in the x-retransmitted-key header 26 | #[arg(long)] 27 | pub secret_key: Option, 28 | 29 | /// Allow unauthenticated access with no secret key 30 | #[arg(long)] 31 | pub no_secret_key: bool, 32 | } 33 | -------------------------------------------------------------------------------- /src/flected/file.rs: -------------------------------------------------------------------------------- 1 | use crate::err::Error; 2 | use http_body_util::BodyExt; 3 | use hyper::body::Body; 4 | use memmap2::Mmap; 5 | use tempfile::tempfile; 6 | use tokio::fs::File; 7 | use tokio::io::AsyncWriteExt; 8 | 9 | pub async fn write_to_mmap( 10 | mut body: impl Body + Unpin, 11 | ) -> Result 12 | where 13 | B: AsRef<[u8]>, 14 | Error: From, 15 | { 16 | let file = tempfile()?; 17 | 18 | let mut file = File::from_std(file); 19 | while let Some(frame) = body.frame().await { 20 | let frame = frame?; 21 | if let Some(bytes) = frame.data_ref() { 22 | file.write_all(bytes.as_ref()).await?; 23 | } 24 | } 25 | let file = file.into_std().await; 26 | 27 | // safety: this is an unlinked, exclusive-access temporary file, 28 | // so it cannot be modified or truncated by anyone else 29 | let mmap = unsafe { Mmap::map(&file)? }; 30 | 31 | Ok(mmap) 32 | } 33 | -------------------------------------------------------------------------------- /src/err.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Debug, Display}; 2 | use std::io; 3 | 4 | pub type Error = Box; 5 | 6 | pub struct DisplayError(Error); 7 | 8 | impl Debug for DisplayError { 9 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 10 | Display::fmt(&self.0, f) 11 | } 12 | } 13 | 14 | impl> From for DisplayError { 15 | fn from(display: T) -> Self { 16 | DisplayError(display.into()) 17 | } 18 | } 19 | 20 | pub trait IoErrorExt { 21 | fn applies_to(&self) -> AppliesTo; 22 | } 23 | 24 | impl IoErrorExt for io::Error { 25 | fn applies_to(&self) -> AppliesTo { 26 | match self.kind() { 27 | io::ErrorKind::ConnectionRefused 28 | | io::ErrorKind::ConnectionAborted 29 | | io::ErrorKind::ConnectionReset => AppliesTo::Connection, 30 | _ => AppliesTo::Listener, 31 | } 32 | } 33 | } 34 | 35 | pub enum AppliesTo { 36 | Connection, 37 | Listener, 38 | } 39 | -------------------------------------------------------------------------------- /src/transmitted/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::err::Error; 2 | use crate::http; 3 | use crate::transmitted::routes::{State, respond_to_request}; 4 | use sha2::{Digest, Sha256}; 5 | 6 | pub mod opt; 7 | mod path; 8 | mod routes; 9 | 10 | pub async fn main(options: opt::Options) -> Result<(), Error> { 11 | let opt::Options { 12 | listen, 13 | key: opt::KeyOptions { 14 | secret_key, 15 | no_secret_key, 16 | }, 17 | } = options; 18 | 19 | let state = State { 20 | client: http::make_client()?, 21 | secret_key_hash: match (secret_key, no_secret_key) { 22 | (Some(secret_key), _) => { 23 | let hash = Sha256::digest(secret_key); 24 | Some(Box::from(hash.as_slice())) 25 | } 26 | (None, true) => None, 27 | (None, false) => unreachable!("no secret key but no no_secret_key"), 28 | }, 29 | }; 30 | 31 | http::run_simple_server(listen, state, respond_to_request).await?; 32 | 33 | Ok(()) 34 | } 35 | -------------------------------------------------------------------------------- /src/flected/routes.rs: -------------------------------------------------------------------------------- 1 | use crate::flected::body::BytesBody; 2 | use bytes::Bytes; 3 | use hyper::body::Incoming; 4 | use hyper::{Method, Request, Response, StatusCode}; 5 | use std::collections::BTreeMap; 6 | use tokio::sync::RwLock; 7 | 8 | mod index; 9 | mod paths; 10 | 11 | #[derive(Default)] 12 | pub struct State { 13 | files: RwLock>, 14 | } 15 | 16 | pub async fn respond_to_request(req: Request, state: &State) -> Response { 17 | match *req.method() { 18 | Method::GET if req.uri().path() == "/" => index::get(req, state).await, 19 | Method::GET => paths::get(req, state).await, 20 | Method::POST => paths::post(req, state).await, 21 | Method::DELETE => paths::delete(req, state).await, 22 | _ => { 23 | log::warn!("{} {} -> [method not allowed]", req.method(), req.uri()); 24 | let mut resp = Response::new(BytesBody::empty()); 25 | *resp.status_mut() = StatusCode::METHOD_NOT_ALLOWED; 26 | resp 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 erikdesjardins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "re" 3 | version = "0.5.2" 4 | description = "Network utility toolkit. re{layed,flected,directed,transmitted,vealed}" 5 | edition = "2024" 6 | 7 | [dependencies] 8 | bytes = "1" 9 | clap = { version = "4", features = ["derive"] } 10 | env_logger = { version = "0.11", default-features = false, features = ["humantime"] } 11 | futures = "0.3" 12 | headers = "0.4" 13 | http = "1" 14 | http-body-util = "0.1" 15 | hyper = { version = "1", features = ["client", "server", "http1", "http2"] } 16 | hyper-rustls = { version = "0.27", default-features = false, features = ["native-tokio", "http1", "http2", "tls12", "logging", "ring"] } 17 | hyper-util = { version = "0.1", features = ["client", "server-auto"] } 18 | log = "0.4" 19 | memmap2 = "0.9" 20 | ring = "0.17" 21 | sha2 = "0.10" 22 | tempfile = "3" 23 | thiserror = "2" 24 | tokio = { version = "1", features = ["fs", "io-util", "macros", "net", "rt", "rt-multi-thread", "sync", "time"] } 25 | tokio-tungstenite = { version = "0.28.0", features = ["rustls-tls-native-roots"] } 26 | tokio-util = { version = "0.7", features = ["io"] } 27 | 28 | [profile.release] 29 | panic = "abort" 30 | lto = true 31 | codegen-units = 1 32 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::type_complexity, clippy::manual_map)] 2 | 3 | #[macro_use] 4 | mod macros; 5 | 6 | mod directed; 7 | mod flected; 8 | mod layed; 9 | mod transmitted; 10 | mod vealed; 11 | 12 | mod body; 13 | mod config; 14 | mod err; 15 | mod future; 16 | mod http; 17 | mod opt; 18 | mod tcp; 19 | mod websocket; 20 | 21 | #[tokio::main] 22 | async fn main() -> Result<(), err::DisplayError> { 23 | let opt::Options { verbose, command } = clap::Parser::parse(); 24 | 25 | env_logger::Builder::new() 26 | .filter_level(match verbose { 27 | 0 => log::LevelFilter::Info, 28 | 1 => log::LevelFilter::Debug, 29 | _ => log::LevelFilter::Trace, 30 | }) 31 | .init(); 32 | 33 | match command { 34 | opt::Command::Directed(options) => directed::main(options).await?, 35 | opt::Command::Flected(options) => flected::main(options).await?, 36 | opt::Command::Layed(options) => layed::main(options).await?, 37 | opt::Command::Transmitted(options) => transmitted::main(options).await?, 38 | opt::Command::Vealed(options) => vealed::main(options).await?, 39 | } 40 | 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /src/layed/heartbeat.rs: -------------------------------------------------------------------------------- 1 | use crate::layed::config::HEARTBEAT_TIMEOUT; 2 | use std::convert::Infallible; 3 | use std::io; 4 | use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; 5 | use tokio::time::{interval, timeout}; 6 | 7 | const HEARTBEAT: [u8; 1] = [0xdd]; 8 | const EXIT: [u8; 1] = [0x1c]; 9 | 10 | pub async fn read_from(mut reader: impl AsyncRead + Unpin) -> Result<(), io::Error> { 11 | let mut buf = [0; 1]; 12 | loop { 13 | timeout(HEARTBEAT_TIMEOUT, reader.read_exact(&mut buf)).await??; 14 | match buf { 15 | HEARTBEAT => continue, 16 | EXIT => return Ok(()), 17 | _ => return Err(io::ErrorKind::InvalidData.into()), 18 | } 19 | } 20 | } 21 | 22 | pub async fn write_forever(mut writer: impl AsyncWrite + Unpin) -> Result { 23 | let mut heartbeat = interval(HEARTBEAT_TIMEOUT / 2); 24 | loop { 25 | writer.write_all(&HEARTBEAT).await?; 26 | writer.flush().await?; 27 | heartbeat.tick().await; 28 | } 29 | } 30 | 31 | pub async fn write_final(mut writer: impl AsyncWrite + Unpin) -> Result<(), io::Error> { 32 | writer.write_all(&EXIT).await?; 33 | writer.flush().await?; 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /src/flected/body.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | use hyper::body::{Body, Frame, SizeHint}; 3 | use std::task::Context; 4 | use std::{cmp, convert::Infallible}; 5 | use tokio::macros::support::{Pin, Poll}; 6 | 7 | pub struct BytesBody(Bytes); 8 | 9 | impl BytesBody { 10 | pub fn new(bytes: Bytes) -> Self { 11 | Self(bytes) 12 | } 13 | 14 | pub fn empty() -> Self { 15 | Self(Bytes::new()) 16 | } 17 | } 18 | 19 | impl From for BytesBody { 20 | fn from(s: String) -> Self { 21 | Self(Bytes::from(s)) 22 | } 23 | } 24 | 25 | impl Body for BytesBody { 26 | type Data = Bytes; 27 | type Error = Infallible; 28 | 29 | fn poll_frame( 30 | mut self: Pin<&mut Self>, 31 | _: &mut Context<'_>, 32 | ) -> Poll, Self::Error>>> { 33 | if self.0.is_empty() { 34 | return Poll::Ready(None); 35 | } 36 | 37 | // windows/linux can't handle write calls bigger than this 38 | let chunk_size = i32::MAX as usize; 39 | let bytes_to_read = cmp::min(self.0.len(), chunk_size); 40 | let read = self.0.split_to(bytes_to_read); 41 | 42 | Poll::Ready(Some(Ok(Frame::data(read)))) 43 | } 44 | 45 | fn is_end_stream(&self) -> bool { 46 | self.0.is_empty() 47 | } 48 | 49 | fn size_hint(&self) -> SizeHint { 50 | SizeHint::with_exact(u64::try_from(self.0.len()).unwrap()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/vealed/forwarder.rs: -------------------------------------------------------------------------------- 1 | use crate::config::COPY_BUFFER_SIZE; 2 | use crate::tcp; 3 | use std::io; 4 | use std::net::SocketAddr; 5 | use std::sync::atomic::{AtomicUsize, Ordering::Relaxed}; 6 | use tokio::io::copy_bidirectional_with_sizes; 7 | use tokio::net::TcpListener; 8 | 9 | static ACTIVE: AtomicUsize = AtomicUsize::new(0); 10 | 11 | pub async fn run(from_addr: SocketAddr, to_addrs: &[SocketAddr]) -> Result<(), io::Error> { 12 | log::info!("Binding to: {}", from_addr); 13 | let mut connections = TcpListener::bind(from_addr).await?; 14 | 15 | loop { 16 | let mut inbound = tcp::accept(&mut connections).await?; 17 | 18 | let mut outbound = match tcp::connect(to_addrs).await { 19 | Ok(outbound) => outbound, 20 | Err(e) => { 21 | log::error!("Failed to connect: {}", e); 22 | continue; 23 | } 24 | }; 25 | 26 | log::info!("Spawning ({} active)", ACTIVE.fetch_add(1, Relaxed) + 1); 27 | tokio::spawn(async move { 28 | let done = copy_bidirectional_with_sizes( 29 | &mut inbound, 30 | &mut outbound, 31 | COPY_BUFFER_SIZE, 32 | COPY_BUFFER_SIZE, 33 | ) 34 | .await; 35 | let active = ACTIVE.fetch_sub(1, Relaxed) - 1; 36 | match done { 37 | Ok((down, up)) => log::info!("Closing ({} active): {}/{}", active, down, up), 38 | Err(e) => log::info!("Closing ({} active): {}", active, e), 39 | } 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/directed/opt.rs: -------------------------------------------------------------------------------- 1 | use crate::directed::redir::{From, To}; 2 | use clap::Args; 3 | use std::net::SocketAddr; 4 | 5 | /// Redirect HTTP traffic somewhere else 6 | #[derive(Args, Debug)] 7 | pub struct Options { 8 | #[arg( 9 | help = "Socket address to listen on (--help for more)", 10 | long_help = r"Socket address to listen on: 11 | - incoming http connections are received on this socket 12 | Examples: 13 | - 127.0.0.1:3000 14 | - 0.0.0.0:80 15 | - [2001:db8::1]:8080" 16 | )] 17 | pub listen: SocketAddr, 18 | 19 | #[arg( 20 | help = "Path prefixes to redirect from (--help for more)", 21 | long_help = r"Path prefixes to redirect from: 22 | - each prefix is checked in order, and the first match is chosen 23 | - 404s if no prefixes match 24 | Examples: 25 | - / 26 | - /resources/static/" 27 | )] 28 | #[arg(short, long, required = true, display_order = 0)] 29 | pub from: Vec, 30 | 31 | #[arg( 32 | help = "Address prefixes to redirect to (--help for more)", 33 | long_help = r"Address prefixes to redirect to: 34 | - each matching request's tail is appended to the corresponding address prefix 35 | - some schemes have special behavior 36 | Examples: 37 | - http://localhost:8080/services/api/ 38 | - https://test.dev/v1/ 39 | - file://./static/ 40 | - file://./static/|./static/index.html (fallback to ./static/index.html) 41 | - status://404 (empty response with status 404)" 42 | )] 43 | #[arg(short, long, required = true, display_order = 0)] 44 | pub to: Vec, 45 | } 46 | -------------------------------------------------------------------------------- /src/flected/routes/index.rs: -------------------------------------------------------------------------------- 1 | use crate::flected::body::BytesBody; 2 | use crate::flected::routes::State; 3 | use hyper::body::Incoming; 4 | use hyper::{Request, Response}; 5 | 6 | pub async fn get(req: Request, state: &State) -> Response { 7 | let files = state.files.read().await; 8 | log::info!("GET {} -> [listing {} files]", req.uri(), files.len()); 9 | let files_listing = files 10 | .iter() 11 | .map(|(path, file)| { 12 | format!(concat!( 13 | "
", 14 | "{path} ", 15 | "{len} bytes ", 16 | "(delete)", 17 | "
", 18 | ), path = path, len = file.len()) 19 | }) 20 | .collect::>() 21 | .join(""); 22 | Response::new(BytesBody::from(format!( 23 | concat!( 24 | "", 25 | "", 26 | "", 27 | "", 28 | "visit a path to upload a file", 29 | "

", 30 | "or upload by name ", 31 | "", 36 | "{files_listing}", 37 | "", 38 | "", 39 | ), 40 | files_listing = files_listing 41 | ))) 42 | } 43 | -------------------------------------------------------------------------------- /src/layed/opt.rs: -------------------------------------------------------------------------------- 1 | use crate::opt::SocketAddrsFromDns; 2 | use clap::{Args, Subcommand, ValueEnum}; 3 | use std::net::SocketAddr; 4 | 5 | /// Relay TCP connections to a machine behind a dynamic IP/firewall 6 | #[derive(Args, Debug)] 7 | pub struct Options { 8 | #[command(subcommand)] 9 | pub mode: Mode, 10 | } 11 | 12 | #[derive(Subcommand, Debug)] 13 | pub enum Mode { 14 | /// Run the server half on a public machine 15 | Server { 16 | /// Socket address to receive gateway connections from client 17 | gateway: SocketAddr, 18 | 19 | /// Socket address to receive public traffic on 20 | public: SocketAddr, 21 | 22 | /// Whether to use a WebSocket instead of raw TCP for the gateway. 23 | /// 24 | /// This has worse performance, but allows traversal of HTTP-only proxies. 25 | /// If used, the client must also enable this option. 26 | #[arg(long)] 27 | websocket: bool, 28 | }, 29 | /// Run the client half on a private machine 30 | Client { 31 | /// Address of server's gateway 32 | gateway: SocketAddrsFromDns, 33 | 34 | /// Address to relay public traffic to 35 | private: SocketAddrsFromDns, 36 | 37 | /// Whether to use a WebSocket instead of raw TCP for the gateway. 38 | /// 39 | /// This has worse performance, but allows traversal of HTTP-only proxies. 40 | /// If used, the server must also enable this option. 41 | #[arg(long, value_enum, default_value_t = WebSocketEnabled::Off)] 42 | websocket: WebSocketEnabled, 43 | }, 44 | } 45 | 46 | #[derive(ValueEnum, Copy, Clone, Debug, PartialEq)] 47 | pub enum WebSocketEnabled { 48 | Off, 49 | Insecure, 50 | Secure, 51 | } 52 | -------------------------------------------------------------------------------- /src/layed/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::tcp; 2 | use crate::websocket; 3 | 4 | mod backoff; 5 | mod client; 6 | mod config; 7 | mod heartbeat; 8 | mod magic; 9 | pub mod opt; 10 | mod server; 11 | mod stream; 12 | 13 | pub async fn main(options: opt::Options) -> Result<(), std::io::Error> { 14 | let opt::Options { mode } = options; 15 | 16 | match mode { 17 | opt::Mode::Server { 18 | gateway, 19 | public, 20 | websocket, 21 | } => { 22 | if websocket { 23 | server::run(&gateway, &public, async |mut listener| { 24 | (websocket::accept(&mut listener).await, listener) 25 | }) 26 | .await?; 27 | } else { 28 | server::run(&gateway, &public, async |mut listener| { 29 | (tcp::accept(&mut listener).await, listener) 30 | }) 31 | .await?; 32 | } 33 | } 34 | opt::Mode::Client { 35 | gateway, 36 | private, 37 | websocket, 38 | } => match websocket { 39 | opt::WebSocketEnabled::Insecure | opt::WebSocketEnabled::Secure => { 40 | let scheme = if websocket == opt::WebSocketEnabled::Insecure { 41 | "ws" 42 | } else { 43 | "wss" 44 | }; 45 | let uri = http::uri::Builder::new() 46 | .scheme(scheme) 47 | .authority(gateway.orig()) 48 | .path_and_query("/ws/") 49 | .build() 50 | .unwrap(); 51 | client::run(|| websocket::connect(&uri), &private).await; 52 | } 53 | opt::WebSocketEnabled::Off => { 54 | client::run(|| tcp::connect(&gateway), &private).await; 55 | } 56 | }, 57 | } 58 | 59 | Ok(()) 60 | } 61 | -------------------------------------------------------------------------------- /src/opt.rs: -------------------------------------------------------------------------------- 1 | use clap::{ArgAction, Parser, Subcommand}; 2 | use std::io; 3 | use std::net::{SocketAddr, ToSocketAddrs}; 4 | use std::ops::Deref; 5 | use std::str::FromStr; 6 | 7 | #[derive(Parser, Debug)] 8 | #[clap(version, about)] 9 | pub struct Options { 10 | /// Logging verbosity (-v debug, -vv trace) 11 | #[arg(short = 'v', long = "verbose", action = ArgAction::Count, global = true)] 12 | pub verbose: u8, 13 | 14 | #[command(subcommand)] 15 | pub command: Command, 16 | } 17 | 18 | #[derive(Subcommand, Debug)] 19 | pub enum Command { 20 | Directed(crate::directed::opt::Options), 21 | Flected(crate::flected::opt::Options), 22 | Layed(crate::layed::opt::Options), 23 | Transmitted(crate::transmitted::opt::Options), 24 | Vealed(crate::vealed::opt::Options), 25 | } 26 | 27 | #[derive(Clone, Debug)] 28 | pub struct SocketAddrsFromDns { 29 | orig: String, 30 | addrs: Vec, 31 | } 32 | 33 | impl SocketAddrsFromDns { 34 | pub fn orig(&self) -> &str { 35 | &self.orig 36 | } 37 | 38 | pub fn addrs(&self) -> &[SocketAddr] { 39 | &self.addrs 40 | } 41 | } 42 | 43 | impl Deref for SocketAddrsFromDns { 44 | type Target = [SocketAddr]; 45 | 46 | fn deref(&self) -> &Self::Target { 47 | self.addrs() 48 | } 49 | } 50 | 51 | impl FromStr for SocketAddrsFromDns { 52 | type Err = io::Error; 53 | 54 | fn from_str(arg: &str) -> Result { 55 | let addrs = arg.to_socket_addrs()?.collect::>(); 56 | match addrs.len() { 57 | 0 => Err(io::Error::new( 58 | io::ErrorKind::AddrNotAvailable, 59 | "Resolved to zero addresses", 60 | )), 61 | _ => Ok(Self { 62 | orig: arg.to_string(), 63 | addrs, 64 | }), 65 | } 66 | } 67 | } 68 | 69 | #[cfg(test)] 70 | mod tests { 71 | use super::*; 72 | 73 | #[test] 74 | fn verify_cli() { 75 | use clap::CommandFactory; 76 | Options::command().debug_assert(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/layed/stream.rs: -------------------------------------------------------------------------------- 1 | use futures::stream; 2 | use futures::{Stream, StreamExt}; 3 | use std::mem::ManuallyDrop; 4 | use std::pin::{Pin, pin}; 5 | use std::task::Context; 6 | use std::task::Poll; 7 | use tokio::sync::mpsc; 8 | 9 | /// Spawns a stream onto the local set to perform idle work. 10 | /// This keeps polling the inner stream even when no item is demanded by the parent, 11 | /// allowing it to keep making progress. 12 | pub fn spawn_idle(f: impl FnOnce(Requests) -> S) -> impl Stream 13 | where 14 | T: Send + 'static, 15 | S: Stream + Send + 'static, 16 | { 17 | let (request, requests) = mpsc::channel(1); 18 | let (response, responses) = mpsc::channel(1); 19 | 20 | let idle = f(Requests(requests)); 21 | tokio::spawn(async move { 22 | let mut idle = pin!(idle); 23 | loop { 24 | match idle.next().await { 25 | Some((token, val)) => match response.send((ManuallyDrop::new(token), val)).await { 26 | Ok(()) => continue, 27 | Err(mpsc::error::SendError(_)) => return, 28 | }, 29 | None => return, 30 | } 31 | } 32 | }); 33 | 34 | stream::unfold( 35 | (request, responses, ManuallyDrop::new(RequestToken(()))), 36 | |(request, mut responses, token)| async { 37 | match request.send(token).await { 38 | Ok(()) => match responses.recv().await { 39 | Some((token, val)) => Some((val, (request, responses, token))), 40 | None => None, 41 | }, 42 | Err(mpsc::error::SendError(_)) => None, 43 | } 44 | }, 45 | ) 46 | } 47 | 48 | pub struct RequestToken(()); 49 | 50 | impl Drop for RequestToken { 51 | fn drop(&mut self) { 52 | panic!("Deadlock: request token dropped"); 53 | } 54 | } 55 | 56 | pub struct Requests(mpsc::Receiver>); 57 | 58 | impl Stream for Requests { 59 | type Item = RequestToken; 60 | 61 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 62 | self.0 63 | .poll_recv(cx) 64 | .map(|p| p.map(ManuallyDrop::into_inner)) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - v*.*.* 9 | pull_request: 10 | 11 | jobs: 12 | fmt: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - run: rustup toolchain install stable --profile minimal 17 | - run: rustup component add rustfmt 18 | 19 | - run: cargo fmt --all -- --check 20 | 21 | clippy: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v3 25 | - run: rustup toolchain install stable --profile minimal 26 | - run: rustup component add clippy 27 | 28 | - run: RUSTFLAGS="-D warnings" cargo clippy 29 | 30 | test: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v3 34 | - run: rustup toolchain install stable --profile minimal 35 | 36 | - run: cargo test 37 | 38 | build-linux: 39 | runs-on: ubuntu-latest 40 | permissions: 41 | contents: write 42 | steps: 43 | - uses: actions/checkout@v3 44 | - run: rustup toolchain install stable --profile minimal 45 | - run: rustup target add x86_64-unknown-linux-musl 46 | - run: sudo apt-get install musl-tools 47 | 48 | - run: cargo build --release --target=x86_64-unknown-linux-musl 49 | - run: strip target/x86_64-unknown-linux-musl/release/re 50 | - run: ls -lh target/x86_64-unknown-linux-musl/release/re 51 | 52 | - uses: softprops/action-gh-release@v1 53 | if: startsWith(github.ref, 'refs/tags/') 54 | with: 55 | files: target/x86_64-unknown-linux-musl/release/re 56 | env: 57 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 58 | 59 | build-windows: 60 | runs-on: windows-latest 61 | permissions: 62 | contents: write 63 | steps: 64 | - uses: actions/checkout@v3 65 | - run: rustup toolchain install stable --profile minimal 66 | 67 | - run: cargo build --release 68 | env: 69 | RUSTFLAGS: -Ctarget-feature=+crt-static 70 | - run: dir target/release/re.exe 71 | 72 | - uses: softprops/action-gh-release@v1 73 | if: startsWith(github.ref, 'refs/tags/') 74 | with: 75 | files: target/release/re.exe 76 | env: 77 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 78 | -------------------------------------------------------------------------------- /src/layed/client.rs: -------------------------------------------------------------------------------- 1 | use crate::config::COPY_BUFFER_SIZE; 2 | use crate::layed::backoff::Backoff; 3 | use crate::layed::config::CLIENT_BACKOFF_SECS; 4 | use crate::layed::heartbeat; 5 | use crate::layed::magic; 6 | use crate::tcp; 7 | use std::io; 8 | use std::net::SocketAddr; 9 | use std::sync::atomic::{AtomicUsize, Ordering::Relaxed}; 10 | use std::time::Duration; 11 | use tokio::io::AsyncRead; 12 | use tokio::io::AsyncWrite; 13 | use tokio::io::copy_bidirectional_with_sizes; 14 | use tokio::time::sleep; 15 | 16 | static ACTIVE: AtomicUsize = AtomicUsize::new(0); 17 | 18 | pub async fn run( 19 | connect_to_gateway: impl AsyncFn() -> Result, 20 | private_addrs: &[SocketAddr], 21 | ) -> ! 22 | where 23 | Conn: AsyncRead + AsyncWrite + Unpin + Send + 'static, 24 | { 25 | let mut backoff = Backoff::new(CLIENT_BACKOFF_SECS); 26 | 27 | loop { 28 | let one_round = async { 29 | log::info!("Connecting to gateway"); 30 | let mut gateway = connect_to_gateway().await?; 31 | 32 | log::info!("Sending early handshake"); 33 | magic::write_to(&mut gateway).await?; 34 | 35 | log::info!("Waiting for end of heartbeat"); 36 | heartbeat::read_from(&mut gateway).await?; 37 | 38 | log::info!("Sending late handshake"); 39 | magic::write_to(&mut gateway).await?; 40 | 41 | log::info!("Connecting to private"); 42 | let mut private = tcp::connect(private_addrs).await?; 43 | 44 | log::info!("Spawning ({} active)", ACTIVE.fetch_add(1, Relaxed) + 1); 45 | tokio::spawn(async move { 46 | let done = copy_bidirectional_with_sizes( 47 | &mut gateway, 48 | &mut private, 49 | COPY_BUFFER_SIZE, 50 | COPY_BUFFER_SIZE, 51 | ) 52 | .await; 53 | let active = ACTIVE.fetch_sub(1, Relaxed) - 1; 54 | match done { 55 | Ok((down, up)) => log::info!("Closing ({} active): {}/{}", active, down, up), 56 | Err(e) => log::info!("Closing ({} active): {}", active, e), 57 | } 58 | }); 59 | 60 | Ok::<(), io::Error>(()) 61 | } 62 | .await; 63 | 64 | match one_round { 65 | Ok(()) => { 66 | backoff.reset(); 67 | } 68 | Err(e) => { 69 | log::error!("Failed: {}", e); 70 | let seconds = backoff.next(); 71 | log::warn!("Retrying in {} seconds", seconds); 72 | sleep(Duration::from_secs(u64::from(seconds))).await; 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/http.rs: -------------------------------------------------------------------------------- 1 | use hyper::body::{Body, Incoming}; 2 | use hyper::service::service_fn; 3 | use hyper::{Request, Response}; 4 | use hyper_rustls::HttpsConnector; 5 | use hyper_util::client::legacy::Client; 6 | use hyper_util::client::legacy::connect::HttpConnector; 7 | use hyper_util::rt::{TokioExecutor, TokioIo}; 8 | use hyper_util::server::conn::auto; 9 | use std::convert::Infallible; 10 | use std::future::Future; 11 | use std::io; 12 | use std::net::SocketAddr; 13 | use std::sync::Arc; 14 | use tokio::net::TcpListener; 15 | 16 | pub type ProxyClient = Client, Incoming>; 17 | 18 | pub fn make_client() -> Result { 19 | Ok(Client::builder(TokioExecutor::new()).build( 20 | HttpsConnector::<()>::builder() 21 | .with_native_roots()? 22 | .https_or_http() 23 | .enable_http1() 24 | .enable_http2() 25 | .build(), 26 | )) 27 | } 28 | 29 | pub async fn run_simple_server( 30 | addr: SocketAddr, 31 | state: S, 32 | handle_req: F, 33 | ) -> Result<(), io::Error> 34 | where 35 | S: Send + Sync + 'static, 36 | F: for<'s> ServiceFn<'s, Request, S, Response> + Copy + Send + 'static, 37 | B: Body + Send + 'static, 38 | ::Data: Send, 39 | ::Error: Into>, 40 | { 41 | let state = Arc::new(state); 42 | let listener = TcpListener::bind(addr).await?; 43 | 44 | loop { 45 | let (tcp, _) = listener.accept().await?; 46 | let io = TokioIo::new(tcp); 47 | 48 | let state = Arc::clone(&state); 49 | tokio::spawn(async move { 50 | let serve = service_fn(move |req| { 51 | let state = Arc::clone(&state); 52 | async move { Ok::<_, Infallible>(handle_req(req, &state).await) } 53 | }); 54 | 55 | if let Err(e) = auto::Builder::new(TokioExecutor::new()) 56 | .serve_connection_with_upgrades(io, serve) 57 | .await 58 | { 59 | log::error!("Error serving connection: {}", e); 60 | } 61 | }); 62 | } 63 | } 64 | 65 | // Work around the lack of HKT bounds. 66 | // Because the future will borrow from the state argument, we need to write bounds like this: 67 | // ``` 68 | // where 69 | // F: for<'s> FnOnce(Request, &'s S) -> Fut<'s> 70 | // Fut<'s>: Future, E>> + 's 71 | // ``` 72 | // Which can't currently be done. Instead, factor both bounds out to a dedicated trait, 73 | // which is implemented for all matching functions. 74 | pub trait ServiceFn<'s, T, S, R> 75 | where 76 | Self: FnOnce(T, &'s S) -> Self::Fut, 77 | Self::Fut: Future + Send + 's, 78 | S: 's, 79 | { 80 | type Fut; 81 | } 82 | 83 | impl<'s, T, S, R, F, Fut> ServiceFn<'s, T, S, R> for F 84 | where 85 | F: FnOnce(T, &'s S) -> Fut, 86 | Fut: Future + Send + 's, 87 | S: 's, 88 | { 89 | type Fut = Fut; 90 | } 91 | -------------------------------------------------------------------------------- /src/directed/routes.rs: -------------------------------------------------------------------------------- 1 | use crate::body; 2 | use crate::directed::redir::{Action, Rules}; 3 | use crate::err::Error; 4 | use crate::http::ProxyClient; 5 | use http_body_util::BodyExt; 6 | use http_body_util::combinators::BoxBody; 7 | use hyper::body::{Bytes, Incoming}; 8 | use hyper::{Request, Response, StatusCode}; 9 | use tokio::fs::File; 10 | 11 | pub struct State { 12 | pub client: ProxyClient, 13 | pub rules: Rules, 14 | } 15 | 16 | pub async fn respond_to_request( 17 | mut req: Request, 18 | state: &State, 19 | ) -> Response> { 20 | match state.rules.try_match(req.uri()) { 21 | Some(Ok(Action::Http(uri))) => { 22 | // Proxy this request to the new URL 23 | let req_uri = req.uri().clone(); 24 | req.uri_mut().clone_from(&uri); 25 | match state.client.request(req).await { 26 | Ok(resp) => { 27 | log::info!("{} -> {}", req_uri, uri); 28 | resp.map(|body| body.map_err(Error::from).boxed()) 29 | } 30 | Err(e) => { 31 | log::warn!("{} -> [proxy error] {} : {}", req_uri, uri, e); 32 | let mut resp = Response::new(body::empty()); 33 | *resp.status_mut() = StatusCode::BAD_GATEWAY; 34 | resp 35 | } 36 | } 37 | } 38 | Some(Ok(Action::File { path, fallback })) => { 39 | // Proxy this request to the filesystem 40 | let found_file = match File::open(&path).await { 41 | Ok(file) => Ok((path, file)), 42 | Err(e) => match fallback { 43 | Some(fallback) => match File::open(&fallback).await { 44 | Ok(file) => Ok((fallback, file)), 45 | Err(_) => Err((path, e)), 46 | }, 47 | None => Err((path, e)), 48 | }, 49 | }; 50 | match found_file { 51 | Ok((found_path, file)) => { 52 | log::info!("{} -> {}", req.uri(), found_path.display()); 53 | Response::new(body::from_file(file).map_err(Error::from).boxed()) 54 | } 55 | Err((path, e)) => { 56 | log::warn!("{} -> [file error] {} : {}", req.uri(), path.display(), e); 57 | let mut resp = Response::new(body::empty()); 58 | *resp.status_mut() = StatusCode::NOT_FOUND; 59 | resp 60 | } 61 | } 62 | } 63 | Some(Ok(Action::Status(status))) => { 64 | log::info!("{} -> {}", req.uri(), status); 65 | let mut resp = Response::new(body::empty()); 66 | *resp.status_mut() = status; 67 | resp 68 | } 69 | Some(Err(e)) => { 70 | log::warn!("{} -> [internal error] : {}", req.uri(), e); 71 | let mut resp = Response::new(body::empty()); 72 | *resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; 73 | resp 74 | } 75 | None => { 76 | log::warn!("{} -> [no match]", req.uri()); 77 | let mut resp = Response::new(body::empty()); 78 | *resp.status_mut() = StatusCode::BAD_GATEWAY; 79 | resp 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/transmitted/routes.rs: -------------------------------------------------------------------------------- 1 | use crate::body; 2 | use crate::http::ProxyClient; 3 | use crate::transmitted::path::extract_uri_from_path; 4 | use http_body_util::BodyExt; 5 | use http_body_util::combinators::BoxBody; 6 | use hyper::body::{Bytes, Incoming}; 7 | use hyper::http::HeaderValue; 8 | use hyper::{Method, Request, Response, StatusCode, header}; 9 | use sha2::{Digest, Sha256}; 10 | use std::mem; 11 | 12 | pub struct State { 13 | pub client: ProxyClient, 14 | pub secret_key_hash: Option>, 15 | } 16 | 17 | #[allow(clippy::declare_interior_mutable_const)] 18 | pub async fn respond_to_request( 19 | mut req: Request, 20 | state: &State, 21 | ) -> Response> { 22 | const X_RETRANSMITTED_KEY: &str = "x-retransmitted-key"; 23 | const ANY: HeaderValue = HeaderValue::from_static("*"); 24 | const ALLOWED_HEADERS: HeaderValue = HeaderValue::from_static(X_RETRANSMITTED_KEY); 25 | 26 | if req.method() == Method::OPTIONS { 27 | log::info!("{} {} -> [preflight response]", req.method(), req.uri()); 28 | let mut resp = Response::new(body::empty()); 29 | resp.headers_mut() 30 | .append(header::ACCESS_CONTROL_ALLOW_ORIGIN, ANY); 31 | resp.headers_mut() 32 | .append(header::ACCESS_CONTROL_ALLOW_HEADERS, ALLOWED_HEADERS); 33 | return resp; 34 | } 35 | 36 | if let Some(secret_key_hash) = &state.secret_key_hash { 37 | let provided_key = match req.headers_mut().remove(X_RETRANSMITTED_KEY) { 38 | Some(k) => k, 39 | None => { 40 | log::info!("{} {} -> [missing key]", req.method(), req.uri()); 41 | let mut resp = Response::new(body::empty()); 42 | *resp.status_mut() = StatusCode::UNAUTHORIZED; 43 | return resp; 44 | } 45 | }; 46 | let provided_key_hash = Sha256::digest(provided_key); 47 | match ring::constant_time::verify_slices_are_equal( 48 | provided_key_hash.as_slice(), 49 | secret_key_hash, 50 | ) { 51 | Ok(()) => {} 52 | Err(ring::error::Unspecified) => { 53 | log::warn!("{} {} -> [invalid key]", req.method(), req.uri()); 54 | let mut resp = Response::new(body::empty()); 55 | *resp.status_mut() = StatusCode::UNAUTHORIZED; 56 | return resp; 57 | } 58 | } 59 | } 60 | 61 | let uri = match extract_uri_from_path(req.uri()) { 62 | None => { 63 | log::warn!("{} {} -> [missing url]", req.method(), req.uri()); 64 | let mut resp = Response::new(body::empty()); 65 | *resp.status_mut() = StatusCode::BAD_REQUEST; 66 | return resp; 67 | } 68 | Some(Err((e, unparsed))) => { 69 | log::warn!( 70 | "{} {} -> [invalid url] {:?} {}", 71 | req.method(), 72 | req.uri(), 73 | unparsed, 74 | e 75 | ); 76 | let mut resp = Response::new(body::empty()); 77 | *resp.status_mut() = StatusCode::BAD_REQUEST; 78 | return resp; 79 | } 80 | Some(Ok(u)) => u, 81 | }; 82 | 83 | match uri.host().map(|h| h.parse()) { 84 | None => { 85 | log::warn!("{} {} -> [missing host]", req.method(), req.uri()); 86 | let mut resp = Response::new(body::empty()); 87 | *resp.status_mut() = StatusCode::BAD_REQUEST; 88 | return resp; 89 | } 90 | Some(Err(e)) => { 91 | log::warn!("{} {} -> [invalid host] {}", req.method(), req.uri(), e); 92 | let mut resp = Response::new(body::empty()); 93 | *resp.status_mut() = StatusCode::BAD_REQUEST; 94 | return resp; 95 | } 96 | Some(Ok(host)) => { 97 | req.headers_mut().insert(header::HOST, host); 98 | } 99 | } 100 | 101 | let orig_method = req.method().clone(); 102 | let orig_uri = mem::replace(req.uri_mut(), uri); 103 | let mut resp = match state.client.request(req).await { 104 | Ok(r) => r, 105 | Err(e) => { 106 | log::error!("{} {} -> [proxy error] {}", orig_method, orig_uri, e); 107 | let mut resp = Response::new(body::empty()); 108 | *resp.status_mut() = StatusCode::BAD_GATEWAY; 109 | return resp; 110 | } 111 | }; 112 | 113 | log::info!("{} {} -> [success]", orig_method, orig_uri); 114 | resp.headers_mut() 115 | .append(header::ACCESS_CONTROL_ALLOW_ORIGIN, ANY); 116 | resp.map(|body| body.boxed()) 117 | } 118 | -------------------------------------------------------------------------------- /src/flected/routes/paths.rs: -------------------------------------------------------------------------------- 1 | use crate::flected::body::BytesBody; 2 | use crate::flected::file::write_to_mmap; 3 | use crate::flected::routes::State; 4 | use bytes::Bytes; 5 | use headers::{AcceptRanges, ContentRange, HeaderMapExt, Range}; 6 | use hyper::body::Incoming; 7 | use hyper::header::HOST; 8 | use hyper::{Request, Response, StatusCode}; 9 | use std::collections::Bound; 10 | 11 | pub async fn get(req: Request, state: &State) -> Response { 12 | let file = state.files.read().await.get(req.uri().path()).cloned(); 13 | match file { 14 | Some(file) => match req 15 | .headers() 16 | .typed_get::() 17 | .and_then(|r| r.satisfiable_ranges(file.len() as u64).next()) 18 | { 19 | Some((start, end)) => { 20 | let file_len = file.len(); 21 | let start_inclusive = match start { 22 | Bound::Included(start) => start as usize, 23 | Bound::Excluded(start) => start as usize + 1, 24 | Bound::Unbounded => 0, 25 | }; 26 | let end_exclusive = match end { 27 | Bound::Included(end) => end as usize + 1, 28 | Bound::Excluded(end) => end as usize, 29 | Bound::Unbounded => file_len, 30 | }; 31 | match file 32 | .get(start_inclusive..end_exclusive) 33 | .map(|s| file.slice_ref(s)) 34 | { 35 | Some(body) => { 36 | log::info!( 37 | "GET {} -> [found range {}..{} bytes of {}]", 38 | req.uri(), 39 | start_inclusive, 40 | end_exclusive, 41 | file_len 42 | ); 43 | let mut resp = Response::new(BytesBody::new(body)); 44 | *resp.status_mut() = StatusCode::PARTIAL_CONTENT; 45 | resp.headers_mut().typed_insert( 46 | ContentRange::bytes( 47 | (start_inclusive as u64)..(end_exclusive as u64), 48 | file_len as u64, 49 | ) 50 | .unwrap(), 51 | ); 52 | resp 53 | } 54 | None => { 55 | log::info!("GET {} -> [bad range]", req.uri()); 56 | let mut resp = Response::new(BytesBody::empty()); 57 | *resp.status_mut() = StatusCode::RANGE_NOT_SATISFIABLE; 58 | resp.headers_mut() 59 | .typed_insert(ContentRange::unsatisfied_bytes(file_len as u64)); 60 | resp 61 | } 62 | } 63 | } 64 | None => { 65 | log::info!("GET {} -> [found {} bytes]", req.uri(), file.len()); 66 | let mut resp = Response::new(BytesBody::new(file)); 67 | resp.headers_mut().typed_insert(AcceptRanges::bytes()); 68 | resp 69 | } 70 | }, 71 | None => { 72 | log::info!("GET {} -> [not found]", req.uri()); 73 | let path = req.uri().path().trim_start_matches('/'); 74 | let host = req 75 | .headers() 76 | .get(HOST) 77 | .and_then(|h| h.to_str().ok()) 78 | .unwrap_or("example.com"); 79 | let mut resp = Response::new(BytesBody::from(format!( 80 | concat!( 81 | "", 82 | "", 83 | "", 84 | "", 85 | "curl -o /dev/null -X POST {host}/{path} --data-binary @- < {path}", 86 | "

", 87 | "or ", 88 | "", 92 | "", 93 | "", 94 | ), 95 | path = path, 96 | host = host 97 | ))); 98 | *resp.status_mut() = StatusCode::NOT_FOUND; 99 | resp 100 | } 101 | } 102 | } 103 | 104 | pub async fn post(req: Request, state: &State) -> Response { 105 | log::info!("POST {} -> [start upload]", req.uri()); 106 | let (parts, body) = req.into_parts(); 107 | let file = match write_to_mmap(body).await { 108 | Ok(f) => f, 109 | Err(e) => { 110 | log::warn!("POST {} -> [upload error] {}", parts.uri, e); 111 | let mut resp = Response::new(BytesBody::empty()); 112 | *resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; 113 | return resp; 114 | } 115 | }; 116 | log::info!("POST {} -> [uploaded {} bytes]", parts.uri, file.len()); 117 | state 118 | .files 119 | .write() 120 | .await 121 | .insert(parts.uri.path().to_string(), Bytes::from_owner(file)); 122 | Response::new(BytesBody::empty()) 123 | } 124 | 125 | pub async fn delete(req: Request, state: &State) -> Response { 126 | let file = state.files.write().await.remove(req.uri().path()); 127 | match file { 128 | Some(file) => { 129 | log::info!("DELETE {} -> [deleted {} bytes]", req.uri(), file.len()); 130 | Response::new(BytesBody::empty()) 131 | } 132 | None => { 133 | log::info!("DELETE {} -> [not found]", req.uri()); 134 | let mut resp = Response::new(BytesBody::empty()); 135 | *resp.status_mut() = StatusCode::NOT_FOUND; 136 | resp 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/directed/redir.rs: -------------------------------------------------------------------------------- 1 | use http::status::InvalidStatusCode; 2 | use http::uri::InvalidUri; 3 | use hyper::{StatusCode, Uri}; 4 | use std::path::PathBuf; 5 | use std::str::FromStr; 6 | use thiserror::Error; 7 | 8 | #[derive(Clone, Debug)] 9 | pub struct From(String); 10 | 11 | #[derive(Debug, Error)] 12 | pub enum BadRedirectFrom { 13 | #[error("path does not start with slash")] 14 | NoLeadingSlash, 15 | #[error("path does not end with slash")] 16 | NoTrailingSlash, 17 | } 18 | 19 | impl FromStr for From { 20 | type Err = BadRedirectFrom; 21 | fn from_str(s: &str) -> Result { 22 | match () { 23 | _ if !s.starts_with('/') => Err(BadRedirectFrom::NoLeadingSlash), 24 | _ if !s.ends_with('/') => Err(BadRedirectFrom::NoTrailingSlash), 25 | _ => Ok(From(s.to_string())), 26 | } 27 | } 28 | } 29 | 30 | #[derive(Clone, Debug)] 31 | pub enum To { 32 | Http(String), 33 | File(PathBuf, Option), 34 | Status(StatusCode), 35 | } 36 | 37 | #[derive(Debug, Error)] 38 | pub enum BadRedirectTo { 39 | #[error("invalid uri: {0}")] 40 | InvalidUri(InvalidUri), 41 | #[error("invalid scheme: {0}")] 42 | InvalidScheme(String), 43 | #[error("invalid status code: {0}")] 44 | InvalidStatus(InvalidStatusCode), 45 | #[error("too many fallbacks provided")] 46 | TooManyFallbacks, 47 | #[error("fallback not allowed: {0}")] 48 | FallbackNotAllowed(String), 49 | #[error("path does not end with slash")] 50 | NoTrailingSlash, 51 | #[error("does not begin with scheme")] 52 | NoScheme, 53 | } 54 | 55 | impl FromStr for To { 56 | type Err = BadRedirectTo; 57 | fn from_str(to_str: &str) -> Result { 58 | let (path, fallback) = { 59 | let mut parts = to_str.split('|').fuse(); 60 | match (parts.next(), parts.next(), parts.next()) { 61 | (Some(path), fallback, None) => (path, fallback), 62 | _ => return Err(BadRedirectTo::TooManyFallbacks), 63 | } 64 | }; 65 | 66 | match path.parse::() { 67 | Ok(uri) => match (uri.scheme().map(|s| s.as_str()), fallback) { 68 | (Some("http"), None) | (Some("https"), None) => { 69 | let uri = uri.to_string(); 70 | match () { 71 | _ if !uri.ends_with('/') => Err(BadRedirectTo::NoTrailingSlash), 72 | _ => Ok(To::Http(uri)), 73 | } 74 | } 75 | (Some("file"), fallback) => { 76 | let uri = uri.authority().map_or("", |a| a.as_str()).to_string() + uri.path(); 77 | match () { 78 | _ if !uri.ends_with('/') => Err(BadRedirectTo::NoTrailingSlash), 79 | _ => Ok(To::File(PathBuf::from(uri), fallback.map(PathBuf::from))), 80 | } 81 | } 82 | (Some("status"), None) => { 83 | match StatusCode::from_bytes( 84 | uri.authority().map_or("", |a| a.as_str()).as_bytes(), 85 | ) { 86 | Ok(status) => Ok(To::Status(status)), 87 | Err(e) => Err(BadRedirectTo::InvalidStatus(e)), 88 | } 89 | } 90 | (Some(scheme), None) => Err(BadRedirectTo::InvalidScheme(scheme.to_string())), 91 | (Some(_), Some(fallback)) => { 92 | Err(BadRedirectTo::FallbackNotAllowed(fallback.to_string())) 93 | } 94 | (None, _) => Err(BadRedirectTo::NoScheme), 95 | }, 96 | Err(e) => Err(BadRedirectTo::InvalidUri(e)), 97 | } 98 | } 99 | } 100 | 101 | #[derive(Debug, Error)] 102 | pub enum BadRedirect { 103 | #[error("unequal number of `from` and `to` arguments")] 104 | UnequalFromTo, 105 | } 106 | 107 | #[derive(Debug)] 108 | pub struct Rules { 109 | redirects: Vec<(From, To)>, 110 | } 111 | 112 | impl Rules { 113 | pub fn zip(from: Vec, to: Vec) -> Result { 114 | if from.len() == to.len() { 115 | Ok(Self { 116 | redirects: from.into_iter().zip(to).collect(), 117 | }) 118 | } else { 119 | Err(BadRedirect::UnequalFromTo) 120 | } 121 | } 122 | 123 | pub fn try_match(&self, uri: &Uri) -> Option> { 124 | self.redirects.iter().find_map(|(from, to)| { 125 | let req_path = match to { 126 | To::Http(..) => uri.path_and_query()?.as_str(), 127 | To::File(..) | To::Status(..) => uri.path(), 128 | }; 129 | req_path.strip_prefix(from.0.as_str()).map(|req_tail| { 130 | Ok(match to { 131 | To::Http(prefix) => Action::Http((prefix.to_string() + req_tail).parse()?), 132 | To::File(prefix, fallback) => Action::File { 133 | path: prefix.join(req_tail), 134 | fallback: fallback.clone(), 135 | }, 136 | To::Status(status) => Action::Status(*status), 137 | }) 138 | }) 139 | }) 140 | } 141 | } 142 | 143 | pub enum Action { 144 | Http(Uri), 145 | File { 146 | path: PathBuf, 147 | fallback: Option, 148 | }, 149 | Status(StatusCode), 150 | } 151 | 152 | #[cfg(test)] 153 | #[rustfmt::skip] 154 | mod tests { 155 | use super::*; 156 | 157 | case!(from_just_slash: assert!(matches!(From::from_str("/"), Ok(_)))); 158 | case!(from_slash_api: assert!(matches!(From::from_str("/api/"), Ok(_)))); 159 | case!(from_multi_slash: assert!(matches!(From::from_str("/resources/static/"), Ok(_)))); 160 | 161 | case!(from_no_leading: assert!(matches!(From::from_str("foo/"), Err(BadRedirectFrom::NoLeadingSlash)))); 162 | case!(from_no_trailing: assert!(matches!(From::from_str("/foo"), Err(BadRedirectFrom::NoTrailingSlash)))); 163 | 164 | case!(to_localhost: assert!(matches!(To::from_str("http://localhost:3000/"), Ok(To::Http(_))))); 165 | case!(to_localhost_path: assert!(matches!(To::from_str("http://localhost:8080/services/api/"), Ok(To::Http(_))))); 166 | case!(to_localhost_https: assert!(matches!(To::from_str("https://localhost:8080/"), Ok(To::Http(_))))); 167 | case!(to_file: assert!(matches!(To::from_str("file://./"), Ok(To::File(_, None))))); 168 | case!(to_file_path: assert!(matches!(To::from_str("file://./static/"), Ok(To::File(_, None))))); 169 | case!(to_file_fallback: assert!(matches!(To::from_str("file://./static/|./static/index.html"), Ok(To::File(_, Some(_)))))); 170 | 171 | case!(to_bad_uri: assert!(matches!(To::from_str("example.com/"), Err(BadRedirectTo::InvalidUri(_))))); 172 | case!(to_bad_scheme: assert!(matches!(To::from_str("ftp://example.com/"), Err(BadRedirectTo::InvalidScheme(_))))); 173 | case!(to_many_fallbacks: assert!(matches!(To::from_str("file://./|./|./"), Err(BadRedirectTo::TooManyFallbacks)))); 174 | case!(to_bad_fallback: assert!(matches!(To::from_str("http://example.com/|./"), Err(BadRedirectTo::FallbackNotAllowed(_))))); 175 | case!(to_no_trailing: assert!(matches!(To::from_str("http://example.com/foo"), Err(BadRedirectTo::NoTrailingSlash)))); 176 | case!(to_no_scheme: assert!(matches!(To::from_str("/foo"), Err(BadRedirectTo::NoScheme)))); 177 | 178 | case!(rules_zip_unequal: assert!(matches!(Rules::zip(vec![From("/".to_string())], vec![]), Err(_)))); 179 | case!(rules_zip: assert!(matches!(Rules::zip(vec![From("/".to_string())], vec![To::Http("/".to_string())]), Ok(_)))); 180 | } 181 | -------------------------------------------------------------------------------- /src/layed/server.rs: -------------------------------------------------------------------------------- 1 | use crate::config::COPY_BUFFER_SIZE; 2 | use crate::err::{AppliesTo, IoErrorExt}; 3 | use crate::layed::backoff::Backoff; 4 | use crate::layed::config::{QUEUE_TIMEOUT, SERVER_ACCEPT_BACKOFF_SECS}; 5 | use crate::layed::heartbeat; 6 | use crate::layed::magic; 7 | use crate::layed::stream::spawn_idle; 8 | use crate::tcp; 9 | use futures::StreamExt; 10 | use futures::future::{Either, select}; 11 | use futures::stream; 12 | use std::io; 13 | use std::net::SocketAddr; 14 | use std::pin::pin; 15 | use std::sync::atomic::{AtomicUsize, Ordering::Relaxed}; 16 | use std::time::Duration; 17 | use tokio::io::{AsyncRead, AsyncWrite, copy_bidirectional_with_sizes}; 18 | use tokio::net::TcpListener; 19 | use tokio::time::error::Elapsed; 20 | use tokio::time::{sleep, timeout}; 21 | 22 | static ACTIVE: AtomicUsize = AtomicUsize::new(0); 23 | 24 | pub async fn run( 25 | gateway_addr: &SocketAddr, 26 | public_addr: &SocketAddr, 27 | accept_gateway_conn: impl Fn(TcpListener) -> Fut + Send + 'static, 28 | ) -> Result<(), io::Error> 29 | where 30 | Fut: Future, TcpListener)> + Send, 31 | Conn: AsyncRead + AsyncWrite + Unpin + Send + 'static, 32 | { 33 | log::info!("Binding to gateway: {}", gateway_addr); 34 | let gateway_connections = TcpListener::bind(gateway_addr).await?; 35 | log::info!("Binding to public: {}", public_addr); 36 | let mut public_connections = TcpListener::bind(public_addr).await?; 37 | 38 | let mut gateway_connections = pin!(spawn_idle(|requests| { 39 | stream::unfold( 40 | (gateway_connections, accept_gateway_conn, requests), 41 | |(mut gateway_connections, accept_gateway_conn, mut requests)| async { 42 | loop { 43 | let mut backoff = Backoff::new(SERVER_ACCEPT_BACKOFF_SECS); 44 | let mut gateway = loop { 45 | let result; 46 | (result, gateway_connections) = 47 | accept_gateway_conn(gateway_connections).await; 48 | match result { 49 | Ok(gateway) => break gateway, 50 | Err(e) => { 51 | log::error!("Error accepting gateway connections: {}", e); 52 | let seconds = backoff.next(); 53 | log::warn!("Retrying in {} seconds", seconds); 54 | sleep(Duration::from_secs(u64::from(seconds))).await; 55 | continue; 56 | } 57 | } 58 | }; 59 | 60 | // early handshake: immediately kill unknown connections 61 | match magic::read_from(&mut gateway).await { 62 | Ok(()) => log::info!("Early handshake succeeded"), 63 | Err(e) => { 64 | log::info!("Early handshake failed: {}", e); 65 | continue; 66 | } 67 | } 68 | 69 | // heartbeat: so the client can tell if the connection drops 70 | let token = { 71 | let heartbeat = pin!(heartbeat::write_forever(&mut gateway)); 72 | match select(requests.next(), heartbeat).await { 73 | Either::Left((Some(token), _)) => token, 74 | Either::Left((None, _)) => return None, 75 | Either::Right((Ok(i), _)) => match i {}, 76 | Either::Right((Err(e), _)) => { 77 | log::info!("Heartbeat failed: {}", e); 78 | continue; 79 | } 80 | } 81 | }; 82 | 83 | return Some(( 84 | (token, gateway), 85 | (gateway_connections, accept_gateway_conn, requests), 86 | )); 87 | } 88 | }, 89 | ) 90 | })); 91 | 92 | 'public: loop { 93 | let mut backoff = Backoff::new(SERVER_ACCEPT_BACKOFF_SECS); 94 | let mut public = loop { 95 | match tcp::accept(&mut public_connections).await { 96 | Ok(public) => break public, 97 | Err(e) => { 98 | log::error!("Error accepting public connections: {}", e); 99 | let seconds = backoff.next(); 100 | log::warn!("Retrying in {} seconds", seconds); 101 | sleep(Duration::from_secs(u64::from(seconds))).await; 102 | continue; 103 | } 104 | } 105 | }; 106 | 107 | let mut gateway = loop { 108 | // drop public connections which wait for too long, to avoid unlimited queuing when no gateway is connected 109 | let mut gateway = match timeout(QUEUE_TIMEOUT, gateway_connections.next()).await { 110 | Ok(Some(gateway)) => gateway, 111 | Ok(None) => return Ok(()), 112 | Err(e) => { 113 | let _: Elapsed = e; 114 | log::info!("Public connection expired waiting for gateway"); 115 | drain_queue(&mut public_connections).await; 116 | continue 'public; 117 | } 118 | }; 119 | 120 | // finish heartbeat: do this as late as possible so clients can't send late handshake and disconnect 121 | match heartbeat::write_final(&mut gateway).await { 122 | Ok(()) => log::info!("Heartbeat completed"), 123 | Err(e) => { 124 | log::info!("Heartbeat failed at finalization: {}", e); 125 | continue; 126 | } 127 | } 128 | 129 | // late handshake: ensure that client hasn't disappeared some time after early handshake 130 | match magic::read_from(&mut gateway).await { 131 | Ok(()) => log::info!("Late handshake succeeded"), 132 | Err(e) => { 133 | log::info!("Late handshake failed: {}", e); 134 | continue; 135 | } 136 | } 137 | 138 | break gateway; 139 | }; 140 | 141 | log::info!("Spawning ({} active)", ACTIVE.fetch_add(1, Relaxed) + 1); 142 | tokio::spawn(async move { 143 | let done = copy_bidirectional_with_sizes( 144 | &mut public, 145 | &mut gateway, 146 | COPY_BUFFER_SIZE, 147 | COPY_BUFFER_SIZE, 148 | ) 149 | .await; 150 | let active = ACTIVE.fetch_sub(1, Relaxed) - 1; 151 | match done { 152 | Ok((down, up)) => log::info!("Closing ({} active): {}/{}", active, down, up), 153 | Err(e) => log::info!("Closing ({} active): {}", active, e), 154 | } 155 | }); 156 | } 157 | } 158 | 159 | pub async fn drain_queue(listener: &mut TcpListener) { 160 | loop { 161 | // timeout because we need to yield to receive the second queued conn 162 | // (listener.poll_recv() won't return Poll::Ready twice in a row, 163 | // even if there are multiple queued connections) 164 | match timeout(Duration::from_millis(1), listener.accept()).await { 165 | Ok(Ok((_, _))) => log::info!("Queued conn dropped"), 166 | Ok(Err(e)) => match e.applies_to() { 167 | AppliesTo::Connection => log::info!("Queued conn dropped: {}", e), 168 | AppliesTo::Listener => break, 169 | }, 170 | Err(e) => { 171 | let _: Elapsed = e; 172 | break; 173 | } 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "anstream" 7 | version = "0.6.21" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "is_terminal_polyfill", 17 | "utf8parse", 18 | ] 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.13" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" 25 | 26 | [[package]] 27 | name = "anstyle-parse" 28 | version = "0.2.7" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 31 | dependencies = [ 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle-query" 37 | version = "1.1.5" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" 40 | dependencies = [ 41 | "windows-sys 0.61.2", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-wincon" 46 | version = "3.0.11" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" 49 | dependencies = [ 50 | "anstyle", 51 | "once_cell_polyfill", 52 | "windows-sys 0.61.2", 53 | ] 54 | 55 | [[package]] 56 | name = "atomic-waker" 57 | version = "1.1.2" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 60 | 61 | [[package]] 62 | name = "base64" 63 | version = "0.22.1" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 66 | 67 | [[package]] 68 | name = "bitflags" 69 | version = "2.10.0" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" 72 | 73 | [[package]] 74 | name = "block-buffer" 75 | version = "0.10.4" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 78 | dependencies = [ 79 | "generic-array", 80 | ] 81 | 82 | [[package]] 83 | name = "bytes" 84 | version = "1.11.0" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" 87 | 88 | [[package]] 89 | name = "cc" 90 | version = "1.2.46" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" 93 | dependencies = [ 94 | "find-msvc-tools", 95 | "shlex", 96 | ] 97 | 98 | [[package]] 99 | name = "cfg-if" 100 | version = "1.0.4" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 103 | 104 | [[package]] 105 | name = "clap" 106 | version = "4.5.51" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" 109 | dependencies = [ 110 | "clap_builder", 111 | "clap_derive", 112 | ] 113 | 114 | [[package]] 115 | name = "clap_builder" 116 | version = "4.5.51" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" 119 | dependencies = [ 120 | "anstream", 121 | "anstyle", 122 | "clap_lex", 123 | "strsim", 124 | ] 125 | 126 | [[package]] 127 | name = "clap_derive" 128 | version = "4.5.49" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" 131 | dependencies = [ 132 | "heck", 133 | "proc-macro2", 134 | "quote", 135 | "syn", 136 | ] 137 | 138 | [[package]] 139 | name = "clap_lex" 140 | version = "0.7.6" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" 143 | 144 | [[package]] 145 | name = "colorchoice" 146 | version = "1.0.4" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 149 | 150 | [[package]] 151 | name = "core-foundation" 152 | version = "0.10.1" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" 155 | dependencies = [ 156 | "core-foundation-sys", 157 | "libc", 158 | ] 159 | 160 | [[package]] 161 | name = "core-foundation-sys" 162 | version = "0.8.7" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 165 | 166 | [[package]] 167 | name = "cpufeatures" 168 | version = "0.2.17" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 171 | dependencies = [ 172 | "libc", 173 | ] 174 | 175 | [[package]] 176 | name = "crypto-common" 177 | version = "0.1.7" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" 180 | dependencies = [ 181 | "generic-array", 182 | "typenum", 183 | ] 184 | 185 | [[package]] 186 | name = "data-encoding" 187 | version = "2.9.0" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" 190 | 191 | [[package]] 192 | name = "digest" 193 | version = "0.10.7" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 196 | dependencies = [ 197 | "block-buffer", 198 | "crypto-common", 199 | ] 200 | 201 | [[package]] 202 | name = "env_filter" 203 | version = "0.1.4" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" 206 | dependencies = [ 207 | "log", 208 | ] 209 | 210 | [[package]] 211 | name = "env_logger" 212 | version = "0.11.8" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" 215 | dependencies = [ 216 | "env_filter", 217 | "jiff", 218 | "log", 219 | ] 220 | 221 | [[package]] 222 | name = "equivalent" 223 | version = "1.0.2" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 226 | 227 | [[package]] 228 | name = "errno" 229 | version = "0.3.14" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" 232 | dependencies = [ 233 | "libc", 234 | "windows-sys 0.61.2", 235 | ] 236 | 237 | [[package]] 238 | name = "fastrand" 239 | version = "2.3.0" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 242 | 243 | [[package]] 244 | name = "find-msvc-tools" 245 | version = "0.1.5" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" 248 | 249 | [[package]] 250 | name = "fnv" 251 | version = "1.0.7" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 254 | 255 | [[package]] 256 | name = "futures" 257 | version = "0.3.31" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 260 | dependencies = [ 261 | "futures-channel", 262 | "futures-core", 263 | "futures-executor", 264 | "futures-io", 265 | "futures-sink", 266 | "futures-task", 267 | "futures-util", 268 | ] 269 | 270 | [[package]] 271 | name = "futures-channel" 272 | version = "0.3.31" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 275 | dependencies = [ 276 | "futures-core", 277 | "futures-sink", 278 | ] 279 | 280 | [[package]] 281 | name = "futures-core" 282 | version = "0.3.31" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 285 | 286 | [[package]] 287 | name = "futures-executor" 288 | version = "0.3.31" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 291 | dependencies = [ 292 | "futures-core", 293 | "futures-task", 294 | "futures-util", 295 | ] 296 | 297 | [[package]] 298 | name = "futures-io" 299 | version = "0.3.31" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 302 | 303 | [[package]] 304 | name = "futures-macro" 305 | version = "0.3.31" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 308 | dependencies = [ 309 | "proc-macro2", 310 | "quote", 311 | "syn", 312 | ] 313 | 314 | [[package]] 315 | name = "futures-sink" 316 | version = "0.3.31" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 319 | 320 | [[package]] 321 | name = "futures-task" 322 | version = "0.3.31" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 325 | 326 | [[package]] 327 | name = "futures-util" 328 | version = "0.3.31" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 331 | dependencies = [ 332 | "futures-channel", 333 | "futures-core", 334 | "futures-io", 335 | "futures-macro", 336 | "futures-sink", 337 | "futures-task", 338 | "memchr", 339 | "pin-project-lite", 340 | "pin-utils", 341 | "slab", 342 | ] 343 | 344 | [[package]] 345 | name = "generic-array" 346 | version = "0.14.7" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 349 | dependencies = [ 350 | "typenum", 351 | "version_check", 352 | ] 353 | 354 | [[package]] 355 | name = "getrandom" 356 | version = "0.2.16" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 359 | dependencies = [ 360 | "cfg-if", 361 | "libc", 362 | "wasi", 363 | ] 364 | 365 | [[package]] 366 | name = "getrandom" 367 | version = "0.3.4" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" 370 | dependencies = [ 371 | "cfg-if", 372 | "libc", 373 | "r-efi", 374 | "wasip2", 375 | ] 376 | 377 | [[package]] 378 | name = "h2" 379 | version = "0.4.12" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" 382 | dependencies = [ 383 | "atomic-waker", 384 | "bytes", 385 | "fnv", 386 | "futures-core", 387 | "futures-sink", 388 | "http", 389 | "indexmap", 390 | "slab", 391 | "tokio", 392 | "tokio-util", 393 | "tracing", 394 | ] 395 | 396 | [[package]] 397 | name = "hashbrown" 398 | version = "0.16.0" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" 401 | 402 | [[package]] 403 | name = "headers" 404 | version = "0.4.1" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" 407 | dependencies = [ 408 | "base64", 409 | "bytes", 410 | "headers-core", 411 | "http", 412 | "httpdate", 413 | "mime", 414 | "sha1", 415 | ] 416 | 417 | [[package]] 418 | name = "headers-core" 419 | version = "0.3.0" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" 422 | dependencies = [ 423 | "http", 424 | ] 425 | 426 | [[package]] 427 | name = "heck" 428 | version = "0.5.0" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 431 | 432 | [[package]] 433 | name = "http" 434 | version = "1.3.1" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 437 | dependencies = [ 438 | "bytes", 439 | "fnv", 440 | "itoa", 441 | ] 442 | 443 | [[package]] 444 | name = "http-body" 445 | version = "1.0.1" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 448 | dependencies = [ 449 | "bytes", 450 | "http", 451 | ] 452 | 453 | [[package]] 454 | name = "http-body-util" 455 | version = "0.1.3" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 458 | dependencies = [ 459 | "bytes", 460 | "futures-core", 461 | "http", 462 | "http-body", 463 | "pin-project-lite", 464 | ] 465 | 466 | [[package]] 467 | name = "httparse" 468 | version = "1.10.1" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 471 | 472 | [[package]] 473 | name = "httpdate" 474 | version = "1.0.3" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 477 | 478 | [[package]] 479 | name = "hyper" 480 | version = "1.8.1" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" 483 | dependencies = [ 484 | "atomic-waker", 485 | "bytes", 486 | "futures-channel", 487 | "futures-core", 488 | "h2", 489 | "http", 490 | "http-body", 491 | "httparse", 492 | "httpdate", 493 | "itoa", 494 | "pin-project-lite", 495 | "pin-utils", 496 | "smallvec", 497 | "tokio", 498 | "want", 499 | ] 500 | 501 | [[package]] 502 | name = "hyper-rustls" 503 | version = "0.27.7" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" 506 | dependencies = [ 507 | "http", 508 | "hyper", 509 | "hyper-util", 510 | "log", 511 | "rustls", 512 | "rustls-native-certs", 513 | "rustls-pki-types", 514 | "tokio", 515 | "tokio-rustls", 516 | "tower-service", 517 | ] 518 | 519 | [[package]] 520 | name = "hyper-util" 521 | version = "0.1.18" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" 524 | dependencies = [ 525 | "bytes", 526 | "futures-channel", 527 | "futures-core", 528 | "futures-util", 529 | "http", 530 | "http-body", 531 | "hyper", 532 | "libc", 533 | "pin-project-lite", 534 | "socket2", 535 | "tokio", 536 | "tower-service", 537 | "tracing", 538 | ] 539 | 540 | [[package]] 541 | name = "indexmap" 542 | version = "2.12.0" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" 545 | dependencies = [ 546 | "equivalent", 547 | "hashbrown", 548 | ] 549 | 550 | [[package]] 551 | name = "is_terminal_polyfill" 552 | version = "1.70.2" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" 555 | 556 | [[package]] 557 | name = "itoa" 558 | version = "1.0.15" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 561 | 562 | [[package]] 563 | name = "jiff" 564 | version = "0.2.16" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" 567 | dependencies = [ 568 | "jiff-static", 569 | "log", 570 | "portable-atomic", 571 | "portable-atomic-util", 572 | "serde_core", 573 | ] 574 | 575 | [[package]] 576 | name = "jiff-static" 577 | version = "0.2.16" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" 580 | dependencies = [ 581 | "proc-macro2", 582 | "quote", 583 | "syn", 584 | ] 585 | 586 | [[package]] 587 | name = "libc" 588 | version = "0.2.177" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" 591 | 592 | [[package]] 593 | name = "linux-raw-sys" 594 | version = "0.11.0" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" 597 | 598 | [[package]] 599 | name = "log" 600 | version = "0.4.28" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" 603 | 604 | [[package]] 605 | name = "memchr" 606 | version = "2.7.6" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 609 | 610 | [[package]] 611 | name = "memmap2" 612 | version = "0.9.9" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" 615 | dependencies = [ 616 | "libc", 617 | ] 618 | 619 | [[package]] 620 | name = "mime" 621 | version = "0.3.17" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 624 | 625 | [[package]] 626 | name = "mio" 627 | version = "1.1.0" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" 630 | dependencies = [ 631 | "libc", 632 | "wasi", 633 | "windows-sys 0.61.2", 634 | ] 635 | 636 | [[package]] 637 | name = "once_cell" 638 | version = "1.21.3" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 641 | 642 | [[package]] 643 | name = "once_cell_polyfill" 644 | version = "1.70.2" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" 647 | 648 | [[package]] 649 | name = "openssl-probe" 650 | version = "0.1.6" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 653 | 654 | [[package]] 655 | name = "pin-project-lite" 656 | version = "0.2.16" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 659 | 660 | [[package]] 661 | name = "pin-utils" 662 | version = "0.1.0" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 665 | 666 | [[package]] 667 | name = "portable-atomic" 668 | version = "1.11.1" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" 671 | 672 | [[package]] 673 | name = "portable-atomic-util" 674 | version = "0.2.4" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" 677 | dependencies = [ 678 | "portable-atomic", 679 | ] 680 | 681 | [[package]] 682 | name = "ppv-lite86" 683 | version = "0.2.21" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 686 | dependencies = [ 687 | "zerocopy", 688 | ] 689 | 690 | [[package]] 691 | name = "proc-macro2" 692 | version = "1.0.103" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" 695 | dependencies = [ 696 | "unicode-ident", 697 | ] 698 | 699 | [[package]] 700 | name = "quote" 701 | version = "1.0.42" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" 704 | dependencies = [ 705 | "proc-macro2", 706 | ] 707 | 708 | [[package]] 709 | name = "r-efi" 710 | version = "5.3.0" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 713 | 714 | [[package]] 715 | name = "rand" 716 | version = "0.9.2" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" 719 | dependencies = [ 720 | "rand_chacha", 721 | "rand_core", 722 | ] 723 | 724 | [[package]] 725 | name = "rand_chacha" 726 | version = "0.9.0" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 729 | dependencies = [ 730 | "ppv-lite86", 731 | "rand_core", 732 | ] 733 | 734 | [[package]] 735 | name = "rand_core" 736 | version = "0.9.3" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 739 | dependencies = [ 740 | "getrandom 0.3.4", 741 | ] 742 | 743 | [[package]] 744 | name = "re" 745 | version = "0.5.2" 746 | dependencies = [ 747 | "bytes", 748 | "clap", 749 | "env_logger", 750 | "futures", 751 | "headers", 752 | "http", 753 | "http-body-util", 754 | "hyper", 755 | "hyper-rustls", 756 | "hyper-util", 757 | "log", 758 | "memmap2", 759 | "ring", 760 | "sha2", 761 | "tempfile", 762 | "thiserror", 763 | "tokio", 764 | "tokio-tungstenite", 765 | "tokio-util", 766 | ] 767 | 768 | [[package]] 769 | name = "ring" 770 | version = "0.17.8" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" 773 | dependencies = [ 774 | "cc", 775 | "cfg-if", 776 | "getrandom 0.2.16", 777 | "libc", 778 | "spin", 779 | "untrusted", 780 | "windows-sys 0.52.0", 781 | ] 782 | 783 | [[package]] 784 | name = "rustix" 785 | version = "1.1.2" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" 788 | dependencies = [ 789 | "bitflags", 790 | "errno", 791 | "libc", 792 | "linux-raw-sys", 793 | "windows-sys 0.61.2", 794 | ] 795 | 796 | [[package]] 797 | name = "rustls" 798 | version = "0.23.35" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" 801 | dependencies = [ 802 | "log", 803 | "once_cell", 804 | "ring", 805 | "rustls-pki-types", 806 | "rustls-webpki", 807 | "subtle", 808 | "zeroize", 809 | ] 810 | 811 | [[package]] 812 | name = "rustls-native-certs" 813 | version = "0.8.2" 814 | source = "registry+https://github.com/rust-lang/crates.io-index" 815 | checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" 816 | dependencies = [ 817 | "openssl-probe", 818 | "rustls-pki-types", 819 | "schannel", 820 | "security-framework", 821 | ] 822 | 823 | [[package]] 824 | name = "rustls-pki-types" 825 | version = "1.13.0" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" 828 | dependencies = [ 829 | "zeroize", 830 | ] 831 | 832 | [[package]] 833 | name = "rustls-webpki" 834 | version = "0.103.8" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" 837 | dependencies = [ 838 | "ring", 839 | "rustls-pki-types", 840 | "untrusted", 841 | ] 842 | 843 | [[package]] 844 | name = "schannel" 845 | version = "0.1.28" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" 848 | dependencies = [ 849 | "windows-sys 0.61.2", 850 | ] 851 | 852 | [[package]] 853 | name = "security-framework" 854 | version = "3.5.1" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" 857 | dependencies = [ 858 | "bitflags", 859 | "core-foundation", 860 | "core-foundation-sys", 861 | "libc", 862 | "security-framework-sys", 863 | ] 864 | 865 | [[package]] 866 | name = "security-framework-sys" 867 | version = "2.15.0" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" 870 | dependencies = [ 871 | "core-foundation-sys", 872 | "libc", 873 | ] 874 | 875 | [[package]] 876 | name = "serde_core" 877 | version = "1.0.228" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 880 | dependencies = [ 881 | "serde_derive", 882 | ] 883 | 884 | [[package]] 885 | name = "serde_derive" 886 | version = "1.0.228" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 889 | dependencies = [ 890 | "proc-macro2", 891 | "quote", 892 | "syn", 893 | ] 894 | 895 | [[package]] 896 | name = "sha1" 897 | version = "0.10.6" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 900 | dependencies = [ 901 | "cfg-if", 902 | "cpufeatures", 903 | "digest", 904 | ] 905 | 906 | [[package]] 907 | name = "sha2" 908 | version = "0.10.9" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 911 | dependencies = [ 912 | "cfg-if", 913 | "cpufeatures", 914 | "digest", 915 | ] 916 | 917 | [[package]] 918 | name = "shlex" 919 | version = "1.3.0" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 922 | 923 | [[package]] 924 | name = "slab" 925 | version = "0.4.11" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" 928 | 929 | [[package]] 930 | name = "smallvec" 931 | version = "1.15.1" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 934 | 935 | [[package]] 936 | name = "socket2" 937 | version = "0.6.1" 938 | source = "registry+https://github.com/rust-lang/crates.io-index" 939 | checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" 940 | dependencies = [ 941 | "libc", 942 | "windows-sys 0.60.2", 943 | ] 944 | 945 | [[package]] 946 | name = "spin" 947 | version = "0.9.8" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 950 | 951 | [[package]] 952 | name = "strsim" 953 | version = "0.11.1" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 956 | 957 | [[package]] 958 | name = "subtle" 959 | version = "2.6.1" 960 | source = "registry+https://github.com/rust-lang/crates.io-index" 961 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 962 | 963 | [[package]] 964 | name = "syn" 965 | version = "2.0.110" 966 | source = "registry+https://github.com/rust-lang/crates.io-index" 967 | checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" 968 | dependencies = [ 969 | "proc-macro2", 970 | "quote", 971 | "unicode-ident", 972 | ] 973 | 974 | [[package]] 975 | name = "tempfile" 976 | version = "3.23.0" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" 979 | dependencies = [ 980 | "fastrand", 981 | "getrandom 0.3.4", 982 | "once_cell", 983 | "rustix", 984 | "windows-sys 0.61.2", 985 | ] 986 | 987 | [[package]] 988 | name = "thiserror" 989 | version = "2.0.17" 990 | source = "registry+https://github.com/rust-lang/crates.io-index" 991 | checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" 992 | dependencies = [ 993 | "thiserror-impl", 994 | ] 995 | 996 | [[package]] 997 | name = "thiserror-impl" 998 | version = "2.0.17" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" 1001 | dependencies = [ 1002 | "proc-macro2", 1003 | "quote", 1004 | "syn", 1005 | ] 1006 | 1007 | [[package]] 1008 | name = "tokio" 1009 | version = "1.48.0" 1010 | source = "registry+https://github.com/rust-lang/crates.io-index" 1011 | checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" 1012 | dependencies = [ 1013 | "bytes", 1014 | "libc", 1015 | "mio", 1016 | "pin-project-lite", 1017 | "socket2", 1018 | "tokio-macros", 1019 | "windows-sys 0.61.2", 1020 | ] 1021 | 1022 | [[package]] 1023 | name = "tokio-macros" 1024 | version = "2.6.0" 1025 | source = "registry+https://github.com/rust-lang/crates.io-index" 1026 | checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" 1027 | dependencies = [ 1028 | "proc-macro2", 1029 | "quote", 1030 | "syn", 1031 | ] 1032 | 1033 | [[package]] 1034 | name = "tokio-rustls" 1035 | version = "0.26.4" 1036 | source = "registry+https://github.com/rust-lang/crates.io-index" 1037 | checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" 1038 | dependencies = [ 1039 | "rustls", 1040 | "tokio", 1041 | ] 1042 | 1043 | [[package]] 1044 | name = "tokio-tungstenite" 1045 | version = "0.28.0" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" 1048 | dependencies = [ 1049 | "futures-util", 1050 | "log", 1051 | "rustls", 1052 | "rustls-native-certs", 1053 | "rustls-pki-types", 1054 | "tokio", 1055 | "tokio-rustls", 1056 | "tungstenite", 1057 | ] 1058 | 1059 | [[package]] 1060 | name = "tokio-util" 1061 | version = "0.7.17" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" 1064 | dependencies = [ 1065 | "bytes", 1066 | "futures-core", 1067 | "futures-sink", 1068 | "pin-project-lite", 1069 | "tokio", 1070 | ] 1071 | 1072 | [[package]] 1073 | name = "tower-service" 1074 | version = "0.3.3" 1075 | source = "registry+https://github.com/rust-lang/crates.io-index" 1076 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 1077 | 1078 | [[package]] 1079 | name = "tracing" 1080 | version = "0.1.41" 1081 | source = "registry+https://github.com/rust-lang/crates.io-index" 1082 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 1083 | dependencies = [ 1084 | "pin-project-lite", 1085 | "tracing-core", 1086 | ] 1087 | 1088 | [[package]] 1089 | name = "tracing-core" 1090 | version = "0.1.34" 1091 | source = "registry+https://github.com/rust-lang/crates.io-index" 1092 | checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 1093 | dependencies = [ 1094 | "once_cell", 1095 | ] 1096 | 1097 | [[package]] 1098 | name = "try-lock" 1099 | version = "0.2.5" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1102 | 1103 | [[package]] 1104 | name = "tungstenite" 1105 | version = "0.28.0" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" 1108 | dependencies = [ 1109 | "bytes", 1110 | "data-encoding", 1111 | "http", 1112 | "httparse", 1113 | "log", 1114 | "rand", 1115 | "rustls", 1116 | "rustls-pki-types", 1117 | "sha1", 1118 | "thiserror", 1119 | "utf-8", 1120 | ] 1121 | 1122 | [[package]] 1123 | name = "typenum" 1124 | version = "1.19.0" 1125 | source = "registry+https://github.com/rust-lang/crates.io-index" 1126 | checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" 1127 | 1128 | [[package]] 1129 | name = "unicode-ident" 1130 | version = "1.0.22" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 1133 | 1134 | [[package]] 1135 | name = "untrusted" 1136 | version = "0.9.0" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 1139 | 1140 | [[package]] 1141 | name = "utf-8" 1142 | version = "0.7.6" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 1145 | 1146 | [[package]] 1147 | name = "utf8parse" 1148 | version = "0.2.2" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1151 | 1152 | [[package]] 1153 | name = "version_check" 1154 | version = "0.9.5" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1157 | 1158 | [[package]] 1159 | name = "want" 1160 | version = "0.3.1" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1163 | dependencies = [ 1164 | "try-lock", 1165 | ] 1166 | 1167 | [[package]] 1168 | name = "wasi" 1169 | version = "0.11.1+wasi-snapshot-preview1" 1170 | source = "registry+https://github.com/rust-lang/crates.io-index" 1171 | checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 1172 | 1173 | [[package]] 1174 | name = "wasip2" 1175 | version = "1.0.1+wasi-0.2.4" 1176 | source = "registry+https://github.com/rust-lang/crates.io-index" 1177 | checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" 1178 | dependencies = [ 1179 | "wit-bindgen", 1180 | ] 1181 | 1182 | [[package]] 1183 | name = "windows-link" 1184 | version = "0.2.1" 1185 | source = "registry+https://github.com/rust-lang/crates.io-index" 1186 | checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 1187 | 1188 | [[package]] 1189 | name = "windows-sys" 1190 | version = "0.52.0" 1191 | source = "registry+https://github.com/rust-lang/crates.io-index" 1192 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1193 | dependencies = [ 1194 | "windows-targets 0.52.6", 1195 | ] 1196 | 1197 | [[package]] 1198 | name = "windows-sys" 1199 | version = "0.60.2" 1200 | source = "registry+https://github.com/rust-lang/crates.io-index" 1201 | checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 1202 | dependencies = [ 1203 | "windows-targets 0.53.5", 1204 | ] 1205 | 1206 | [[package]] 1207 | name = "windows-sys" 1208 | version = "0.61.2" 1209 | source = "registry+https://github.com/rust-lang/crates.io-index" 1210 | checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 1211 | dependencies = [ 1212 | "windows-link", 1213 | ] 1214 | 1215 | [[package]] 1216 | name = "windows-targets" 1217 | version = "0.52.6" 1218 | source = "registry+https://github.com/rust-lang/crates.io-index" 1219 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1220 | dependencies = [ 1221 | "windows_aarch64_gnullvm 0.52.6", 1222 | "windows_aarch64_msvc 0.52.6", 1223 | "windows_i686_gnu 0.52.6", 1224 | "windows_i686_gnullvm 0.52.6", 1225 | "windows_i686_msvc 0.52.6", 1226 | "windows_x86_64_gnu 0.52.6", 1227 | "windows_x86_64_gnullvm 0.52.6", 1228 | "windows_x86_64_msvc 0.52.6", 1229 | ] 1230 | 1231 | [[package]] 1232 | name = "windows-targets" 1233 | version = "0.53.5" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" 1236 | dependencies = [ 1237 | "windows-link", 1238 | "windows_aarch64_gnullvm 0.53.1", 1239 | "windows_aarch64_msvc 0.53.1", 1240 | "windows_i686_gnu 0.53.1", 1241 | "windows_i686_gnullvm 0.53.1", 1242 | "windows_i686_msvc 0.53.1", 1243 | "windows_x86_64_gnu 0.53.1", 1244 | "windows_x86_64_gnullvm 0.53.1", 1245 | "windows_x86_64_msvc 0.53.1", 1246 | ] 1247 | 1248 | [[package]] 1249 | name = "windows_aarch64_gnullvm" 1250 | version = "0.52.6" 1251 | source = "registry+https://github.com/rust-lang/crates.io-index" 1252 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1253 | 1254 | [[package]] 1255 | name = "windows_aarch64_gnullvm" 1256 | version = "0.53.1" 1257 | source = "registry+https://github.com/rust-lang/crates.io-index" 1258 | checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" 1259 | 1260 | [[package]] 1261 | name = "windows_aarch64_msvc" 1262 | version = "0.52.6" 1263 | source = "registry+https://github.com/rust-lang/crates.io-index" 1264 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1265 | 1266 | [[package]] 1267 | name = "windows_aarch64_msvc" 1268 | version = "0.53.1" 1269 | source = "registry+https://github.com/rust-lang/crates.io-index" 1270 | checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" 1271 | 1272 | [[package]] 1273 | name = "windows_i686_gnu" 1274 | version = "0.52.6" 1275 | source = "registry+https://github.com/rust-lang/crates.io-index" 1276 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1277 | 1278 | [[package]] 1279 | name = "windows_i686_gnu" 1280 | version = "0.53.1" 1281 | source = "registry+https://github.com/rust-lang/crates.io-index" 1282 | checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" 1283 | 1284 | [[package]] 1285 | name = "windows_i686_gnullvm" 1286 | version = "0.52.6" 1287 | source = "registry+https://github.com/rust-lang/crates.io-index" 1288 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1289 | 1290 | [[package]] 1291 | name = "windows_i686_gnullvm" 1292 | version = "0.53.1" 1293 | source = "registry+https://github.com/rust-lang/crates.io-index" 1294 | checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" 1295 | 1296 | [[package]] 1297 | name = "windows_i686_msvc" 1298 | version = "0.52.6" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1301 | 1302 | [[package]] 1303 | name = "windows_i686_msvc" 1304 | version = "0.53.1" 1305 | source = "registry+https://github.com/rust-lang/crates.io-index" 1306 | checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" 1307 | 1308 | [[package]] 1309 | name = "windows_x86_64_gnu" 1310 | version = "0.52.6" 1311 | source = "registry+https://github.com/rust-lang/crates.io-index" 1312 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1313 | 1314 | [[package]] 1315 | name = "windows_x86_64_gnu" 1316 | version = "0.53.1" 1317 | source = "registry+https://github.com/rust-lang/crates.io-index" 1318 | checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" 1319 | 1320 | [[package]] 1321 | name = "windows_x86_64_gnullvm" 1322 | version = "0.52.6" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1325 | 1326 | [[package]] 1327 | name = "windows_x86_64_gnullvm" 1328 | version = "0.53.1" 1329 | source = "registry+https://github.com/rust-lang/crates.io-index" 1330 | checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" 1331 | 1332 | [[package]] 1333 | name = "windows_x86_64_msvc" 1334 | version = "0.52.6" 1335 | source = "registry+https://github.com/rust-lang/crates.io-index" 1336 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1337 | 1338 | [[package]] 1339 | name = "windows_x86_64_msvc" 1340 | version = "0.53.1" 1341 | source = "registry+https://github.com/rust-lang/crates.io-index" 1342 | checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" 1343 | 1344 | [[package]] 1345 | name = "wit-bindgen" 1346 | version = "0.46.0" 1347 | source = "registry+https://github.com/rust-lang/crates.io-index" 1348 | checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" 1349 | 1350 | [[package]] 1351 | name = "zerocopy" 1352 | version = "0.8.27" 1353 | source = "registry+https://github.com/rust-lang/crates.io-index" 1354 | checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" 1355 | dependencies = [ 1356 | "zerocopy-derive", 1357 | ] 1358 | 1359 | [[package]] 1360 | name = "zerocopy-derive" 1361 | version = "0.8.27" 1362 | source = "registry+https://github.com/rust-lang/crates.io-index" 1363 | checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" 1364 | dependencies = [ 1365 | "proc-macro2", 1366 | "quote", 1367 | "syn", 1368 | ] 1369 | 1370 | [[package]] 1371 | name = "zeroize" 1372 | version = "1.8.2" 1373 | source = "registry+https://github.com/rust-lang/crates.io-index" 1374 | checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" 1375 | --------------------------------------------------------------------------------