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)