feat: added the ability to start new listeners

This commit is contained in:
Andrew Rioux 2025-02-01 03:11:09 -05:00
parent ba5145c5ae
commit f5afd60086
Signed by: andrew.rioux
GPG Key ID: 9B8BAC47C17ABB94
9 changed files with 194 additions and 14 deletions

67
Cargo.lock generated
View File

@ -89,6 +89,12 @@ version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]]
name = "arc-swap"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
[[package]] [[package]]
name = "async-compression" name = "async-compression"
version = "0.4.18" version = "0.4.18"
@ -189,6 +195,31 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "aws-lc-rs"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c2b7ddaa2c56a367ad27a094ad8ef4faacf8a617c2575acb2ba88949df999ca"
dependencies = [
"aws-lc-sys",
"paste",
"zeroize",
]
[[package]]
name = "aws-lc-sys"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71b2ddd3ada61a305e1d8bb6c005d1eaa7d14d903681edfc400406d523a9b491"
dependencies = [
"bindgen",
"cc",
"cmake",
"dunce",
"fs_extra",
"paste",
]
[[package]] [[package]]
name = "axum" name = "axum"
version = "0.7.9" version = "0.7.9"
@ -290,6 +321,7 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56bac90848f6a9393ac03c63c640925c4b7c8ca21654de40d53f55964667c7d8" checksum = "56bac90848f6a9393ac03c63c640925c4b7c8ca21654de40d53f55964667c7d8"
dependencies = [ dependencies = [
"arc-swap",
"bytes", "bytes",
"futures-util", "futures-util",
"http", "http",
@ -298,6 +330,9 @@ dependencies = [
"hyper", "hyper",
"hyper-util", "hyper-util",
"pin-project-lite", "pin-project-lite",
"rustls",
"rustls-pemfile",
"rustls-pki-types",
"tokio", "tokio",
"tokio-rustls", "tokio-rustls",
"tower 0.4.13", "tower 0.4.13",
@ -490,6 +525,15 @@ dependencies = [
"vec_map", "vec_map",
] ]
[[package]]
name = "cmake"
version = "0.1.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e24a03c8b52922d68a1589ad61032f2c1aa5a8158d2aa0d93c6e9534944bbad6"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "codee" name = "codee"
version = "0.2.0" version = "0.2.0"
@ -799,6 +843,12 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408" checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408"
[[package]]
name = "dunce"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]] [[package]]
name = "either" name = "either"
version = "1.13.0" version = "1.13.0"
@ -922,6 +972,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]] [[package]]
name = "futures" name = "futures"
version = "0.3.31" version = "0.3.31"
@ -2674,6 +2730,7 @@ version = "0.23.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8"
dependencies = [ dependencies = [
"aws-lc-rs",
"once_cell", "once_cell",
"rustls-pki-types", "rustls-pki-types",
"rustls-webpki", "rustls-webpki",
@ -2681,6 +2738,15 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "rustls-pemfile"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
dependencies = [
"rustls-pki-types",
]
[[package]] [[package]]
name = "rustls-pki-types" name = "rustls-pki-types"
version = "1.10.1" version = "1.10.1"
@ -2693,6 +2759,7 @@ version = "0.102.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
dependencies = [ dependencies = [
"aws-lc-rs",
"ring", "ring",
"rustls-pki-types", "rustls-pki-types",
"untrusted", "untrusted",

View File

@ -20,7 +20,7 @@ tower-http = { version = "0.5", features = ["fs", "compression-br", "compression
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
thiserror = "1" thiserror = "1"
http = "1" http = "1"
axum-server = { version = "^0.7", features = ["tokio-rustls"], optional = true } axum-server = { version = "^0.7", features = ["tokio-rustls", "tls-rustls"], optional = true }
tracing-subscriber = { version = "0.3", features = ["chrono", "env-filter", "serde", "tracing", "tracing-serde"], optional = true } tracing-subscriber = { version = "0.3", features = ["chrono", "env-filter", "serde", "tracing", "tracing-serde"], optional = true }
structopt = { version = "0.3", optional = true } structopt = { version = "0.3", optional = true }
anyhow = "1.0" anyhow = "1.0"

View File

@ -3,8 +3,97 @@ use std::{
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
use axum::routing::{get, post, Router};
use axum_server::{service::MakeService, tls_rustls::RustlsConfig};
use sqlx::SqlitePool;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
pub type BeaconListenerHandle = JoinHandle<()>; pub struct BeaconListenerHandle {
join_handle: JoinHandle<()>
}
impl BeaconListenerHandle {
pub fn is_finished(&self) -> bool {
self.join_handle.is_finished()
}
pub fn abort(&self) {
self.join_handle.abort()
}
}
pub type BeaconListenerMap = Arc<RwLock<HashMap<i64, BeaconListenerHandle>>>; pub type BeaconListenerMap = Arc<RwLock<HashMap<i64, BeaconListenerHandle>>>;
pub async fn start_all_listeners(beacon_listener_map: BeaconListenerMap, db: SqlitePool) -> Result<(), crate::error::Error> {
let listener_ids = sqlx::query!("SELECT listener_id FROM beacon_listener")
.fetch_all(&db)
.await?;
tracing::info!("Starting {} listener(s)...", listener_ids.len());
for listener in listener_ids {
start_listener(Arc::clone(&beacon_listener_map), listener.listener_id, db.clone()).await?;
}
Ok(())
}
#[derive(Clone)]
struct ListenerState {
db: SqlitePool
}
pub async fn start_listener(beacon_listener_map: BeaconListenerMap, listener_id: i64, db: SqlitePool) -> Result<(), crate::error::Error> {
{
let Ok(blm_handle) = beacon_listener_map.read() else {
return Err(crate::error::Error::Generic("Could not acquire write lock on beacon listener map".to_string()));
};
if blm_handle.get(&listener_id).is_some() {
return Err(crate::error::Error::Generic("Beacon listener already started".to_string()));
}
}
let listener = sqlx::query!("SELECT * FROM beacon_listener WHERE listener_id = ?", listener_id)
.fetch_one(&db)
.await?;
let app: Router<()> = Router::new()
.route("/register_beacon", post(|| async {}))
.route("/test", get(|| async {
"hi there"
}))
.with_state(ListenerState {
db
});
let hidden_app = Router::new().nest("/hidden_sparse", app);
let tls_config = RustlsConfig::from_pem(
listener.certificate.as_bytes().to_vec(),
listener.privkey.as_bytes().to_vec()
).await?;
let addr = std::net::SocketAddr::from(([0, 0, 0, 0], listener.port as u16));
tracing::debug!("Starting listener {}, {}, on port {}", listener_id, listener.domain_name, listener.port);
let join_handle = tokio::task::spawn(async move {
let res = axum_server::tls_rustls::bind_rustls(addr, tls_config)
.serve(hidden_app.into_make_service())
.await;
if let Err(e) = res {
tracing::error!("error running sparse listener: {e:?}");
}
});
let Ok(mut blm_handle) = beacon_listener_map.write() else {
return Err(crate::error::Error::Generic("Could not acquire write lock on beacon listener map".to_string()));
};
blm_handle.insert(listener_id, BeaconListenerHandle {
join_handle
});
Ok(())
}

View File

@ -111,7 +111,19 @@ pub async fn remove_listener(listener_id: i64) -> Result<(), ServerFnError> {
#[server] #[server]
pub async fn start_listener(listener_id: i64) -> Result<(), ServerFnError> { pub async fn start_listener(listener_id: i64) -> Result<(), ServerFnError> {
unimplemented!() let user = user::get_auth_session().await?;
if user.is_none() {
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
}
crate::beacon_handler::start_listener(
expect_context(),
listener_id,
expect_context()
).await?;
Ok(())
} }
#[component] #[component]
@ -193,7 +205,7 @@ fn DisplayListeners(listeners: Vec<PubListener>) -> impl IntoView {
{listener.public_ip.clone()} {listener.public_ip.clone()}
":" ":"
{listener.port} {listener.port}
")" ") "
{match listener.active { {match listener.active {
true => Either::Left(view! { true => Either::Left(view! {
<span>"active!"</span> <span>"active!"</span>

View File

@ -30,10 +30,6 @@ pub enum Command {
/// Address to bind to for the management interface /// Address to bind to for the management interface
#[structopt(default_value = "127.0.0.1:3000")] #[structopt(default_value = "127.0.0.1:3000")]
management_address: SocketAddrV4, management_address: SocketAddrV4,
/// Public address to bind to for the beacons to call back to
#[structopt(default_value = "127.0.0.1:5000")]
bind_address: SocketAddrV4,
}, },
/// Extract the public key and print it to standard out /// Extract the public key and print it to standard out

View File

@ -8,6 +8,8 @@ pub enum Error {
TokioJoin(tokio::task::JoinError), TokioJoin(tokio::task::JoinError),
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
Pbkdf2(pbkdf2::password_hash::errors::Error), Pbkdf2(pbkdf2::password_hash::errors::Error),
#[cfg(feature = "ssr")]
Io(std::io::Error),
} }
impl std::fmt::Display for Error { impl std::fmt::Display for Error {
@ -31,6 +33,10 @@ impl std::fmt::Display for Error {
Error::Pbkdf2(err) => { Error::Pbkdf2(err) => {
write!(f, "password hash error: {err:?}") write!(f, "password hash error: {err:?}")
} }
#[cfg(feature = "ssr")]
Error::Io(err) => {
write!(f, "io error: {err:?}")
}
} }
} }
} }
@ -42,6 +48,8 @@ impl std::error::Error for Error {
Error::Sqlx(err) => Some(err), Error::Sqlx(err) => Some(err),
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
Error::TokioJoin(err) => Some(err), Error::TokioJoin(err) => Some(err),
#[cfg(feature = "ssr")]
Error::Io(err) => Some(err),
_ => None, _ => None,
} }
} }
@ -75,3 +83,10 @@ impl From<pbkdf2::password_hash::errors::Error> for Error {
Self::Pbkdf2(err) Self::Pbkdf2(err)
} }
} }
#[cfg(feature = "ssr")]
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Self::Io(err)
}
}

View File

@ -79,9 +79,9 @@ async fn main() -> anyhow::Result<std::process::ExitCode> {
tracing::info!("Done running database migrations!"); tracing::info!("Done running database migrations!");
match options.command.clone() { match options.command.clone() {
Some(cli::Command::Serve { management_address, bind_address }) => { Some(cli::Command::Serve { management_address }) => {
tracing::info!("Performing requested action, acting as web server"); tracing::info!("Performing requested action, acting as web server");
webserver::serve_web(management_address, bind_address, pool).await webserver::serve_web(management_address, pool).await
} }
Some(cli::Command::ExtractPubKey { }) => { Some(cli::Command::ExtractPubKey { }) => {
Ok(ExitCode::SUCCESS) Ok(ExitCode::SUCCESS)
@ -95,8 +95,7 @@ async fn main() -> anyhow::Result<std::process::ExitCode> {
tracing::info!("Performing default action of acting as web server"); tracing::info!("Performing default action of acting as web server");
let default_management_ip = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 3000); let default_management_ip = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 3000);
let default_beacon_ip = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 5000); webserver::serve_web(default_management_ip, pool).await
webserver::serve_web(default_management_ip, default_beacon_ip, pool).await
} }
} }
} }

View File

@ -8,7 +8,7 @@ use {
crate::db::user crate::db::user
}; };
fn format_delta(time: chrono::TimeDelta) -> String { pub fn format_delta(time: chrono::TimeDelta) -> String {
let seconds = time.num_seconds(); let seconds = time.num_seconds();
match seconds { match seconds {

View File

@ -8,7 +8,7 @@ use tokio::signal;
use sparse_server::app::*; use sparse_server::app::*;
pub async fn serve_web(management_address: SocketAddrV4, _bind_address: SocketAddrV4, db: SqlitePool) -> anyhow::Result<ExitCode> { pub async fn serve_web(management_address: SocketAddrV4, db: SqlitePool) -> anyhow::Result<ExitCode> {
let conf = get_configuration(None).unwrap(); let conf = get_configuration(None).unwrap();
let leptos_options = conf.leptos_options; let leptos_options = conf.leptos_options;
let routes = generate_route_list(App); let routes = generate_route_list(App);
@ -20,6 +20,8 @@ pub async fn serve_web(management_address: SocketAddrV4, _bind_address: SocketAd
.br(true) .br(true)
.zstd(true); .zstd(true);
crate::beacon_handler::start_all_listeners(std::sync::Arc::clone(&beacon_listeners), db.clone()).await?;
let app = Router::new() let app = Router::new()
.leptos_routes_with_context( .leptos_routes_with_context(
&leptos_options, &leptos_options,