diff --git a/Cargo.lock b/Cargo.lock index 29647f9..5e75a18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -487,9 +487,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" [[package]] name = "camino" @@ -2983,9 +2983,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.22" +version = "0.23.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7" +checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" dependencies = [ "aws-lc-rs", "log", @@ -3370,7 +3370,10 @@ name = "sparse-beacon" version = "0.7.0" dependencies = [ "async-trait", + "bytes", "futures", + "http", + "http-body-util", "hyper", "hyper-rustls", "hyper-util", @@ -3379,6 +3382,7 @@ dependencies = [ "pcap-sys", "pin-project", "rand 0.9.0", + "rustls", "simple_logger", "smoltcp", "sparse-actions", @@ -3396,7 +3400,9 @@ dependencies = [ "axum", "axum-server", "futures", + "rcgen", "rustls", + "rustls-pki-types", "serde", "serde_json", "sqlx", diff --git a/pcap-sys/src/ffi.rs b/pcap-sys/src/ffi.rs index 5c559d3..a8c110c 100644 --- a/pcap-sys/src/ffi.rs +++ b/pcap-sys/src/ffi.rs @@ -13,7 +13,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use libc::{c_char, c_int, c_uchar, c_uint, c_ushort, c_void}; +use libc::{c_char, c_int, c_uchar, c_uint, c_ushort}; pub const DLT_NULL: i32 = 0; pub const DLT_EN10MB: i32 = 1; @@ -161,17 +161,6 @@ extern "C" { mask: u32, ) -> c_int; pub fn pcap_setfilter(dev: *mut PcapDev, fp: *const BpfProgram) -> c_int; - pub fn pcap_loop( - p: *mut PcapDev, - cnt: c_int, - callback: unsafe extern "C" fn( - user: *mut c_void, - header: *const PktHeader, - data: *const u8, - ), - user: *mut c_void, - ) -> c_int; - pub fn pcap_breakloop(p: *mut PcapDev); pub fn pcap_sendpacket(p: *mut PcapDev, buf: *const c_uchar, size: c_int) -> c_int; pub fn pcap_setnonblock(dev: *mut PcapDev, nonblock: c_int, errbuf: *mut c_char) -> c_int; pub fn pcap_get_selectable_fd(p: *mut PcapDev) -> c_int; diff --git a/pcap-sys/src/lib.rs b/pcap-sys/src/lib.rs index f4f4471..20df86b 100644 --- a/pcap-sys/src/lib.rs +++ b/pcap-sys/src/lib.rs @@ -122,32 +122,19 @@ pub struct BpfProgram {} pub struct Interface { dev_name: CString, dev: *mut ffi::PcapDev, - absorbed: bool, nonblocking: bool, state: State, } impl Drop for Interface { fn drop(&mut self) { - if !self.absorbed { - unsafe { ffi::pcap_close(self.dev) }; - } + unsafe { ffi::pcap_close(self.dev) }; } } unsafe impl Send for Interface {} unsafe impl Sync for Interface {} -struct ListenHandler<'a, F> -where - F: FnMut(&Interface, packets::EthernetPkt) -> error::Result, -{ - packet_handler: F, - break_on_fail: bool, - fail_error: Option, - interface: &'a Interface, -} - impl Interface { pub fn new(name: &str) -> error::Result { let mut errbuf = [0i8; ffi::PCAP_ERRBUF_SIZE]; @@ -162,7 +149,6 @@ impl Interface { Ok(Interface { dev_name, dev, - absorbed: false, nonblocking: false, state: State::Disabled, }) @@ -263,7 +249,6 @@ impl Interface { Err(unsafe { ffi::pcap_geterr(self.dev) })?; } - self.absorbed = true; self.state = State::Activated; Ok(()) @@ -390,85 +375,6 @@ impl Interface { Ok(packets::EthernetPkt { data: rdata }.to_owned()) } - pub fn listen( - &self, - packet_handler: F, - break_on_fail: bool, - packet_count: i32, - ) -> error::Result<(Option, i32)> - where - F: FnMut(&Interface, packets::EthernetPkt) -> error::Result, - { - if self.state == State::Listening { - return Err(error::Error::IncorrectDeviceState( - State::Activated, - self.state, - )); - } - - unsafe extern "C" fn cback( - user: *mut libc::c_void, - header: *const ffi::PktHeader, - data: *const u8, - ) where - F: FnMut(&Interface, packets::EthernetPkt) -> error::Result, - { - let info = &mut *(user as *mut ListenHandler); - - let data = slice::from_raw_parts(data, (*header).caplen as usize); - - if data.len() < 14 { - eprintln!( - " * Failed to get full packet, captured {} bytes", - data.len() - ); - } - - let result = (info.packet_handler)(info.interface, packets::EthernetPkt { data }); - - match result { - Err(e) => { - eprintln!(" * Packet handle error: {:?}", e); - if info.break_on_fail { - info.fail_error = Some(e); - ffi::pcap_breakloop(info.interface.dev); - } - } - Ok(b) => { - if b { - ffi::pcap_breakloop(info.interface.dev); - } - } - } - } - - let interface = Interface { - dev_name: self.dev_name.clone(), - dev: self.dev, - absorbed: true, - nonblocking: self.nonblocking, - state: State::Listening, - }; - - let mut info = ListenHandler:: { - packet_handler, - break_on_fail, - fail_error: None, - interface: &interface, - }; - - let count = unsafe { - ffi::pcap_loop( - self.dev, - packet_count, - cback::, - &mut info as *mut ListenHandler as *mut libc::c_void, - ) - }; - - Ok((info.fail_error, count)) - } - #[cfg(target_os = "windows")] pub fn get_wait_ready_callback(&self) -> WaitHandle { let handle = unsafe { ffi::pcap_getevent(self.dev) }; diff --git a/sparse-beacon/Cargo.toml b/sparse-beacon/Cargo.toml index e61f134..b1564f2 100644 --- a/sparse-beacon/Cargo.toml +++ b/sparse-beacon/Cargo.toml @@ -14,12 +14,16 @@ tracing = "0.1.41" rand = "0.9.0" pin-project = "1.1.9" hyper-util = { version = "0.1.10", features = ["client", "client-legacy", "http1", "http2", "service", "tokio"] } -hyper-rustls = { version = "0.27.5", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } +hyper-rustls = { version = "0.27.5", default-features = false, features = ["http1", "http2", "native-tokio", "ring"] } +rustls = { version = "0.23.23", default-features = false, features = ["ring", "std"] } tower-service = "0.3.3" futures = "0.3.31" simple_logger = "5.0.0" +http = "1.2.0" pcap-sys = { version = "0.1.0", path = "../pcap-sys" } sparse-actions = { version = "2.0.0", path = "../sparse-actions" } packets = { version = "0.1.0", path = "../packets" } nl-sys = { version = "0.1.0", path = "../nl-sys" } +bytes = "1.10.0" +http-body-util = "0.1.2" diff --git a/sparse-beacon/src/callback.rs b/sparse-beacon/src/callback.rs index 66bc9dc..79c1cd4 100644 --- a/sparse-beacon/src/callback.rs +++ b/sparse-beacon/src/callback.rs @@ -1,313 +1,51 @@ -use std::{ - future::Future, - net::Ipv4Addr, - pin::Pin, - sync::{Arc, Mutex}, - task::{Context, Poll}, -}; +use std::{future::Future, pin::Pin, task::{self, Poll}}; -use futures::ready; -use smoltcp::{ - iface::{Config, Interface, SocketHandle, SocketSet}, - socket::tcp::{RecvError, SendError, Socket, SocketBuffer, State}, - time::Instant, - wire::{EthernetAddress, IpCidr, Ipv4Address}, -}; -use tokio::{ - io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, - task::{spawn, spawn_blocking, JoinHandle}, -}; +use futures::stream::StreamExt; +use http::Uri; +use http_body_util::{Empty, BodyExt}; +use hyper::Request; +use hyper_util::{client::legacy::Client, rt::{TokioExecutor, TokioIo}}; +use rustls::RootCertStore; +use tower_service::Service; use sparse_actions::payload_types::Parameters; -use crate::{adapter, error}; +use crate::{adapter, error, tcp::{self, setup_network}}; -pub struct NetInterfaceHandle { - net: Arc, crate::socket::RawSocket, Interface)>>, - tcp_handle: SocketHandle, - - background_process: JoinHandle<()>, -} - -impl Drop for NetInterfaceHandle { - fn drop(&mut self) { - self.background_process.abort(); - } -} - -impl AsyncRead for NetInterfaceHandle { - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - tbuf: &mut tokio::io::ReadBuf<'_>, - ) -> Poll> { - let this = self.get_mut(); - let Ok(mut inner) = this.net.lock() else { - return Poll::Ready(Err(std::io::Error::new( - std::io::ErrorKind::BrokenPipe, - "mutex for tcp connection is poisoned", - ))); - }; - let (ref mut s_guard, _, _) = *inner; - - let socket = s_guard.get_mut::(this.tcp_handle); - - let has_data = socket.can_recv(); - while socket.can_recv() { - let buf = match socket.recv(|buf| (buf.len(), buf.to_vec())) { - Ok(v) => v, - Err(RecvError::InvalidState) => { - return Poll::Ready(Err(std::io::Error::new( - std::io::ErrorKind::NetworkDown, - "received InvalidState from smoltcp", - ))); - } - Err(RecvError::Finished) => { - return Poll::Ready(Err(std::io::Error::new( - std::io::ErrorKind::BrokenPipe, - "tried reading from finished connection", - ))); - } - }; - tbuf.put_slice(&buf); - } - - socket.register_recv_waker(cx.waker()); - - if has_data { - Poll::Ready(Ok(())) - } else { - Poll::Pending - } - } -} - -impl AsyncWrite for NetInterfaceHandle { - fn poll_write( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - src: &[u8], - ) -> Poll> { - let this = self.get_mut(); - let Ok(mut inner) = this.net.lock() else { - return Poll::Ready(Err(std::io::Error::new( - std::io::ErrorKind::BrokenPipe, - "mutex for tcp connection is poisoned", - ))); - }; - let (ref mut s_guard, _, _) = *inner; - - let socket = s_guard.get_mut::(this.tcp_handle); - - socket.register_send_waker(cx.waker()); - - if socket.can_send() { - let to_send = socket.send_capacity().min(src.len()); - match socket.send_slice(&src[..to_send]) { - Ok(s) => Poll::Ready(Ok(s)), - Err(SendError::InvalidState) => { - return Poll::Ready(Err(std::io::Error::new( - std::io::ErrorKind::NetworkDown, - "received InvalidState from smoltcp", - ))) - } - } - } else { - Poll::Pending - } - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.get_mut(); - let Ok(mut inner) = this.net.lock() else { - return Poll::Ready(Err(std::io::Error::new( - std::io::ErrorKind::BrokenPipe, - "mutex for tcp connection is poisoned", - ))); - }; - let (ref mut s_guard, _, _) = *inner; - - let socket = s_guard.get_mut::(this.tcp_handle); - socket.close(); - - Poll::Ready(Ok(())) - } -} - -pub async fn setup_network( - adapter: &T, - parameters: &Parameters, -) -> Result +#[derive(Clone)] +pub struct ServerConnector where - T: adapter::BeaconAdapter + Clone + Send + 'static, + T: adapter::BeaconAdapter + Clone + Send + 'static { - let net_info = tokio::task::spawn_blocking({ - let adapter = adapter.clone(); - move || adapter.networking_info() - }) - .await??; + adapter: T, + parameters: Parameters +} - let (interface, gateway_ip, mac_address, source_ip, netmask) = match unsafe { - parameters.source_ip.custom_networking.mode - } { - 0 => { - // custom networking - let interface_name = unsafe { - ¶meters.source_ip.custom_networking.interface - [..parameters.source_ip.custom_networking.interface_len as usize] - }; +impl Service for ServerConnector +where + T: adapter::BeaconAdapter + Clone + Send + Sync + 'static +{ + type Response = TokioIo; + type Error = error::BeaconError; + type Future = Pin> + Send + >>; - let interface = if interface_name.is_empty() { - let Some(default_route) = net_info.routes.iter().find(|r| r.network.1 == 0) else { - return Err(error::BeaconError::NoDefaultRoute); - }; - - &net_info.interfaces[default_route.interface_index] - } else { - net_info - .interfaces - .iter() - .find(|intf| intf.name == interface_name) - .ok_or(error::BeaconError::NoDefaultRoute)? - }; - - unsafe { - ( - interface, - Ipv4Addr::new( - parameters.source_ip.custom_networking.gateway.a, - parameters.source_ip.custom_networking.gateway.b, - parameters.source_ip.custom_networking.gateway.c, - parameters.source_ip.custom_networking.gateway.d, - ), - parameters.source_ip.custom_networking.source_mac.clone(), - Ipv4Addr::new( - parameters.source_ip.custom_networking.source_ip.a, - parameters.source_ip.custom_networking.source_ip.b, - parameters.source_ip.custom_networking.source_ip.c, - parameters.source_ip.custom_networking.source_ip.d, - ), - parameters.source_ip.custom_networking.netmask as u8, - ) - } - } - 1 => { - // host networking - let Some(default_route) = net_info.routes.iter().find(|r| r.network.1 == 0) else { - return Err(error::BeaconError::NoDefaultRoute); - }; - - let default_route_if = &net_info.interfaces[default_route.interface_index]; - - ( - default_route_if, - default_route.gateway.0, - default_route_if.mac_addr.clone(), - unsafe { - Ipv4Addr::new( - parameters.source_ip.use_host_networking.source_ip.a, - parameters.source_ip.use_host_networking.source_ip.b, - parameters.source_ip.use_host_networking.source_ip.c, - parameters.source_ip.use_host_networking.source_ip.d, - ) - }, - default_route.gateway.1, - ) - } - _ => panic!("Corrupted parameters present!"), - }; - - let go_promisc = mac_address != [0, 0, 0, 0, 0, 0]; - let mac_address = Some(mac_address) - .filter(|smac| smac != &[0, 0, 0, 0, 0, 0]) - .unwrap_or(interface.mac_addr); - - let local_port = 49152 + rand::random::() % 16384; - let mut device = crate::socket::RawSocket::new::(interface, go_promisc, local_port)?; - - let mut config = Config::new(EthernetAddress(mac_address).into()); - config.random_seed = rand::random(); - - let mut iface = Interface::new(config, &mut device, Instant::now()); - iface.update_ip_addrs(|addrs| { - addrs - .push(IpCidr::new(source_ip.into(), netmask)) - .expect("could not add new IP address"); - }); - iface - .routes_mut() - .add_default_ipv4_route(gateway_ip.into()) - .expect("did not expect route table to be full"); - - let tcp_rx_buffer = SocketBuffer::new(vec![0; 8192]); - let tcp_tx_buffer = SocketBuffer::new(vec![0; 8192]); - let tcp_socket = Socket::new(tcp_rx_buffer, tcp_tx_buffer); - - let mut sockets = SocketSet::new(vec![]); - let tcp_handle = sockets.add(tcp_socket); - - let mut active = false; - let ready_wait = device.get_ready_wait_callback(); - - let destination = ( - Ipv4Address::new( - parameters.destination_ip.a, - parameters.destination_ip.b, - parameters.destination_ip.c, - parameters.destination_ip.d, - ), - 8080, //parameters.destination_port, - ); - - while !active { - let timestamp = Instant::now(); - iface.poll(timestamp, &mut device, &mut sockets); - - let cx = iface.context(); - - let socket = sockets.get_mut::(tcp_handle); - if !socket.is_active() { - socket.connect(cx, destination, local_port)?; - } - active = socket.is_active() && socket.state() == State::Established; - - ready_wait.wait(iface.poll_delay(timestamp, &sockets).map(Into::into))?; + fn poll_ready(&mut self, _: &mut task::Context<'_>) -> Poll> { + Poll::Ready(Ok(())) } - let net = Arc::new(Mutex::new((sockets, device, iface))); - - let background_process = spawn({ - let net = Arc::clone(&net); - - async move { - loop { - let delay = { - let Ok(mut guard) = net.lock() else { - continue; - }; - let (ref mut s_guard, ref mut d_guard, ref mut i_guard) = *guard; - - let timestamp = Instant::now(); - i_guard.poll(timestamp, d_guard, s_guard); - - i_guard.poll_delay(timestamp, s_guard) - }; - - let _ = ready_wait.wait(delay.map(Into::into)); + fn call(&mut self, _: Uri) -> Self::Future { + Box::pin({ + let adapter = self.adapter.clone(); + let params = self.parameters.clone(); + async move { + setup_network(adapter, params) + .await + .map(TokioIo::new) } - } - }); - - Ok(NetInterfaceHandle { - net, - tcp_handle, - - background_process, - }) + }) + } } pub async fn perform_callback( @@ -315,23 +53,57 @@ pub async fn perform_callback( parameters: &Parameters, ) -> Result<(), error::BeaconError> where - T: adapter::BeaconAdapter + Clone + Send + 'static, + T: adapter::BeaconAdapter + Clone + Send + Sync + 'static, { - println!("Attempting net connection..."); - let mut net_handle = setup_network(adapter, parameters).await?; - println!("Got connection!"); + let server_cert = rustls::pki_types::CertificateDer::from( + parameters.pubkey_cert[..parameters.pubkey_cert_size as usize].to_owned() + ); - let mut buffer = vec![0u8; 4096]; + let client_cert = rustls::pki_types::CertificateDer::from( + parameters.client_cert[..parameters.client_cert_length as usize].to_owned() + ); - net_handle.write(&*b"Hello there\n").await?; + let client_key = rustls::pki_types::PrivateKeyDer::try_from( + parameters.client_key[..parameters.client_key_length as usize].to_owned() + ) + .map_err(|_| rustls::Error::InvalidCertificate( + rustls::CertificateError::BadEncoding + ))?; - while let Ok(v) = net_handle.read(&mut buffer).await { - println!("Received {v} bytes: {:?}", &buffer[..v]); + let mut root_store = RootCertStore::empty(); + root_store.add(server_cert.clone())?; - net_handle.write(&buffer[..v]).await?; + let tls_config = rustls::ClientConfig::builder() + .with_root_certificates(root_store) + .with_client_auth_cert( + vec![client_cert, server_cert], + client_key + )?; + + let https = hyper_rustls::HttpsConnectorBuilder::new() + .with_tls_config(tls_config) + .https_only() + .enable_http1() + .enable_http2() + .wrap_connector(ServerConnector { + adapter: adapter.clone(), + parameters: parameters.clone() + }); + + let client = Client::builder(TokioExecutor::new()) + .build(https); + + for _ in 1..5 { + let req = Request::builder() + .uri("https://sparse.com/hidden_sparse/test".parse::()?) + .body(Empty::::new())?; + let resp = client.request(req).await?; + + println!("{:?} {:?}", resp.version(), resp.status()); + let body = resp.into_body(); + let body = body.collect().await; + println!("{:?}", body); } - println!("Finishing connection"); - Ok(()) } diff --git a/sparse-beacon/src/error.rs b/sparse-beacon/src/error.rs index e6919ba..e2600ad 100644 --- a/sparse-beacon/src/error.rs +++ b/sparse-beacon/src/error.rs @@ -16,4 +16,12 @@ pub enum BeaconError { Connect(#[from] smoltcp::socket::tcp::ConnectError), #[error("netlink error")] Nl(#[from] nl_sys::error::Error), + #[error("http comms error")] + Http(#[from] http::Error), + #[error("uri parse error")] + InvalidUri(#[from] http::uri::InvalidUri), + #[error("hyper http error")] + HyperError(#[from] hyper_util::client::legacy::Error), + #[error("rustls")] + Rustls(#[from] rustls::Error), } diff --git a/sparse-beacon/src/lib.rs b/sparse-beacon/src/lib.rs index 6198b70..202c109 100644 --- a/sparse-beacon/src/lib.rs +++ b/sparse-beacon/src/lib.rs @@ -1,14 +1,16 @@ use sparse_actions::payload_types::Parameters; -pub mod adapter; mod callback; -pub mod error; mod socket; +mod tcp; + +pub mod adapter; +pub mod error; pub use error::BeaconError; pub async fn run_beacon_step(host_adapter: A, params: Parameters) -> Result<(), BeaconError> where - A: adapter::BeaconAdapter + Clone + Send + 'static, + A: adapter::BeaconAdapter + Clone + Send + Sync + 'static, { callback::perform_callback(&host_adapter, ¶ms).await?; diff --git a/sparse-beacon/src/tcp.rs b/sparse-beacon/src/tcp.rs new file mode 100644 index 0000000..1a8afbc --- /dev/null +++ b/sparse-beacon/src/tcp.rs @@ -0,0 +1,328 @@ +use std::{ + net::Ipv4Addr, + pin::Pin, + sync::{Arc, Mutex}, + task::{Context, Poll}, +}; + +use smoltcp::{ + iface::{Config, Interface, SocketHandle, SocketSet}, + socket::tcp::{RecvError, SendError, Socket, SocketBuffer, State}, + time::Instant, + wire::{EthernetAddress, IpCidr, Ipv4Address}, +}; +use tokio::{ + io::{AsyncRead, AsyncWrite}, + task::{spawn_blocking, JoinHandle}, + sync::broadcast +}; +use hyper_util::client::legacy::connect; + +use sparse_actions::payload_types::Parameters; + +use crate::{adapter, error}; + +pub struct NetInterfaceHandle { + net: Arc, crate::socket::RawSocket, Interface)>>, + tcp_handle: SocketHandle, + + close_background: broadcast::Sender<()>, + background_process: JoinHandle<()>, +} + +impl Drop for NetInterfaceHandle { + fn drop(&mut self) { + println!("Running drop for net interface handle; {} copies exist", Arc::strong_count(&self.net)); + let _ = self.close_background.send(()); + self.background_process.abort(); + } +} + +impl AsyncRead for NetInterfaceHandle { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + tbuf: &mut tokio::io::ReadBuf<'_>, + ) -> Poll> { + let this = self.get_mut(); + let Ok(mut inner) = this.net.lock() else { + return Poll::Ready(Err(std::io::Error::new( + std::io::ErrorKind::BrokenPipe, + "mutex for tcp connection is poisoned", + ))); + }; + let (ref mut s_guard, _, _) = *inner; + + let socket = s_guard.get_mut::(this.tcp_handle); + + let has_data = socket.can_recv(); + while socket.can_recv() { + let buf = match socket.recv(|buf| (buf.len(), buf.to_vec())) { + Ok(v) => v, + Err(RecvError::InvalidState) => { + return Poll::Ready(Err(std::io::Error::new( + std::io::ErrorKind::NetworkDown, + "received InvalidState from smoltcp", + ))); + } + Err(RecvError::Finished) => { + return Poll::Ready(Err(std::io::Error::new( + std::io::ErrorKind::BrokenPipe, + "tried reading from finished connection", + ))); + } + }; + tbuf.put_slice(&buf); + } + + socket.register_recv_waker(cx.waker()); + + if has_data { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + } +} + +impl AsyncWrite for NetInterfaceHandle { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + src: &[u8], + ) -> Poll> { + let this = self.get_mut(); + let Ok(mut inner) = this.net.lock() else { + return Poll::Ready(Err(std::io::Error::new( + std::io::ErrorKind::BrokenPipe, + "mutex for tcp connection is poisoned", + ))); + }; + let (ref mut s_guard, _, _) = *inner; + + let socket = s_guard.get_mut::(this.tcp_handle); + + socket.register_send_waker(cx.waker()); + + if socket.can_send() { + let to_send = socket.send_capacity().min(src.len()); + match socket.send_slice(&src[..to_send]) { + Ok(s) => Poll::Ready(Ok(s)), + Err(SendError::InvalidState) => { + return Poll::Ready(Err(std::io::Error::new( + std::io::ErrorKind::NetworkDown, + "received InvalidState from smoltcp", + ))) + } + } + } else { + Poll::Pending + } + } + + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + let this = self.get_mut(); + let Ok(mut inner) = this.net.lock() else { + return Poll::Ready(Err(std::io::Error::new( + std::io::ErrorKind::BrokenPipe, + "mutex for tcp connection is poisoned", + ))); + }; + let (ref mut s_guard, _, _) = *inner; + + let socket = s_guard.get_mut::(this.tcp_handle); + socket.close(); + + Poll::Ready(Ok(())) + } +} + +impl connect::Connection for NetInterfaceHandle { + fn connected(&self) -> connect::Connected { + connect::Connected::new() + } +} + +pub async fn setup_network( + adapter: T, + parameters: Parameters, +) -> Result +where + T: adapter::BeaconAdapter + Clone + Send + 'static, +{ + let net_info = tokio::task::spawn_blocking({ + let adapter = adapter.clone(); + move || adapter.networking_info() + }) + .await??; + + let (interface, gateway_ip, mac_address, source_ip, netmask) = match unsafe { + parameters.source_ip.custom_networking.mode + } { + 0 => { + // custom networking + let interface_name = unsafe { + ¶meters.source_ip.custom_networking.interface + [..parameters.source_ip.custom_networking.interface_len as usize] + }; + + let interface = if interface_name.is_empty() { + let Some(default_route) = net_info.routes.iter().find(|r| r.network.1 == 0) else { + return Err(error::BeaconError::NoDefaultRoute); + }; + + &net_info.interfaces[default_route.interface_index] + } else { + net_info + .interfaces + .iter() + .find(|intf| intf.name == interface_name) + .ok_or(error::BeaconError::NoDefaultRoute)? + }; + + unsafe { + ( + interface, + Ipv4Addr::new( + parameters.source_ip.custom_networking.gateway.a, + parameters.source_ip.custom_networking.gateway.b, + parameters.source_ip.custom_networking.gateway.c, + parameters.source_ip.custom_networking.gateway.d, + ), + parameters.source_ip.custom_networking.source_mac.clone(), + Ipv4Addr::new( + parameters.source_ip.custom_networking.source_ip.a, + parameters.source_ip.custom_networking.source_ip.b, + parameters.source_ip.custom_networking.source_ip.c, + parameters.source_ip.custom_networking.source_ip.d, + ), + parameters.source_ip.custom_networking.netmask as u8, + ) + } + } + 1 => { + // host networking + let Some(default_route) = net_info.routes.iter().find(|r| r.network.1 == 0) else { + return Err(error::BeaconError::NoDefaultRoute); + }; + + let default_route_if = &net_info.interfaces[default_route.interface_index]; + + ( + default_route_if, + default_route.gateway.0, + default_route_if.mac_addr.clone(), + unsafe { + Ipv4Addr::new( + parameters.source_ip.use_host_networking.source_ip.a, + parameters.source_ip.use_host_networking.source_ip.b, + parameters.source_ip.use_host_networking.source_ip.c, + parameters.source_ip.use_host_networking.source_ip.d, + ) + }, + default_route.gateway.1, + ) + } + _ => panic!("Corrupted parameters present!"), + }; + + let go_promisc = mac_address != [0, 0, 0, 0, 0, 0]; + let mac_address = Some(mac_address) + .filter(|smac| smac != &[0, 0, 0, 0, 0, 0]) + .unwrap_or(interface.mac_addr); + + let local_port = 49152 + rand::random::() % 16384; + let mut device = crate::socket::RawSocket::new::(interface, go_promisc, local_port)?; + + let mut config = Config::new(EthernetAddress(mac_address).into()); + config.random_seed = rand::random(); + + let mut iface = Interface::new(config, &mut device, Instant::now()); + iface.update_ip_addrs(|addrs| { + addrs + .push(IpCidr::new(source_ip.into(), netmask)) + .expect("could not add new IP address"); + }); + iface + .routes_mut() + .add_default_ipv4_route(gateway_ip.into()) + .expect("did not expect route table to be full"); + + let tcp_rx_buffer = SocketBuffer::new(vec![0; 8192]); + let tcp_tx_buffer = SocketBuffer::new(vec![0; 8192]); + let tcp_socket = Socket::new(tcp_rx_buffer, tcp_tx_buffer); + + let mut sockets = SocketSet::new(vec![]); + let tcp_handle = sockets.add(tcp_socket); + + let mut active = false; + let ready_wait = device.get_ready_wait_callback(); + + let (close_background, mut close_background_recv) = broadcast::channel(1); + + let destination = ( + Ipv4Address::new( + parameters.destination_ip.a, + parameters.destination_ip.b, + parameters.destination_ip.c, + parameters.destination_ip.d, + ), + parameters.destination_port, + ); + + while !active { + let timestamp = Instant::now(); + iface.poll(timestamp, &mut device, &mut sockets); + + let cx = iface.context(); + + let socket = sockets.get_mut::(tcp_handle); + if !socket.is_active() { + socket.connect(cx, destination, local_port)?; + } + active = socket.is_active() && socket.state() == State::Established; + + ready_wait.wait(iface.poll_delay(timestamp, &sockets).map(Into::into))?; + } + + let net = Arc::new(Mutex::new((sockets, device, iface))); + + let background_process = spawn_blocking({ + let net = Arc::clone(&net); + + move || { + loop { + if close_background_recv.try_recv().is_ok() { + println!("Running drop for background thread; {} copies exist", Arc::strong_count(&net)); + break; + } + + let delay = { + let Ok(mut guard) = net.lock() else { + continue; + }; + let (ref mut s_guard, ref mut d_guard, ref mut i_guard) = *guard; + + let timestamp = Instant::now(); + i_guard.poll(timestamp, d_guard, s_guard); + + i_guard.poll_delay(timestamp, s_guard) + }; + + let _ = ready_wait.wait(delay.map(Into::into)); + } + } + }); + + Ok(NetInterfaceHandle { + net, + tcp_handle, + + close_background, + background_process, + }) +} diff --git a/sparse-handler/Cargo.toml b/sparse-handler/Cargo.toml index 0cc45f6..3db8e40 100644 --- a/sparse-handler/Cargo.toml +++ b/sparse-handler/Cargo.toml @@ -15,3 +15,5 @@ serde = "1.0" serde_json = "1.0" axum-server = { version = "^0.7", features = ["tokio-rustls", "tls-rustls"] } rustls = "0.23" +rcgen = "0.13.2" +rustls-pki-types = "1.11.0" diff --git a/sparse-handler/src/error.rs b/sparse-handler/src/error.rs index cedbf27..9ef5fd2 100644 --- a/sparse-handler/src/error.rs +++ b/sparse-handler/src/error.rs @@ -5,6 +5,8 @@ pub enum Error { TokioJoin(tokio::task::JoinError), Io(std::io::Error), Rustls(rustls::Error), + Rcgen(rcgen::Error), + WebPki(rustls::client::VerifierBuilderError), } impl std::fmt::Display for Error { @@ -25,6 +27,12 @@ impl std::fmt::Display for Error { Error::Rustls(err) => { write!(f, "rustls error: {err:?}") } + Error::Rcgen(err) => { + write!(f, "rcgen error: {err:?}") + } + Error::WebPki(err) => { + write!(f, "webpki error: {err:?}") + } } } } @@ -36,6 +44,8 @@ impl std::error::Error for Error { Error::TokioJoin(err) => Some(err), Error::Io(err) => Some(err), Error::Rustls(err) => Some(err), + Error::Rcgen(err) => Some(err), + Error::WebPki(err) => Some(err), _ => None, } } @@ -72,3 +82,15 @@ impl From for Error { Self::Rustls(err) } } + +impl From for Error { + fn from(err: rcgen::Error) -> Self { + Self::Rcgen(err) + } +} + +impl From for Error { + fn from(err: rustls::client::VerifierBuilderError) -> Self { + Self::WebPki(err) + } +} diff --git a/sparse-handler/src/lib.rs b/sparse-handler/src/lib.rs index ea24d40..e6f25be 100644 --- a/sparse-handler/src/lib.rs +++ b/sparse-handler/src/lib.rs @@ -4,6 +4,8 @@ use std::{ }; use axum::routing::{Router, get, post}; +use rcgen::{Certificate, CertificateParams, KeyPair}; +use rustls::{server::WebPkiClientVerifier, RootCertStore}; use sqlx::SqlitePool; use tokio::task::JoinHandle; @@ -114,18 +116,49 @@ pub async fn start_listener( let hidden_app = Router::new().nest("/hidden_sparse", app); - let keypair = match rustls::pki_types::PrivateKeyDer::try_from(listener.privkey.clone()) { - Ok(pk) => pk, - Err(e) => { + let ca_cert = rustls::pki_types::CertificateDer::from(listener.certificate.clone()); + + let (keypair, cert) = { + let ca_keypair = KeyPair::from_der_and_sign_algo( + &rustls_pki_types::PrivateKeyDer::try_from(&*listener.privkey) + .map_err(|_| rcgen::Error::CouldNotParseCertificate)?, + &rcgen::PKCS_ECDSA_P256_SHA256, + )?; + let ca_params = CertificateParams::from_ca_cert_der(&(*listener.certificate).into()) + .map_err(|_| rcgen::Error::CouldNotParseCertificate)?; + let ca_cert = ca_params.self_signed(&ca_keypair)?; + + let server_key = KeyPair::generate()?; + let Ok(server_params) = CertificateParams::new( + vec![listener.domain_name.clone()] + ) else { return Err(crate::error::Error::Generic(format!( - "Could not parse private key: {e}" + "Could not generate new server keychain" ))); - } + }; + let server_cert = server_params.signed_by(&server_key, &ca_cert, &ca_keypair)?; + + let keypair = match rustls::pki_types::PrivateKeyDer::try_from(server_key.serialize_der()) { + Ok(pk) => pk, + Err(e) => { + return Err(crate::error::Error::Generic(format!( + "Could not parse private key: {e}" + ))); + } + }; + let cert = server_cert.into(); + + (keypair, cert) }; - let cert = rustls::pki_types::CertificateDer::from(listener.certificate.clone()); + + let mut root_store = RootCertStore::empty(); + root_store.add(ca_cert)?; + + let client_verifier = WebPkiClientVerifier::builder(root_store.into()) + .build()?; let mut tls_config = rustls::ServerConfig::builder() - .with_no_client_auth() + .with_client_cert_verifier(client_verifier) .with_single_cert(vec![cert], keypair)?; tls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; diff --git a/sparse-server/src/beacons/templates.rs b/sparse-server/src/beacons/templates.rs index 27de457..f1ad8b7 100644 --- a/sparse-server/src/beacons/templates.rs +++ b/sparse-server/src/beacons/templates.rs @@ -107,7 +107,7 @@ pub async fn add_template( let db = expect_context::(); let listener = sqlx::query!( - "SELECT certificate, privkey FROM beacon_listener WHERE listener_id = ?", + "SELECT domain_name, certificate, privkey FROM beacon_listener WHERE listener_id = ?", listener_id ) .fetch_one(&db)