feat: added tcp
sorry Judah
This commit is contained in:
parent
e388b2eefa
commit
f9ff9f266a
230
Cargo.lock
generated
230
Cargo.lock
generated
@ -599,6 +599,16 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271"
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
@ -683,6 +693,16 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
@ -825,6 +845,38 @@ dependencies = [
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defmt"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86f6162c53f659f65d00619fe31f14556a6e9f8752ccc4a41bd177ffcf3d6130"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"defmt-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defmt-macros"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d135dd939bad62d7490b0002602d35b358dce5fd9233a709d3c1ef467d4bde6"
|
||||
dependencies = [
|
||||
"defmt-parser",
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defmt-parser"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3983b127f13995e68c1e29071e5d115cd96f215ccb5e6812e3728cd6f92653b3"
|
||||
dependencies = [
|
||||
"thiserror 2.0.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.9"
|
||||
@ -1254,6 +1306,15 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hash32"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
@ -1280,6 +1341,16 @@ dependencies = [
|
||||
"hashbrown 0.15.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heapless"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
|
||||
dependencies = [
|
||||
"hash32",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.3"
|
||||
@ -1438,6 +1509,25 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.27.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1447,13 +1537,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2013,6 +2106,12 @@ version = "0.4.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
||||
|
||||
[[package]]
|
||||
name = "managed"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d"
|
||||
|
||||
[[package]]
|
||||
name = "manyhow"
|
||||
version = "0.11.4"
|
||||
@ -2233,6 +2332,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.7"
|
||||
@ -2267,6 +2375,12 @@ version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||
|
||||
[[package]]
|
||||
name = "or_poisoned"
|
||||
version = "0.1.0"
|
||||
@ -2357,6 +2471,7 @@ dependencies = [
|
||||
"packets",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2386,18 +2501,18 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.8"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916"
|
||||
checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.1.8"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb"
|
||||
checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -2875,12 +2990,25 @@ dependencies = [
|
||||
"aws-lc-rs",
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pki-types",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "2.2.0"
|
||||
@ -2929,12 +3057,44 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "send_wrapper"
|
||||
version = "0.6.0"
|
||||
@ -3134,6 +3294,18 @@ dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simple_logger"
|
||||
version = "5.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8c5dfa5e08767553704aa0ffd9d9794d527103c736aba9854773851fd7497eb"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"log",
|
||||
"time",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
@ -3161,6 +3333,21 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smoltcp"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dad095989c1533c1c266d9b1e8d70a1329dd3723c3edac6d03bbd67e7bf6f4bb"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"byteorder",
|
||||
"cfg-if",
|
||||
"defmt",
|
||||
"heapless",
|
||||
"log",
|
||||
"managed",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.8"
|
||||
@ -3182,7 +3369,23 @@ dependencies = [
|
||||
name = "sparse-beacon"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"futures",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"hyper-util",
|
||||
"nl-sys",
|
||||
"packets",
|
||||
"pcap-sys",
|
||||
"pin-project",
|
||||
"rand 0.9.0",
|
||||
"simple_logger",
|
||||
"smoltcp",
|
||||
"sparse-actions",
|
||||
"thiserror 2.0.11",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3281,6 +3484,8 @@ dependencies = [
|
||||
name = "sparse-windows-beacon"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"pcap-sys",
|
||||
"windows",
|
||||
"winreg",
|
||||
]
|
||||
@ -3752,7 +3957,9 @@ checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"libc",
|
||||
"num-conv",
|
||||
"num_threads",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
@ -4078,6 +4285,12 @@ dependencies = [
|
||||
"tracing-serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.24.0"
|
||||
@ -4257,6 +4470,15 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
|
||||
dependencies = [
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
||||
@ -92,6 +92,7 @@ extern "C" {
|
||||
pub fn rtnl_link_get_name(link: *mut rtnl_link) -> *const c_char;
|
||||
pub fn rtnl_link_get_ifindex(link: *mut rtnl_link) -> c_int;
|
||||
pub fn rtnl_link_get_type(link: *mut rtnl_link) -> *const c_char;
|
||||
pub fn rtnl_link_get_mtu(link: *mut rtnl_link) -> c_uint;
|
||||
|
||||
pub fn rtnl_route_alloc_cache(
|
||||
sock: *mut nl_sock,
|
||||
|
||||
@ -91,6 +91,11 @@ impl Link {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the MTU of the link
|
||||
pub fn mtu(&self) -> u32 {
|
||||
unsafe { rtnl_link_get_mtu(self.link) }
|
||||
}
|
||||
|
||||
/// Determines the type of link. Ethernet devices are "veth or eth"
|
||||
pub fn ltype(&self) -> Option<String> {
|
||||
unsafe {
|
||||
|
||||
@ -78,6 +78,10 @@ impl<'a> EthernetPkt<'a> {
|
||||
data: self.data.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn raw(&self) -> &[u8] {
|
||||
self.data
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Layer3Pkt<'a> {
|
||||
@ -425,6 +429,10 @@ impl EthernetPacket {
|
||||
pub fn pkt(&'_ self) -> EthernetPkt<'_> {
|
||||
EthernetPkt { data: &self.data }
|
||||
}
|
||||
|
||||
pub fn from_raw(data: Vec<u8>) -> EthernetPacket {
|
||||
Self { data }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
@ -31,4 +31,8 @@ tokio = { version = "1.21.2", features = [
|
||||
"rt-multi-thread",
|
||||
] }
|
||||
tokio-stream = "0.1.14"
|
||||
|
||||
packets = { path = "../packets" }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows = { version = "0.59.0", features = ["Win32_System_Threading"] }
|
||||
|
||||
@ -31,6 +31,7 @@ pub enum Error {
|
||||
InvalidPcapFd,
|
||||
Io(std::io::Error),
|
||||
Libc(Errno),
|
||||
IncorrectDeviceState(crate::State, crate::State),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@ -61,6 +62,10 @@ impl Display for Error {
|
||||
Error::InvalidPcapFd => write!(f, "internal pcap file descriptor error"),
|
||||
Error::Io(io) => write!(f, "std::io error ({io})"),
|
||||
Error::Libc(err) => write!(f, "libc error ({err})"),
|
||||
Error::IncorrectDeviceState(des, cur) => write!(
|
||||
f,
|
||||
"device in incorrect state (desired: {des:?}; current: {cur:?})"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,3 +181,8 @@ extern "C" {
|
||||
pkt_data: *mut *mut c_char,
|
||||
) -> c_int;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
extern "C" {
|
||||
pub fn pcap_getevent(p: *mut PcapDev) -> windows::Win32::Foundation::HANDLE;
|
||||
}
|
||||
|
||||
@ -16,13 +16,12 @@
|
||||
use std::{
|
||||
ffi::{CStr, CString},
|
||||
ptr, slice,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
pub mod error;
|
||||
mod ffi;
|
||||
pub use packets;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod stream;
|
||||
|
||||
pub mod consts {
|
||||
pub use super::ffi::{
|
||||
@ -102,37 +101,33 @@ impl std::iter::Iterator for PcapDevIterator {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait State {}
|
||||
pub trait Activated: State {}
|
||||
pub trait NotListening: Activated {}
|
||||
pub trait Listening: Activated {}
|
||||
pub trait Disabled: State {}
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
pub enum State {
|
||||
Disabled,
|
||||
Activated,
|
||||
Listening,
|
||||
}
|
||||
|
||||
pub enum DevActivated {}
|
||||
impl State for DevActivated {}
|
||||
impl Activated for DevActivated {}
|
||||
impl NotListening for DevActivated {}
|
||||
|
||||
pub enum DevDisabled {}
|
||||
impl State for DevDisabled {}
|
||||
impl Disabled for DevDisabled {}
|
||||
|
||||
pub enum DevListening {}
|
||||
impl State for DevListening {}
|
||||
impl Activated for DevListening {}
|
||||
impl Listening for DevListening {}
|
||||
impl State {
|
||||
fn is_activated(&self) -> bool {
|
||||
match self {
|
||||
Self::Disabled => false,
|
||||
Self::Activated | Self::Listening => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BpfProgram {}
|
||||
|
||||
pub struct Interface<T: State> {
|
||||
pub struct Interface {
|
||||
dev_name: CString,
|
||||
dev: *mut ffi::PcapDev,
|
||||
marker: std::marker::PhantomData<T>,
|
||||
absorbed: bool,
|
||||
nonblocking: bool,
|
||||
state: State,
|
||||
}
|
||||
|
||||
impl<T: State> Drop for Interface<T> {
|
||||
impl Drop for Interface {
|
||||
fn drop(&mut self) {
|
||||
if !self.absorbed {
|
||||
unsafe { ffi::pcap_close(self.dev) };
|
||||
@ -140,11 +135,21 @@ impl<T: State> Drop for Interface<T> {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: State> Send for Interface<T> {}
|
||||
unsafe impl<T: State> Sync for Interface<T> {}
|
||||
unsafe impl Send for Interface {}
|
||||
unsafe impl Sync for Interface {}
|
||||
|
||||
impl<T: State> Interface<T> {
|
||||
pub fn new(name: &str) -> error::Result<Interface<DevDisabled>> {
|
||||
struct ListenHandler<'a, F>
|
||||
where
|
||||
F: FnMut(&Interface, packets::EthernetPkt) -> error::Result<bool>,
|
||||
{
|
||||
packet_handler: F,
|
||||
break_on_fail: bool,
|
||||
fail_error: Option<error::Error>,
|
||||
interface: &'a Interface,
|
||||
}
|
||||
|
||||
impl Interface {
|
||||
pub fn new(name: &str) -> error::Result<Interface> {
|
||||
let mut errbuf = [0i8; ffi::PCAP_ERRBUF_SIZE];
|
||||
|
||||
let dev_name = CString::new(name)?;
|
||||
@ -154,12 +159,12 @@ impl<T: State> Interface<T> {
|
||||
Err(&errbuf)?;
|
||||
}
|
||||
|
||||
Ok(Interface::<DevDisabled> {
|
||||
Ok(Interface {
|
||||
dev_name,
|
||||
dev,
|
||||
marker: std::marker::PhantomData,
|
||||
absorbed: false,
|
||||
nonblocking: false,
|
||||
state: State::Disabled,
|
||||
})
|
||||
}
|
||||
|
||||
@ -200,10 +205,15 @@ impl<T: State> Interface<T> {
|
||||
pub fn name(&self) -> &str {
|
||||
std::str::from_utf8(self.dev_name.as_bytes()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Disabled> Interface<T> {
|
||||
pub fn set_promisc(&mut self, promisc: bool) -> error::Result<()> {
|
||||
if self.state != State::Disabled {
|
||||
return Err(error::Error::IncorrectDeviceState(
|
||||
State::Disabled,
|
||||
self.state,
|
||||
));
|
||||
}
|
||||
|
||||
if unsafe { ffi::pcap_set_promisc(self.dev, i32::from(promisc)) } != 0 {
|
||||
Err(unsafe { ffi::pcap_geterr(self.dev) })?;
|
||||
}
|
||||
@ -212,6 +222,13 @@ impl<T: Disabled> Interface<T> {
|
||||
}
|
||||
|
||||
pub fn set_buffer_size(&mut self, bufsize: i32) -> error::Result<()> {
|
||||
if self.state != State::Disabled {
|
||||
return Err(error::Error::IncorrectDeviceState(
|
||||
State::Disabled,
|
||||
self.state,
|
||||
));
|
||||
}
|
||||
|
||||
if unsafe { ffi::pcap_set_buffer_size(self.dev, bufsize) } != 0 {
|
||||
Err(unsafe { ffi::pcap_geterr(self.dev) })?;
|
||||
}
|
||||
@ -220,6 +237,13 @@ impl<T: Disabled> Interface<T> {
|
||||
}
|
||||
|
||||
pub fn set_timeout(&mut self, timeout: i32) -> error::Result<()> {
|
||||
if self.state != State::Disabled {
|
||||
return Err(error::Error::IncorrectDeviceState(
|
||||
State::Disabled,
|
||||
self.state,
|
||||
));
|
||||
}
|
||||
|
||||
if unsafe { ffi::pcap_set_timeout(self.dev, timeout) } != 0 {
|
||||
Err(unsafe { ffi::pcap_geterr(self.dev) })?;
|
||||
}
|
||||
@ -227,26 +251,33 @@ impl<T: Disabled> Interface<T> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn activate(mut self) -> error::Result<Interface<DevActivated>> {
|
||||
pub fn activate(&mut self) -> error::Result<()> {
|
||||
if self.state != State::Disabled {
|
||||
return Err(error::Error::IncorrectDeviceState(
|
||||
State::Disabled,
|
||||
self.state,
|
||||
));
|
||||
}
|
||||
|
||||
if unsafe { ffi::pcap_activate(self.dev) } != 0 {
|
||||
Err(unsafe { ffi::pcap_geterr(self.dev) })?;
|
||||
}
|
||||
|
||||
self.absorbed = true;
|
||||
self.state = State::Activated;
|
||||
|
||||
Ok(Interface::<DevActivated> {
|
||||
dev_name: self.dev_name.clone(),
|
||||
dev: self.dev,
|
||||
marker: std::marker::PhantomData,
|
||||
absorbed: false,
|
||||
nonblocking: self.nonblocking,
|
||||
})
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Activated> Interface<T> {
|
||||
pub fn datalink(&self) -> i32 {
|
||||
unsafe { ffi::pcap_datalink(self.dev) }
|
||||
pub fn datalink(&self) -> error::Result<i32> {
|
||||
if !self.state.is_activated() {
|
||||
return Err(error::Error::IncorrectDeviceState(
|
||||
State::Activated,
|
||||
self.state,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(unsafe { ffi::pcap_datalink(self.dev) })
|
||||
}
|
||||
|
||||
pub fn set_filter(
|
||||
@ -255,6 +286,13 @@ impl<T: Activated> Interface<T> {
|
||||
optimize: bool,
|
||||
mask: Option<u32>,
|
||||
) -> error::Result<Box<ffi::BpfProgram>> {
|
||||
if !self.state.is_activated() {
|
||||
return Err(error::Error::IncorrectDeviceState(
|
||||
State::Activated,
|
||||
self.state,
|
||||
));
|
||||
}
|
||||
|
||||
let mut bpf = ffi::BpfProgram {
|
||||
bf_len: 0,
|
||||
bpf_insn: ptr::null(),
|
||||
@ -290,6 +328,13 @@ impl<T: Activated> Interface<T> {
|
||||
}
|
||||
|
||||
pub fn sendpacket(&self, packet: packets::EthernetPkt) -> error::Result<()> {
|
||||
if !self.state.is_activated() {
|
||||
return Err(error::Error::IncorrectDeviceState(
|
||||
State::Activated,
|
||||
self.state,
|
||||
));
|
||||
}
|
||||
|
||||
if unsafe {
|
||||
ffi::pcap_sendpacket(
|
||||
self.dev,
|
||||
@ -304,12 +349,37 @@ impl<T: Activated> Interface<T> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn next_packet(&mut self) -> error::Result<packets::EthernetPacket> {
|
||||
pub fn next_packet(&self) -> error::Result<packets::EthernetPacket> {
|
||||
if !self.state.is_activated() {
|
||||
return Err(error::Error::IncorrectDeviceState(
|
||||
State::Activated,
|
||||
self.state,
|
||||
));
|
||||
}
|
||||
|
||||
let mut header: *mut ffi::PktHeader = ptr::null_mut();
|
||||
let mut data: *mut libc::c_char = ptr::null_mut();
|
||||
|
||||
if unsafe { ffi::pcap_next_ex(self.dev, &mut header as *mut _, &mut data as *mut _) < 1 } {
|
||||
return unsafe { Err(ffi::pcap_geterr(self.dev))? };
|
||||
let res =
|
||||
unsafe { ffi::pcap_next_ex(self.dev, &mut header as *mut _, &mut data as *mut _) };
|
||||
|
||||
match res {
|
||||
1 => {} // no problems
|
||||
0 => {
|
||||
// timeout
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::WouldBlock,
|
||||
"pcap timeout",
|
||||
))
|
||||
.map_err(Into::into);
|
||||
}
|
||||
-1 => {
|
||||
// actual error
|
||||
return unsafe { Err(ffi::pcap_geterr(self.dev)).map_err(Into::into) };
|
||||
}
|
||||
_ => {
|
||||
panic!("Unrecognized value returned from pcap_next_ex");
|
||||
}
|
||||
}
|
||||
|
||||
let rdata = unsafe { slice::from_raw_parts(data as *mut u8, (*header).caplen as usize) };
|
||||
@ -319,34 +389,29 @@ impl<T: Activated> Interface<T> {
|
||||
|
||||
Ok(packets::EthernetPkt { data: rdata }.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
struct ListenHandler<'a, F>
|
||||
where
|
||||
F: FnMut(&Interface<DevListening>, packets::EthernetPkt) -> error::Result<bool>,
|
||||
{
|
||||
packet_handler: F,
|
||||
break_on_fail: bool,
|
||||
fail_error: Option<error::Error>,
|
||||
interface: &'a Interface<DevListening>,
|
||||
}
|
||||
|
||||
impl<T: NotListening> Interface<T> {
|
||||
pub fn listen<F>(
|
||||
&self,
|
||||
packet_handler: F,
|
||||
break_on_fail: bool,
|
||||
packet_count: i32,
|
||||
) -> (Option<error::Error>, i32)
|
||||
) -> error::Result<(Option<error::Error>, i32)>
|
||||
where
|
||||
F: FnMut(&Interface<DevListening>, packets::EthernetPkt) -> error::Result<bool>,
|
||||
F: FnMut(&Interface, packets::EthernetPkt) -> error::Result<bool>,
|
||||
{
|
||||
if self.state == State::Listening {
|
||||
return Err(error::Error::IncorrectDeviceState(
|
||||
State::Activated,
|
||||
self.state,
|
||||
));
|
||||
}
|
||||
|
||||
unsafe extern "C" fn cback<F>(
|
||||
user: *mut libc::c_void,
|
||||
header: *const ffi::PktHeader,
|
||||
data: *const u8,
|
||||
) where
|
||||
F: FnMut(&Interface<DevListening>, packets::EthernetPkt) -> error::Result<bool>,
|
||||
F: FnMut(&Interface, packets::EthernetPkt) -> error::Result<bool>,
|
||||
{
|
||||
let info = &mut *(user as *mut ListenHandler<F>);
|
||||
|
||||
@ -380,9 +445,9 @@ impl<T: NotListening> Interface<T> {
|
||||
let interface = Interface {
|
||||
dev_name: self.dev_name.clone(),
|
||||
dev: self.dev,
|
||||
marker: std::marker::PhantomData,
|
||||
absorbed: true,
|
||||
nonblocking: self.nonblocking,
|
||||
state: State::Listening,
|
||||
};
|
||||
|
||||
let mut info = ListenHandler::<F> {
|
||||
@ -401,20 +466,102 @@ impl<T: NotListening> Interface<T> {
|
||||
)
|
||||
};
|
||||
|
||||
(info.fail_error, count)
|
||||
Ok((info.fail_error, count))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn stream(mut self) -> error::Result<stream::InterfaceStream<DevActivated>> {
|
||||
self.set_non_blocking(true)?;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn get_wait_ready_callback(&self) -> WaitHandle {
|
||||
let handle = unsafe { ffi::pcap_getevent(self.dev) };
|
||||
WaitHandle(handle)
|
||||
}
|
||||
|
||||
Ok(stream::InterfaceStream {
|
||||
inner: tokio::io::unix::AsyncFd::with_interest(
|
||||
stream::InternalInterfaceStream::<DevActivated>::new(unsafe {
|
||||
std::mem::transmute(self)
|
||||
})?,
|
||||
tokio::io::Interest::READABLE,
|
||||
)?,
|
||||
})
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn get_wait_ready_callback(&self) -> WaitHandle {
|
||||
let fd = unsafe { ffi::pcap_get_selectable_fd(self.dev) };
|
||||
WaitHandle(fd)
|
||||
}
|
||||
|
||||
pub fn wait_ready(&self, timeout: Option<Duration>) -> error::Result<()> {
|
||||
self.get_wait_ready_callback().wait(timeout)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[derive(Clone)]
|
||||
pub struct WaitHandle(windows::Win32::Foundation::HANDLE);
|
||||
|
||||
#[cfg(unix)]
|
||||
#[derive(Clone)]
|
||||
pub struct WaitHandle(libc::c_int);
|
||||
|
||||
unsafe impl Send for WaitHandle {}
|
||||
unsafe impl Sync for WaitHandle {}
|
||||
|
||||
impl WaitHandle {
|
||||
#[cfg(windows)]
|
||||
pub fn wait(&self, timeout: Option<Duration>) -> error::Result<()> {
|
||||
use windows::Win32::System::Threading::{WaitForSingleObject, INFINITE};
|
||||
|
||||
let timeout = timeout
|
||||
.map(|t| (t.as_millis() & 0xFFFFFFFF) as u32)
|
||||
.unwrap_or(50);
|
||||
|
||||
unsafe {
|
||||
if WaitForSingleObject(self.0, timeout).0 != 0 {
|
||||
Err(std::io::Error::last_os_error()).map_err(Into::into)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn wait(&self, timeout: Option<Duration>) -> error::Result<()> {
|
||||
unsafe {
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
let mut readfds = {
|
||||
let mut readfds = MaybeUninit::<libc::fd_set>::uninit();
|
||||
libc::FD_ZERO(readfds.as_mut_ptr());
|
||||
libc::FD_SET(self.0, readfds.as_mut_ptr());
|
||||
readfds.assume_init()
|
||||
};
|
||||
|
||||
let mut writefds = {
|
||||
let mut writefds = MaybeUninit::<libc::fd_set>::uninit();
|
||||
libc::FD_ZERO(writefds.as_mut_ptr());
|
||||
libc::FD_SET(self.0, writefds.as_mut_ptr());
|
||||
writefds.assume_init()
|
||||
};
|
||||
|
||||
let mut exceptfds = {
|
||||
let mut exceptfds = MaybeUninit::<libc::fd_set>::uninit();
|
||||
libc::FD_ZERO(exceptfds.as_mut_ptr());
|
||||
exceptfds.assume_init()
|
||||
};
|
||||
|
||||
let mut c_timeout = libc::timeval {
|
||||
tv_sec: 0,
|
||||
tv_usec: 50_000,
|
||||
};
|
||||
if let Some(t) = timeout {
|
||||
c_timeout.tv_sec = t.as_secs() as libc::time_t;
|
||||
c_timeout.tv_usec = (t.as_micros() % 1_000_000) as libc::suseconds_t;
|
||||
}
|
||||
|
||||
let res = libc::select(
|
||||
1,
|
||||
&mut readfds,
|
||||
&mut writefds,
|
||||
&mut exceptfds,
|
||||
&mut c_timeout as *mut _,
|
||||
);
|
||||
|
||||
if res == -1 {
|
||||
Err(std::io::Error::last_os_error()).map_err(Into::into)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,343 +0,0 @@
|
||||
// Copyright (C) 2023 Andrew Rioux
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
os::fd::{AsRawFd, RawFd},
|
||||
pin::Pin,
|
||||
task::{self, Poll},
|
||||
};
|
||||
|
||||
use futures::{ready, StreamExt};
|
||||
use tokio::io::unix::AsyncFd;
|
||||
use tokio_stream::StreamMap;
|
||||
|
||||
use super::{
|
||||
error, ffi, packets, Activated, DevActivated, DevDisabled, Disabled, Interface, NotListening,
|
||||
PcapDevIterator, State,
|
||||
};
|
||||
|
||||
pub(crate) struct InternalInterfaceStream<T: Activated> {
|
||||
interface: Interface<T>,
|
||||
fd: RawFd,
|
||||
}
|
||||
|
||||
impl<T: Activated> InternalInterfaceStream<T> {
|
||||
pub(crate) fn new(interface: Interface<T>) -> error::Result<InternalInterfaceStream<T>> {
|
||||
let fd = unsafe { ffi::pcap_get_selectable_fd(interface.dev) };
|
||||
if fd == -1 {
|
||||
return Err(error::Error::InvalidPcapFd);
|
||||
}
|
||||
Ok(Self { interface, fd })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Activated> AsRawFd for InternalInterfaceStream<T> {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.fd
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InterfaceStream<T: Activated> {
|
||||
pub(crate) inner: AsyncFd<InternalInterfaceStream<T>>,
|
||||
}
|
||||
|
||||
impl<T: Activated> InterfaceStream<T> {
|
||||
pub fn sendpacket(&mut self, packet: packets::EthernetPkt) -> error::Result<()> {
|
||||
self.inner.get_mut().interface.sendpacket(packet)
|
||||
}
|
||||
|
||||
pub fn set_filter(
|
||||
&mut self,
|
||||
filter: &str,
|
||||
optimize: bool,
|
||||
mask: Option<u32>,
|
||||
) -> error::Result<Box<ffi::BpfProgram>> {
|
||||
self.inner
|
||||
.get_mut()
|
||||
.interface
|
||||
.set_filter(filter, optimize, mask)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Activated> Unpin for InterfaceStream<T> {}
|
||||
|
||||
impl<T: Activated> futures::Stream for InterfaceStream<T> {
|
||||
type Item = error::Result<packets::EthernetPacket>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context) -> Poll<Option<Self::Item>> {
|
||||
let stream = Pin::into_inner(self);
|
||||
|
||||
loop {
|
||||
let mut guard = ready!(stream.inner.poll_read_ready_mut(cx))?;
|
||||
|
||||
match guard.try_io(|inner| match inner.get_mut().interface.next_packet() {
|
||||
Ok(p) => Ok(Ok(p)),
|
||||
Err(e) => Ok(Err(e)),
|
||||
}) {
|
||||
Ok(result) => {
|
||||
return Poll::Ready(Some(result?));
|
||||
}
|
||||
Err(_would_block) => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_aggregate_interface_filtered<F>(
|
||||
crash: bool,
|
||||
mut f: F,
|
||||
) -> error::Result<AggregateInterface<DevDisabled>>
|
||||
where
|
||||
F: FnMut(&str) -> bool,
|
||||
{
|
||||
let interfaces = if crash {
|
||||
PcapDevIterator::new()?
|
||||
.filter(|s| (f)(s))
|
||||
.map(|if_name| {
|
||||
let new_name = if_name.clone();
|
||||
Interface::<DevDisabled>::new(&if_name)
|
||||
.map(|interface| (if_name, interface))
|
||||
.map_err(|e| e.add_ifname(&new_name))
|
||||
})
|
||||
.collect::<error::Result<HashMap<_, _>>>()?
|
||||
} else {
|
||||
PcapDevIterator::new()?
|
||||
.filter(|s| (f)(s))
|
||||
.filter_map(|if_name| {
|
||||
let new_name = if_name.clone();
|
||||
Interface::<DevDisabled>::new(&if_name)
|
||||
.map(|interface| (if_name, interface))
|
||||
.ok()
|
||||
.or_else(|| {
|
||||
println!("{} failed to create device", new_name);
|
||||
None
|
||||
})
|
||||
})
|
||||
.collect::<HashMap<_, _>>()
|
||||
};
|
||||
|
||||
Ok(AggregateInterface { interfaces, crash })
|
||||
}
|
||||
|
||||
pub fn new_aggregate_interface(crash: bool) -> error::Result<AggregateInterface<DevDisabled>> {
|
||||
new_aggregate_interface_filtered(crash, |_| true)
|
||||
}
|
||||
|
||||
pub struct AggregateInterface<T: State> {
|
||||
interfaces: HashMap<String, Interface<T>>,
|
||||
crash: bool,
|
||||
}
|
||||
|
||||
impl<T: State> AggregateInterface<T> {
|
||||
pub fn set_non_blocking(&mut self, nonblocking: bool) -> error::Result<()> {
|
||||
for (n, i) in self.interfaces.iter_mut() {
|
||||
i.set_non_blocking(nonblocking)
|
||||
.map_err(|e| e.add_ifname(n))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn lookupnets(&self) -> error::Result<HashMap<&str, (u32, u32)>> {
|
||||
self.interfaces
|
||||
.iter()
|
||||
.map(|(name, interface)| {
|
||||
interface
|
||||
.lookupnet()
|
||||
.map(|net| (&**name, net))
|
||||
.map_err(|e| e.add_ifname(&name))
|
||||
})
|
||||
.collect::<error::Result<_>>()
|
||||
}
|
||||
|
||||
pub fn get_ifnames(&self) -> Vec<&str> {
|
||||
self.interfaces.keys().map(|n| &**n).collect::<_>()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Disabled> AggregateInterface<T> {
|
||||
pub fn set_promisc(&mut self, promisc: bool) -> error::Result<()> {
|
||||
for (n, i) in self.interfaces.iter_mut() {
|
||||
i.set_promisc(promisc).map_err(|e| e.add_ifname(n))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_buffer_size(&mut self, bufsize: i32) -> error::Result<()> {
|
||||
for (n, i) in self.interfaces.iter_mut() {
|
||||
i.set_buffer_size(bufsize).map_err(|e| e.add_ifname(n))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_timeout(&mut self, timeout: i32) -> error::Result<()> {
|
||||
for (n, i) in self.interfaces.iter_mut() {
|
||||
i.set_timeout(timeout).map_err(|e| e.add_ifname(n))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn activate(self) -> error::Result<AggregateInterface<DevActivated>> {
|
||||
Ok(AggregateInterface {
|
||||
interfaces: if self.crash {
|
||||
self.interfaces
|
||||
.into_iter()
|
||||
.map(|(name, interface)| {
|
||||
let new_name = name.clone();
|
||||
interface
|
||||
.activate()
|
||||
.map(|interface| (name, interface))
|
||||
.map_err(|e| e.add_ifname(&new_name))
|
||||
})
|
||||
.collect::<error::Result<_>>()?
|
||||
} else {
|
||||
self.interfaces
|
||||
.into_iter()
|
||||
.filter_map(|(name, interface)| {
|
||||
let name_clone = name.clone();
|
||||
interface
|
||||
.activate()
|
||||
.map(|interface| (name, interface))
|
||||
.ok()
|
||||
.or_else(|| {
|
||||
println!("{} failed to activate", name_clone);
|
||||
None
|
||||
})
|
||||
})
|
||||
.collect::<_>()
|
||||
},
|
||||
crash: self.crash,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Activated> AggregateInterface<T> {
|
||||
pub fn datalinks(&self) -> HashMap<&str, i32> {
|
||||
self.interfaces
|
||||
.iter()
|
||||
.map(|(name, interface)| (&**name, interface.datalink()))
|
||||
.collect::<_>()
|
||||
}
|
||||
|
||||
pub fn prune<F>(&mut self, mut f: F)
|
||||
where
|
||||
F: FnMut(&str, &mut Interface<T>) -> bool,
|
||||
{
|
||||
let to_prune = self
|
||||
.interfaces
|
||||
.iter_mut()
|
||||
.filter_map(|(k, v)| if (f)(k, v) { Some(k.clone()) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for name in to_prune {
|
||||
self.interfaces.remove(&name);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_filter(
|
||||
&mut self,
|
||||
filter: &str,
|
||||
optimize: bool,
|
||||
mask: Option<u32>,
|
||||
) -> error::Result<HashMap<&str, Box<ffi::BpfProgram>>> {
|
||||
if self.crash {
|
||||
self.interfaces
|
||||
.iter_mut()
|
||||
.map(|(name, interface)| {
|
||||
interface
|
||||
.set_filter(filter, optimize, mask)
|
||||
.map(|bpf| (&**name, bpf))
|
||||
.map_err(|e| e.add_ifname(&name))
|
||||
})
|
||||
.collect::<error::Result<_>>()
|
||||
} else {
|
||||
Ok(self
|
||||
.interfaces
|
||||
.iter_mut()
|
||||
.filter_map(|(name, interface)| {
|
||||
let name_clone = name.clone();
|
||||
interface
|
||||
.set_filter(filter, optimize, mask)
|
||||
.map(|bpf| (&**name, bpf))
|
||||
.ok()
|
||||
.or_else(|| {
|
||||
println!("{} failed to set filter", name_clone);
|
||||
None
|
||||
})
|
||||
})
|
||||
.collect::<_>())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sendpacket(&self, ifname: &str, packet: packets::EthernetPkt) -> error::Result<()> {
|
||||
if let Some(interface) = self.interfaces.get(ifname) {
|
||||
interface
|
||||
.sendpacket(packet)
|
||||
.map_err(|e| e.add_ifname(ifname))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NotListening> AggregateInterface<T> {
|
||||
pub fn stream(self) -> error::Result<AggregateInterfaceStream<DevActivated>> {
|
||||
Ok(AggregateInterfaceStream {
|
||||
streams: self
|
||||
.interfaces
|
||||
.into_iter()
|
||||
.map(|(ifname, interface)| {
|
||||
let new_name = ifname.clone();
|
||||
interface
|
||||
.stream()
|
||||
.map(|stream| (ifname, stream))
|
||||
.map_err(|e| e.add_ifname(&new_name))
|
||||
})
|
||||
.collect::<error::Result<_>>()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AggregateInterfaceStream<T: Activated> {
|
||||
streams: StreamMap<String, InterfaceStream<T>>,
|
||||
}
|
||||
|
||||
impl<T: Activated> AggregateInterfaceStream<T> {
|
||||
pub fn get_ifnames(&self) -> Vec<&str> {
|
||||
self.streams.keys().map(|n| &**n).collect::<_>()
|
||||
}
|
||||
|
||||
pub fn sendpacket(&mut self, ifname: &str, packet: packets::EthernetPkt) -> error::Result<()> {
|
||||
if let Some(interface) = self.streams.values_mut().find(|interface| {
|
||||
interface.inner.get_ref().interface.dev_name.as_bytes() == ifname.as_bytes()
|
||||
}) {
|
||||
interface.sendpacket(packet)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Activated> futures::Stream for AggregateInterfaceStream<T> {
|
||||
type Item = (String, error::Result<packets::EthernetPacket>);
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
self.streams.poll_next_unpin(cx)
|
||||
}
|
||||
}
|
||||
@ -5,4 +5,21 @@ edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
pcap-sys = { path = "../pcap-sys" }
|
||||
hyper = { version = "1.6.0", features = ["client", "http1", "http2"] }
|
||||
smoltcp = { version = "0.12.0", default-features = false, features = ["async", "log", "medium-ethernet", "proto-ipv4", "proto-ipv4-fragmentation", "socket-raw", "socket-tcp", "std"] }
|
||||
thiserror = "2.0.11"
|
||||
tokio = { version = "1.43.0", features = ["fs", "io-std", "io-util", "net", "process", "rt", "sync", "tokio-macros"] }
|
||||
async-trait = "0.1.86"
|
||||
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"] }
|
||||
tower-service = "0.3.3"
|
||||
futures = "0.3.31"
|
||||
simple_logger = "5.0.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" }
|
||||
|
||||
29
sparse-beacon/src/adapter.rs
Normal file
29
sparse-beacon/src/adapter.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use crate::error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BeaconRoute {
|
||||
pub network: (Ipv4Addr, u8),
|
||||
pub gateway: (Ipv4Addr, u8),
|
||||
pub interface_index: usize,
|
||||
}
|
||||
|
||||
pub struct BeaconNetworkingInfo {
|
||||
pub routes: Vec<BeaconRoute>,
|
||||
pub interfaces: Vec<BeaconInterface>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BeaconInterface {
|
||||
pub name: Vec<u8>,
|
||||
pub mtu: u16,
|
||||
pub mac_addr: [u8; 6],
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait BeaconAdapter {
|
||||
fn interface_name_from_interface(interface: &BeaconInterface) -> Vec<u8>;
|
||||
|
||||
fn networking_info(&self) -> Result<BeaconNetworkingInfo, error::BeaconError>;
|
||||
}
|
||||
337
sparse-beacon/src/callback.rs
Normal file
337
sparse-beacon/src/callback.rs
Normal file
@ -0,0 +1,337 @@
|
||||
use std::{
|
||||
future::Future,
|
||||
net::Ipv4Addr,
|
||||
pin::Pin,
|
||||
sync::{Arc, Mutex},
|
||||
task::{Context, 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 sparse_actions::payload_types::Parameters;
|
||||
|
||||
use crate::{adapter, error};
|
||||
|
||||
pub struct NetInterfaceHandle {
|
||||
net: Arc<Mutex<(SocketSet<'static>, 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<Result<(), std::io::Error>> {
|
||||
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::<Socket>(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<std::io::Result<usize>> {
|
||||
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::<Socket>(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<std::io::Result<()>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
|
||||
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::<Socket>(this.tcp_handle);
|
||||
socket.close();
|
||||
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn setup_network<T>(
|
||||
adapter: &T,
|
||||
parameters: &Parameters,
|
||||
) -> Result<NetInterfaceHandle, error::BeaconError>
|
||||
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::<u16>() % 16384;
|
||||
let mut device = crate::socket::RawSocket::new::<T>(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::<Socket>(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({
|
||||
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));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(NetInterfaceHandle {
|
||||
net,
|
||||
tcp_handle,
|
||||
|
||||
background_process,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn perform_callback<T>(
|
||||
adapter: &T,
|
||||
parameters: &Parameters,
|
||||
) -> Result<(), error::BeaconError>
|
||||
where
|
||||
T: adapter::BeaconAdapter + Clone + Send + 'static,
|
||||
{
|
||||
println!("Attempting net connection...");
|
||||
let mut net_handle = setup_network(adapter, parameters).await?;
|
||||
println!("Got connection!");
|
||||
|
||||
let mut buffer = vec![0u8; 4096];
|
||||
|
||||
net_handle.write(&*b"Hello there\n").await?;
|
||||
|
||||
while let Ok(v) = net_handle.read(&mut buffer).await {
|
||||
println!("Received {v} bytes: {:?}", &buffer[..v]);
|
||||
|
||||
net_handle.write(&buffer[..v]).await?;
|
||||
}
|
||||
|
||||
println!("Finishing connection");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
19
sparse-beacon/src/error.rs
Normal file
19
sparse-beacon/src/error.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum BeaconError {
|
||||
#[error("io error")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("pcap error")]
|
||||
Pcap(#[from] pcap_sys::error::Error),
|
||||
#[error("utf8 decoding error")]
|
||||
Utf8(#[from] std::str::Utf8Error),
|
||||
#[error("task join error")]
|
||||
Join(#[from] tokio::task::JoinError),
|
||||
#[error("could not find default route")]
|
||||
NoDefaultRoute,
|
||||
#[error("connection error")]
|
||||
Connect(#[from] smoltcp::socket::tcp::ConnectError),
|
||||
#[error("netlink error")]
|
||||
Nl(#[from] nl_sys::error::Error),
|
||||
}
|
||||
@ -1 +1,16 @@
|
||||
pub fn run_beacon_step() {}
|
||||
use sparse_actions::payload_types::Parameters;
|
||||
|
||||
pub mod adapter;
|
||||
mod callback;
|
||||
pub mod error;
|
||||
mod socket;
|
||||
pub use error::BeaconError;
|
||||
|
||||
pub async fn run_beacon_step<A>(host_adapter: A, params: Parameters) -> Result<(), BeaconError>
|
||||
where
|
||||
A: adapter::BeaconAdapter + Clone + Send + 'static,
|
||||
{
|
||||
callback::perform_callback(&host_adapter, ¶ms).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
113
sparse-beacon/src/main.rs
Normal file
113
sparse-beacon/src/main.rs
Normal file
@ -0,0 +1,113 @@
|
||||
use std::{io::SeekFrom, net::Ipv4Addr};
|
||||
|
||||
use tokio::io::{AsyncReadExt, AsyncSeekExt};
|
||||
|
||||
use nl_sys::netlink;
|
||||
|
||||
use sparse_actions::payload_types::{Parameters, XOR_KEY};
|
||||
use sparse_beacon::{
|
||||
adapter::{BeaconAdapter, BeaconInterface, BeaconNetworkingInfo, BeaconRoute},
|
||||
error,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct LinuxAdapter;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl BeaconAdapter for LinuxAdapter {
|
||||
fn interface_name_from_interface(interface: &BeaconInterface) -> Vec<u8> {
|
||||
interface.name.clone()
|
||||
}
|
||||
|
||||
fn networking_info(&self) -> Result<BeaconNetworkingInfo, error::BeaconError> {
|
||||
let nlsock = netlink::Socket::new()?;
|
||||
|
||||
let routes = nlsock.get_routes()?;
|
||||
let links = nlsock.get_links()?;
|
||||
let links_vec = links.iter().collect::<Vec<_>>();
|
||||
|
||||
Ok(BeaconNetworkingInfo {
|
||||
routes: routes
|
||||
.iter()
|
||||
.filter_map(|r| {
|
||||
let dst = r.dst()?;
|
||||
let dst4: Ipv4Addr = (&dst).try_into().ok()?;
|
||||
|
||||
let next_hop = r.nexthop(0)?;
|
||||
let gateway = next_hop.gateway()?;
|
||||
let gateway4: Ipv4Addr = (&gateway).try_into().ok()?;
|
||||
let gateway_int = u32::from(gateway4);
|
||||
|
||||
let src_cidr = routes.iter().find_map(|r| {
|
||||
let dst = r.dst()?;
|
||||
let dst4: Ipv4Addr = (&dst).try_into().ok()?;
|
||||
|
||||
if dst.cidrlen() == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mask = (0xFFFFFFFFu32.overflowing_shr(32 - dst.cidrlen()))
|
||||
.0
|
||||
.overflowing_shl(32 - dst.cidrlen())
|
||||
.0;
|
||||
|
||||
if (mask & u32::from(dst4)) == (mask & gateway_int) {
|
||||
Some(dst.cidrlen())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})?;
|
||||
|
||||
Some(BeaconRoute {
|
||||
network: (dst4, dst.cidrlen() as u8),
|
||||
gateway: (gateway4, src_cidr as u8),
|
||||
interface_index: links_vec
|
||||
.iter()
|
||||
.position(|l| l.ifindex() == next_hop.ifindex())?,
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
interfaces: links
|
||||
.iter()
|
||||
.filter_map(|l| {
|
||||
let mac_addr = l.addr().hw_address();
|
||||
|
||||
Some(BeaconInterface {
|
||||
name: l.name().as_bytes().to_owned(),
|
||||
mtu: (l.mtu() & 0xFFFF) as u16,
|
||||
mac_addr: mac_addr.try_into().ok()?,
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), sparse_beacon::BeaconError> {
|
||||
let installer = std::env::args()
|
||||
.skip(1)
|
||||
.next()
|
||||
.expect("Could not get a reference to a sparse installer");
|
||||
let mut installer_file = tokio::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.open(installer)
|
||||
.await?;
|
||||
|
||||
let parameters_size = std::mem::size_of::<Parameters>() as i64;
|
||||
|
||||
installer_file.seek(SeekFrom::End(-parameters_size)).await?;
|
||||
let mut parameters_buffer = Vec::with_capacity(parameters_size as usize);
|
||||
installer_file.read_to_end(&mut parameters_buffer).await?;
|
||||
|
||||
for b in parameters_buffer.iter_mut() {
|
||||
*b = *b ^ (XOR_KEY as u8);
|
||||
}
|
||||
|
||||
let parameters: Parameters =
|
||||
unsafe { std::mem::transmute(*(parameters_buffer.as_ptr() as *const Parameters)) };
|
||||
|
||||
sparse_beacon::run_beacon_step(LinuxAdapter, parameters).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
132
sparse-beacon/src/socket.rs
Normal file
132
sparse-beacon/src/socket.rs
Normal file
@ -0,0 +1,132 @@
|
||||
use smoltcp::phy::{self, Device, DeviceCapabilities, Medium};
|
||||
|
||||
use pcap_sys::Interface;
|
||||
|
||||
use crate::{adapter, error};
|
||||
|
||||
struct SocketInner {
|
||||
lower: Interface,
|
||||
}
|
||||
|
||||
pub struct RawSocket {
|
||||
inner: SocketInner,
|
||||
mtu: usize,
|
||||
}
|
||||
|
||||
impl RawSocket {
|
||||
pub fn new<T: adapter::BeaconAdapter>(
|
||||
a_interface: &adapter::BeaconInterface,
|
||||
promisc: bool,
|
||||
port: u16,
|
||||
) -> Result<Self, error::BeaconError> {
|
||||
let name_raw = T::interface_name_from_interface(&a_interface);
|
||||
let name = std::str::from_utf8(&name_raw)?;
|
||||
let mut lower = Interface::new(name)?;
|
||||
|
||||
let mtu = a_interface.mtu as usize + if cfg!(unix) { 14 } else { 0 };
|
||||
|
||||
dbg!(promisc);
|
||||
|
||||
lower.set_promisc(promisc)?;
|
||||
lower.set_buffer_size(mtu as i32)?;
|
||||
lower.set_non_blocking(true)?;
|
||||
lower.set_buffer_size(8192)?;
|
||||
lower.set_timeout(10)?;
|
||||
|
||||
lower.activate()?;
|
||||
|
||||
lower.set_filter(&format!("arp or (inbound and tcp port {port})"), true, None)?;
|
||||
|
||||
Ok(Self {
|
||||
inner: SocketInner { lower },
|
||||
mtu,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_ready_wait_callback(&self) -> pcap_sys::WaitHandle {
|
||||
self.inner.lower.get_wait_ready_callback()
|
||||
}
|
||||
}
|
||||
|
||||
impl Device for RawSocket {
|
||||
type RxToken<'a>
|
||||
= RxToken
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
type TxToken<'a>
|
||||
= TxToken<'a>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
fn capabilities(&self) -> DeviceCapabilities {
|
||||
let mut caps = DeviceCapabilities::default();
|
||||
|
||||
caps.max_transmission_unit = self.mtu;
|
||||
caps.medium = Medium::Ethernet;
|
||||
|
||||
caps
|
||||
}
|
||||
|
||||
fn receive(
|
||||
&mut self,
|
||||
_timestamp: smoltcp::time::Instant,
|
||||
) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
|
||||
match self.inner.lower.next_packet() {
|
||||
Ok(p) => {
|
||||
let rx = RxToken {
|
||||
buffer: p.pkt().raw().to_vec(),
|
||||
};
|
||||
let tx = TxToken { inner: &self.inner };
|
||||
Some((rx, tx))
|
||||
}
|
||||
Err(pcap_sys::error::Error::Io(e)) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
||||
None
|
||||
}
|
||||
Err(e) => {
|
||||
panic!("{}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn transmit(&mut self, _timestamp: smoltcp::time::Instant) -> Option<Self::TxToken<'_>> {
|
||||
Some(TxToken { inner: &self.inner })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TxToken<'a> {
|
||||
inner: &'a SocketInner,
|
||||
}
|
||||
|
||||
impl phy::TxToken for TxToken<'_> {
|
||||
fn consume<R, F>(self, len: usize, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> R,
|
||||
{
|
||||
let mut buffer = vec![0; len];
|
||||
let result = f(&mut buffer);
|
||||
let packet = packets::EthernetPacket::from_raw(buffer);
|
||||
match self.inner.lower.sendpacket(packet.pkt()) {
|
||||
Ok(_) => {}
|
||||
Err(pcap_sys::error::Error::Io(e)) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
||||
println!("Failed to send due to non blocking mode");
|
||||
}
|
||||
Err(err) => panic!("{}", err),
|
||||
}
|
||||
drop(packet);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RxToken {
|
||||
buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl phy::RxToken for RxToken {
|
||||
fn consume<R, F>(self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&[u8]) -> R,
|
||||
{
|
||||
f(&self.buffer[..])
|
||||
}
|
||||
}
|
||||
@ -3,14 +3,14 @@ use std::{
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use axum::routing::{get, post, Router};
|
||||
use axum::routing::{Router, get, post};
|
||||
use sqlx::SqlitePool;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
pub mod error;
|
||||
|
||||
pub struct BeaconListenerHandle {
|
||||
join_handle: JoinHandle<()>
|
||||
join_handle: JoinHandle<()>,
|
||||
}
|
||||
|
||||
impl BeaconListenerHandle {
|
||||
@ -34,7 +34,10 @@ impl std::ops::Deref for BeaconListenerMap {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start_all_listeners(beacon_listener_map: BeaconListenerMap, db: SqlitePool) -> Result<(), crate::error::Error> {
|
||||
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?;
|
||||
@ -42,7 +45,12 @@ pub async fn start_all_listeners(beacon_listener_map: BeaconListenerMap, db: Sql
|
||||
tracing::info!("Starting {} listener(s)...", listener_ids.len());
|
||||
|
||||
for listener in listener_ids {
|
||||
start_listener(beacon_listener_map.clone(), listener.listener_id, db.clone()).await?;
|
||||
start_listener(
|
||||
beacon_listener_map.clone(),
|
||||
listener.listener_id,
|
||||
db.clone(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -50,7 +58,7 @@ pub async fn start_all_listeners(beacon_listener_map: BeaconListenerMap, db: Sql
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ListenerState {
|
||||
db: SqlitePool
|
||||
db: SqlitePool,
|
||||
}
|
||||
|
||||
struct Listener {
|
||||
@ -59,41 +67,59 @@ struct Listener {
|
||||
public_ip: String,
|
||||
domain_name: String,
|
||||
certificate: Vec<u8>,
|
||||
privkey: Vec<u8>
|
||||
privkey: Vec<u8>,
|
||||
}
|
||||
|
||||
pub async fn start_listener(beacon_listener_map: BeaconListenerMap, listener_id: i64, db: SqlitePool) -> Result<(), crate::error::Error> {
|
||||
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()));
|
||||
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()));
|
||||
return Err(crate::error::Error::Generic(
|
||||
"Beacon listener already started".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
let listener = sqlx::query_as!(Listener, "SELECT * FROM beacon_listener WHERE listener_id = ?", listener_id)
|
||||
let listener = sqlx::query_as!(
|
||||
Listener,
|
||||
"SELECT * FROM beacon_listener WHERE listener_id = ?",
|
||||
listener_id
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
|
||||
let app: Router<()> = Router::new()
|
||||
.route("/register_beacon", post(|| async {
|
||||
.route(
|
||||
"/register_beacon",
|
||||
post(|| async {
|
||||
tracing::info!("Beacon attempting to register");
|
||||
}))
|
||||
.route("/test", get(|| async {
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/test",
|
||||
get(|| async {
|
||||
tracing::info!("Hello");
|
||||
"hi there"
|
||||
}))
|
||||
.with_state(ListenerState {
|
||||
db
|
||||
});
|
||||
}),
|
||||
)
|
||||
.with_state(ListenerState { db });
|
||||
|
||||
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) => {
|
||||
return Err(crate::error::Error::Generic(format!("Could not parse private key: {e}")));
|
||||
return Err(crate::error::Error::Generic(format!(
|
||||
"Could not parse private key: {e}"
|
||||
)));
|
||||
}
|
||||
};
|
||||
let cert = rustls::pki_types::CertificateDer::from(listener.certificate.clone());
|
||||
@ -105,14 +131,17 @@ pub async fn start_listener(beacon_listener_map: BeaconListenerMap, listener_id:
|
||||
|
||||
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);
|
||||
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,
|
||||
axum_server::tls_rustls::RustlsConfig::from_config(
|
||||
Arc::new(tls_config)
|
||||
)
|
||||
axum_server::tls_rustls::RustlsConfig::from_config(Arc::new(tls_config)),
|
||||
)
|
||||
.serve(hidden_app.into_make_service())
|
||||
.await;
|
||||
@ -123,12 +152,12 @@ pub async fn start_listener(beacon_listener_map: BeaconListenerMap, listener_id:
|
||||
});
|
||||
|
||||
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()));
|
||||
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
|
||||
});
|
||||
blm_handle.insert(listener_id, BeaconListenerHandle { join_handle });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
2
sparse-server/migrations/20250211235645_interface.sql
Normal file
2
sparse-server/migrations/20250211235645_interface.sql
Normal file
@ -0,0 +1,2 @@
|
||||
-- Add migration script here
|
||||
ALTER TABLE beacon_template ADD COLUMN source_interface blob DEFAULT '';
|
||||
@ -1,9 +1,9 @@
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use leptos_meta::{provide_meta_context, MetaTags, Stylesheet, Title};
|
||||
use leptos_router::{
|
||||
components::{A, ParentRoute, Route, Router, Routes},
|
||||
components::{ParentRoute, Route, Router, Routes, A},
|
||||
hooks::use_query_map,
|
||||
path
|
||||
path,
|
||||
};
|
||||
|
||||
use crate::users::User;
|
||||
@ -29,7 +29,7 @@ pub async fn me() -> Result<Option<User>, ServerFnError> {
|
||||
|
||||
Ok(user.map(|user| User {
|
||||
user_id: user.user_id,
|
||||
user_name: user.user_name
|
||||
user_name: user.user_name,
|
||||
}))
|
||||
}
|
||||
|
||||
@ -73,10 +73,7 @@ pub fn App() -> impl IntoView {
|
||||
|
||||
let (user_res, set_user_res) = signal(None::<User>);
|
||||
|
||||
let user = Resource::new(
|
||||
move || login.version().get(),
|
||||
|_| async { me().await }
|
||||
);
|
||||
let user = Resource::new(move || login.version().get(), |_| async { me().await });
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
Effect::new(move || {
|
||||
@ -143,7 +140,12 @@ pub fn App() -> impl IntoView {
|
||||
|
||||
#[component]
|
||||
fn LoginPage(login: ServerAction<Login>) -> impl IntoView {
|
||||
let next = move || use_query_map().read().get("next").unwrap_or("/".to_string());
|
||||
let next = move || {
|
||||
use_query_map()
|
||||
.read()
|
||||
.get("next")
|
||||
.unwrap_or("/".to_string())
|
||||
};
|
||||
|
||||
view! {
|
||||
<main class="login">
|
||||
|
||||
@ -36,7 +36,7 @@ pub struct BeaconResources {
|
||||
listeners: Resource<Result<Vec<listeners::PubListener>, ServerFnError>>,
|
||||
categories: Resource<Result<Vec<categories::Category>, ServerFnError>>,
|
||||
configs: Resource<Result<Vec<configs::BeaconConfig>, ServerFnError>>,
|
||||
templates: Resource<Result<Vec<templates::BeaconTemplate>, ServerFnError>>
|
||||
templates: Resource<Result<Vec<templates::BeaconTemplate>, ServerFnError>>,
|
||||
}
|
||||
|
||||
pub fn provide_beacon_resources() {
|
||||
@ -56,40 +56,48 @@ pub fn provide_beacon_resources() {
|
||||
let remove_template = ServerAction::<templates::RemoveTemplate>::new();
|
||||
|
||||
let listeners = Resource::new(
|
||||
move || (
|
||||
move || {
|
||||
(
|
||||
user.get(),
|
||||
add_listener.version().get(),
|
||||
remove_listener.version().get(),
|
||||
),
|
||||
|_| async { listeners::get_listeners().await }
|
||||
)
|
||||
},
|
||||
|_| async { listeners::get_listeners().await },
|
||||
);
|
||||
|
||||
let categories = Resource::new(
|
||||
move || (
|
||||
move || {
|
||||
(
|
||||
user.get(),
|
||||
add_category.version().get(),
|
||||
remove_category.version().get(),
|
||||
rename_category.version().get(),
|
||||
),
|
||||
|_| async { categories::get_categories().await }
|
||||
)
|
||||
},
|
||||
|_| async { categories::get_categories().await },
|
||||
);
|
||||
|
||||
let configs = Resource::new(
|
||||
move || (
|
||||
move || {
|
||||
(
|
||||
user.get(),
|
||||
add_beacon_config.version().get(),
|
||||
remove_beacon_config.version().get(),
|
||||
),
|
||||
|_| async { configs::get_beacon_configs().await }
|
||||
)
|
||||
},
|
||||
|_| async { configs::get_beacon_configs().await },
|
||||
);
|
||||
|
||||
let templates = Resource::new(
|
||||
move || (
|
||||
move || {
|
||||
(
|
||||
user.get(),
|
||||
add_template.version().get(),
|
||||
remove_template.version().get()
|
||||
),
|
||||
|_| async { templates::get_templates().await }
|
||||
remove_template.version().get(),
|
||||
)
|
||||
},
|
||||
|_| async { templates::get_templates().await },
|
||||
);
|
||||
|
||||
provide_context(BeaconResources {
|
||||
@ -106,7 +114,7 @@ pub fn provide_beacon_resources() {
|
||||
listeners,
|
||||
categories,
|
||||
configs,
|
||||
templates
|
||||
templates,
|
||||
});
|
||||
}
|
||||
|
||||
@ -142,7 +150,7 @@ enum SortMethod {
|
||||
Listener,
|
||||
Config,
|
||||
Category,
|
||||
Template
|
||||
Template,
|
||||
}
|
||||
|
||||
impl std::str::FromStr for SortMethod {
|
||||
@ -154,7 +162,7 @@ impl std::str::FromStr for SortMethod {
|
||||
"Config" => Ok(Self::Config),
|
||||
"Category" => Ok(Self::Category),
|
||||
"Template" => Ok(Self::Template),
|
||||
&_ => Err(())
|
||||
&_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -167,7 +175,8 @@ impl std::string::ToString for SortMethod {
|
||||
SM::Config => "Config",
|
||||
SM::Category => "Category",
|
||||
SM::Template => "Template",
|
||||
}.to_string()
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,18 +1,14 @@
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "ssr")]
|
||||
use {
|
||||
sqlx::SqlitePool,
|
||||
leptos::server_fn::error::NoCustomError,
|
||||
crate::db::user,
|
||||
};
|
||||
use {crate::db::user, leptos::server_fn::error::NoCustomError, sqlx::SqlitePool};
|
||||
|
||||
use super::BeaconResources;
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Category {
|
||||
pub category_id: i64,
|
||||
pub category_name: String
|
||||
pub category_name: String,
|
||||
}
|
||||
|
||||
#[server]
|
||||
@ -20,28 +16,26 @@ pub async fn get_categories() -> Result<Vec<Category>, ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
|
||||
Ok(
|
||||
sqlx::query_as!(
|
||||
Category,
|
||||
"SELECT * FROM beacon_category"
|
||||
)
|
||||
Ok(sqlx::query_as!(Category, "SELECT * FROM beacon_category")
|
||||
.fetch_all(&db)
|
||||
.await?
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
|
||||
#[server]
|
||||
pub async fn add_category(name: String) -> Result<(), ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
@ -61,15 +55,14 @@ pub async fn remove_category(id: i64) -> Result<(), ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM beacon_category WHERE category_id = ?",
|
||||
id
|
||||
)
|
||||
sqlx::query!("DELETE FROM beacon_category WHERE category_id = ?", id)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
@ -81,7 +74,9 @@ pub async fn rename_category(id: i64, name: String) -> Result<(), ServerFnError>
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
@ -99,7 +94,11 @@ pub async fn rename_category(id: i64, name: String) -> Result<(), ServerFnError>
|
||||
|
||||
#[component]
|
||||
pub fn CategoriesView() -> impl IntoView {
|
||||
let BeaconResources { add_category, categories, .. } = expect_context();
|
||||
let BeaconResources {
|
||||
add_category,
|
||||
categories,
|
||||
..
|
||||
} = expect_context();
|
||||
|
||||
view! {
|
||||
<div class="categories">
|
||||
@ -148,7 +147,11 @@ pub fn CategoriesView() -> impl IntoView {
|
||||
|
||||
#[component]
|
||||
fn DisplayCategories(categories: Vec<Category>) -> impl IntoView {
|
||||
let BeaconResources { remove_category, rename_category, .. } = expect_context();
|
||||
let BeaconResources {
|
||||
remove_category,
|
||||
rename_category,
|
||||
..
|
||||
} = expect_context();
|
||||
|
||||
let (target_rename_id, set_target_rename_id) = signal(0);
|
||||
let target_rename_name = RwSignal::new("".to_owned());
|
||||
@ -157,7 +160,8 @@ fn DisplayCategories(categories: Vec<Category>) -> impl IntoView {
|
||||
|
||||
let categories_view = categories
|
||||
.iter()
|
||||
.map(|category| view! {
|
||||
.map(|category| {
|
||||
view! {
|
||||
<li>
|
||||
{category.category_id}
|
||||
": "
|
||||
@ -191,21 +195,24 @@ fn DisplayCategories(categories: Vec<Category>) -> impl IntoView {
|
||||
"delete"
|
||||
</button>
|
||||
</li>
|
||||
}
|
||||
})
|
||||
.collect_view();
|
||||
|
||||
Effect::watch(
|
||||
move || (
|
||||
move || {
|
||||
(
|
||||
rename_category.version().get(),
|
||||
rename_category.value().get(),
|
||||
dialog_ref.get()
|
||||
),
|
||||
move |(_, res,dialog_ref),_,_| {
|
||||
dialog_ref.get(),
|
||||
)
|
||||
},
|
||||
move |(_, res, dialog_ref), _, _| {
|
||||
if let (Some(Ok(())), Some(dialog)) = (res, dialog_ref) {
|
||||
let _ = dialog.close();
|
||||
}
|
||||
},
|
||||
false
|
||||
false,
|
||||
);
|
||||
|
||||
view! {
|
||||
|
||||
@ -2,12 +2,10 @@ use leptos::{either::Either, prelude::*};
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "ssr")]
|
||||
use {
|
||||
std::str::FromStr,
|
||||
|
||||
sqlx::{sqlite::SqliteRow, FromRow, Row, SqlitePool},
|
||||
crate::db::user,
|
||||
leptos::server_fn::error::NoCustomError,
|
||||
|
||||
crate::db::user
|
||||
sqlx::{sqlite::SqliteRow, FromRow, Row, SqlitePool},
|
||||
std::str::FromStr,
|
||||
};
|
||||
|
||||
use super::BeaconResources;
|
||||
@ -32,7 +30,7 @@ impl FromRow<'_, SqliteRow> for BeaconConfigTypes {
|
||||
)),
|
||||
"cron" => Ok(Self::CronSchedule(
|
||||
row.try_get("cron_schedule")?,
|
||||
row.try_get("cron_mode")?
|
||||
row.try_get("cron_mode")?,
|
||||
)),
|
||||
type_name => Err(sqlx::Error::TypeNotFound {
|
||||
type_name: type_name.to_string(),
|
||||
@ -55,7 +53,9 @@ pub async fn get_beacon_configs() -> Result<Vec<BeaconConfig>, ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
@ -78,7 +78,9 @@ pub async fn add_beacon_config(
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
@ -93,10 +95,12 @@ pub async fn add_beacon_config(
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
"regular" => {
|
||||
if regular_interval < 1 {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("Invalid interval provided".to_owned()))
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Invalid interval provided".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
@ -108,10 +112,12 @@ pub async fn add_beacon_config(
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
"random" => {
|
||||
if random_min_time < 1 || random_max_time < random_min_time {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("Invalid random interval provided".to_owned()))
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Invalid random interval provided".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
@ -124,19 +130,21 @@ pub async fn add_beacon_config(
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
"cron" => {
|
||||
if let Err(e) = cron::Schedule::from_str(&cron_schedule) {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(format!(
|
||||
"Could not parse cron expression: {}",
|
||||
e
|
||||
)))
|
||||
)));
|
||||
}
|
||||
|
||||
match &*cron_mode {
|
||||
"local" | "utc" => {},
|
||||
"local" | "utc" => {}
|
||||
_ => {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("Unrecognized timezone specifier for cron".to_string()))
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Unrecognized timezone specifier for cron".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,10 +158,10 @@ pub async fn add_beacon_config(
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
_ => {
|
||||
Err(ServerFnError::<NoCustomError>::ServerError("Invalid mode supplied".to_owned()))
|
||||
}
|
||||
_ => Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Invalid mode supplied".to_owned(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,15 +170,14 @@ pub async fn remove_beacon_config(id: i64) -> Result<(), ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM beacon_config WHERE config_id = ?",
|
||||
id
|
||||
)
|
||||
sqlx::query!("DELETE FROM beacon_config WHERE config_id = ?", id)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
@ -179,7 +186,11 @@ pub async fn remove_beacon_config(id: i64) -> Result<(), ServerFnError> {
|
||||
|
||||
#[component]
|
||||
pub fn ConfigsView() -> impl IntoView {
|
||||
let BeaconResources { add_beacon_config, configs, .. } = expect_context();
|
||||
let BeaconResources {
|
||||
add_beacon_config,
|
||||
configs,
|
||||
..
|
||||
} = expect_context();
|
||||
|
||||
view! {
|
||||
<div class="config">
|
||||
@ -260,11 +271,15 @@ pub fn ConfigsView() -> impl IntoView {
|
||||
|
||||
#[component]
|
||||
fn DisplayConfigs(configs: Vec<BeaconConfig>) -> impl IntoView {
|
||||
let BeaconResources { remove_beacon_config, .. } = expect_context();
|
||||
let BeaconResources {
|
||||
remove_beacon_config,
|
||||
..
|
||||
} = expect_context();
|
||||
|
||||
let configs_view = configs
|
||||
.iter()
|
||||
.map(|config| view! {
|
||||
.map(|config| {
|
||||
view! {
|
||||
<li>
|
||||
{config.config_id}
|
||||
": "
|
||||
@ -296,6 +311,7 @@ fn DisplayConfigs(configs: Vec<BeaconConfig>) -> impl IntoView {
|
||||
"delete"
|
||||
</button>
|
||||
</li>
|
||||
}
|
||||
})
|
||||
.collect_view();
|
||||
|
||||
|
||||
@ -2,17 +2,14 @@
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "ssr")]
|
||||
use {
|
||||
sqlx::SqlitePool,
|
||||
|
||||
crate::db::user,
|
||||
leptos::server_fn::error::NoCustomError,
|
||||
rcgen::{generate_simple_self_signed, CertifiedKey},
|
||||
|
||||
sparse_handler::BeaconListenerMap,
|
||||
|
||||
crate::db::user,
|
||||
sqlx::SqlitePool,
|
||||
};
|
||||
|
||||
use super::BeaconResources;
|
||||
@ -22,7 +19,7 @@ struct DbListener {
|
||||
listener_id: i64,
|
||||
port: i64,
|
||||
public_ip: String,
|
||||
domain_name: String
|
||||
domain_name: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
@ -31,7 +28,7 @@ pub struct PubListener {
|
||||
pub port: i64,
|
||||
pub public_ip: String,
|
||||
pub domain_name: String,
|
||||
pub active: bool
|
||||
pub active: bool,
|
||||
}
|
||||
|
||||
#[server]
|
||||
@ -39,7 +36,9 @@ pub async fn get_listeners() -> Result<Vec<PubListener>, ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
@ -66,13 +65,16 @@ pub async fn get_listeners() -> Result<Vec<PubListener>, ServerFnError> {
|
||||
active: beacon_handles_handle
|
||||
.get(&b.listener_id)
|
||||
.map(|h| !h.is_finished())
|
||||
.unwrap_or(false)
|
||||
.unwrap_or(false),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
pub fn generate_cert_from_keypair(kp: &rcgen::KeyPair, names: Vec<String>) -> Result<rcgen::Certificate, rcgen::Error> {
|
||||
pub fn generate_cert_from_keypair(
|
||||
kp: &rcgen::KeyPair,
|
||||
names: Vec<String>,
|
||||
) -> Result<rcgen::Certificate, rcgen::Error> {
|
||||
use rcgen::CertificateParams;
|
||||
|
||||
let mut params = CertificateParams::new(names)?;
|
||||
@ -83,25 +85,33 @@ pub fn generate_cert_from_keypair(kp: &rcgen::KeyPair, names: Vec<String>) -> Re
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn add_listener(public_ip: String, port: i16, domain_name: String) -> Result<(), ServerFnError> {
|
||||
pub async fn add_listener(
|
||||
public_ip: String,
|
||||
port: i16,
|
||||
domain_name: String,
|
||||
) -> Result<(), ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
if public_ip.parse::<Ipv4Addr>().is_err() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("Unable to parse public IP address".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Unable to parse public IP address".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let subject_alt_names = vec![public_ip.to_string(), domain_name.clone()];
|
||||
|
||||
|
||||
let (key_pair, cert) = tokio::task::spawn_blocking(|| {
|
||||
rcgen::KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256)
|
||||
.and_then(|keypair|
|
||||
generate_cert_from_keypair(&keypair, subject_alt_names).map(|cert| (keypair, cert)))
|
||||
}).await??;
|
||||
rcgen::KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256).and_then(|keypair| {
|
||||
generate_cert_from_keypair(&keypair, subject_alt_names).map(|cert| (keypair, cert))
|
||||
})
|
||||
})
|
||||
.await??;
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
|
||||
@ -128,20 +138,26 @@ pub async fn remove_listener(listener_id: i64) -> Result<(), ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
{
|
||||
let blm = expect_context::<BeaconListenerMap>();
|
||||
|
||||
let Ok(mut blm_handle) = blm.write() else {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("Failed to get write handle for beacon listener map".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Failed to get write handle for beacon listener map".to_owned(),
|
||||
));
|
||||
};
|
||||
|
||||
if let Some(bl) = blm_handle.get_mut(&listener_id) {
|
||||
bl.abort();
|
||||
} else {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("Failed to get write handle for beacon listener map".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Failed to get write handle for beacon listener map".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
blm_handle.remove(&listener_id);
|
||||
@ -164,21 +180,23 @@ pub async fn start_listener(listener_id: i64) -> Result<(), ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
sparse_handler::start_listener(
|
||||
expect_context(),
|
||||
listener_id,
|
||||
expect_context()
|
||||
).await?;
|
||||
sparse_handler::start_listener(expect_context(), listener_id, expect_context()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn ListenersView() -> impl IntoView {
|
||||
let super::BeaconResources { add_listener, listeners, .. } = expect_context::<super::BeaconResources>();
|
||||
let super::BeaconResources {
|
||||
add_listener,
|
||||
listeners,
|
||||
..
|
||||
} = expect_context::<super::BeaconResources>();
|
||||
|
||||
view! {
|
||||
<div class="listeners">
|
||||
@ -230,7 +248,11 @@ pub fn ListenersView() -> impl IntoView {
|
||||
|
||||
#[component]
|
||||
fn DisplayListeners(listeners: Vec<PubListener>) -> impl IntoView {
|
||||
let BeaconResources { listeners: listener_resource, remove_listener, .. } = expect_context::<BeaconResources>();
|
||||
let BeaconResources {
|
||||
listeners: listener_resource,
|
||||
remove_listener,
|
||||
..
|
||||
} = expect_context::<BeaconResources>();
|
||||
|
||||
let (error_msg, set_error_msg) = signal(None);
|
||||
let start_listener_action = Action::new(move |&id: &i64| async move {
|
||||
@ -246,7 +268,8 @@ fn DisplayListeners(listeners: Vec<PubListener>) -> impl IntoView {
|
||||
|
||||
let listeners_view = listeners
|
||||
.iter()
|
||||
.map(|listener| view! {
|
||||
.map(|listener| {
|
||||
view! {
|
||||
<li>
|
||||
{listener.listener_id}
|
||||
": "
|
||||
@ -286,6 +309,7 @@ fn DisplayListeners(listeners: Vec<PubListener>) -> impl IntoView {
|
||||
"delete"
|
||||
</button>
|
||||
</li>
|
||||
}
|
||||
})
|
||||
.collect_view();
|
||||
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "ssr")]
|
||||
use {
|
||||
std::net::Ipv4Addr,
|
||||
|
||||
sqlx::{sqlite::SqliteRow, FromRow, Row, SqlitePool},
|
||||
crate::db::user,
|
||||
leptos::server_fn::error::NoCustomError,
|
||||
|
||||
crate::db::user
|
||||
sqlx::{sqlite::SqliteRow, FromRow, Row, SqlitePool},
|
||||
std::net::Ipv4Addr,
|
||||
};
|
||||
|
||||
use crate::beacons::BeaconResources;
|
||||
@ -15,7 +13,7 @@ use crate::beacons::BeaconResources;
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub enum BeaconSourceMode {
|
||||
Host,
|
||||
Custom(i64, String)
|
||||
Custom(i64, String),
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
@ -25,7 +23,7 @@ impl FromRow<'_, SqliteRow> for BeaconSourceMode {
|
||||
"host" => Ok(Self::Host),
|
||||
"custom" => Ok(Self::Custom(
|
||||
row.try_get("source_netmask")?,
|
||||
row.try_get("source_gateway")?
|
||||
row.try_get("source_gateway")?,
|
||||
)),
|
||||
type_name => Err(sqlx::Error::TypeNotFound {
|
||||
type_name: type_name.to_string(),
|
||||
@ -48,7 +46,7 @@ pub struct BeaconTemplate {
|
||||
|
||||
config_id: i64,
|
||||
listener_id: i64,
|
||||
default_category: Option<i64>
|
||||
default_category: Option<i64>,
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
@ -72,7 +70,8 @@ pub async fn add_template(
|
||||
source_mac: String,
|
||||
source_mode: String,
|
||||
source_netmask: i64,
|
||||
source_gateway: String
|
||||
source_gateway: String,
|
||||
source_interface: String,
|
||||
) -> Result<(), ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
@ -97,7 +96,11 @@ pub async fn add_template(
|
||||
}
|
||||
|
||||
let mac_parts = source_mac.split(":").collect::<Vec<_>>();
|
||||
if mac_parts.len() != 6 || mac_parts.iter().any(|p| p.len() != 2 || u8::from_str_radix(p, 16).is_err()) {
|
||||
if mac_parts.len() != 6
|
||||
|| mac_parts
|
||||
.iter()
|
||||
.any(|p| p.len() != 2 || u8::from_str_radix(p, 16).is_err())
|
||||
{
|
||||
srverr!("Source MAC address is formatted incorrectly");
|
||||
}
|
||||
|
||||
@ -119,7 +122,7 @@ pub async fn add_template(
|
||||
srverr!("Could not parse private key: {e}");
|
||||
}
|
||||
},
|
||||
&rcgen::PKCS_ECDSA_P256_SHA256
|
||||
&rcgen::PKCS_ECDSA_P256_SHA256,
|
||||
)?;
|
||||
let ca_params = CertificateParams::from_ca_cert_der(&(*listener.certificate).into())?;
|
||||
let ca_cert = ca_params.self_signed(&keypair)?;
|
||||
@ -131,6 +134,8 @@ pub async fn add_template(
|
||||
let client_key_der = client_key.serialize_der();
|
||||
let client_cert_der = client_cert.der().to_vec();
|
||||
|
||||
let interface = Some(source_interface).filter(|s| !s.is_empty());
|
||||
|
||||
match &*source_mode {
|
||||
"host" => {
|
||||
let source_mac = Some(source_mac).filter(|mac| mac != "00:00:00:00:00:00");
|
||||
@ -138,9 +143,11 @@ pub async fn add_template(
|
||||
|
||||
sqlx::query!(
|
||||
r"INSERT INTO beacon_template
|
||||
(template_name, operating_system, config_id, listener_id, source_ip, source_mac, source_mode, default_category, client_key, client_cert)
|
||||
(template_name, operating_system, config_id, listener_id, source_ip,
|
||||
source_mac, source_mode, default_category, client_key, client_cert,
|
||||
source_interface)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, ?, 'host', ?, ?, ?)",
|
||||
(?, ?, ?, ?, ?, ?, 'host', ?, ?, ?, ?)",
|
||||
template_name,
|
||||
operating_system,
|
||||
config_id,
|
||||
@ -149,22 +156,25 @@ pub async fn add_template(
|
||||
source_mac,
|
||||
default_category,
|
||||
client_key_der,
|
||||
client_cert_der
|
||||
client_cert_der,
|
||||
interface
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
"custom" => {
|
||||
let source_mac = Some(source_mac).filter(|mac| mac != "00:00:00:00:00:00");
|
||||
let default_category = Some(default_category).filter(|dc| *dc != 0);
|
||||
|
||||
sqlx::query!(
|
||||
r"INSERT INTO beacon_template
|
||||
(template_name, operating_system, config_id, listener_id, source_ip, source_mac, source_mode, source_netmask, source_gateway, default_category, client_key, client_cert)
|
||||
(template_name, operating_system, config_id, listener_id, source_ip,
|
||||
source_mac, source_mode, source_netmask, source_gateway, default_category,
|
||||
client_key, client_cert, source_interface)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, ?, 'host', ?, ?, ?, ?, ?)",
|
||||
(?, ?, ?, ?, ?, ?, 'host', ?, ?, ?, ?, ?, ?)",
|
||||
template_name,
|
||||
operating_system,
|
||||
config_id,
|
||||
@ -175,13 +185,14 @@ pub async fn add_template(
|
||||
source_gateway,
|
||||
default_category,
|
||||
client_key_der,
|
||||
client_cert_der
|
||||
client_cert_der,
|
||||
interface
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
_other => {
|
||||
srverr!("Invalid type of source mode provided");
|
||||
}
|
||||
@ -198,7 +209,10 @@ pub async fn remove_template(template_id: i64) -> Result<(), ServerFnError> {
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
|
||||
sqlx::query!("DELETE FROM beacon_template WHERE template_id = ?", template_id)
|
||||
sqlx::query!(
|
||||
"DELETE FROM beacon_template WHERE template_id = ?",
|
||||
template_id
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
@ -210,7 +224,9 @@ pub async fn get_templates() -> Result<Vec<BeaconTemplate>, ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
@ -222,7 +238,13 @@ pub async fn get_templates() -> Result<Vec<BeaconTemplate>, ServerFnError> {
|
||||
|
||||
#[component]
|
||||
pub fn TemplatesView() -> impl IntoView {
|
||||
let BeaconResources { configs, listeners, categories, templates, .. } = expect_context();
|
||||
let BeaconResources {
|
||||
configs,
|
||||
listeners,
|
||||
categories,
|
||||
templates,
|
||||
..
|
||||
} = expect_context();
|
||||
|
||||
view! {
|
||||
<div class="templates">
|
||||
@ -379,6 +401,8 @@ pub fn AddTemplateForm(
|
||||
<input class="mode-custom" type="number" name="source_netmask" value="24"/>
|
||||
<label class="mode-custom">"Network gateway"</label>
|
||||
<input class="mode-custom" name="source_gateway"/>
|
||||
<label class="mode-custom">"Network interface name"</label>
|
||||
<input class="mode-custom" name="source_interface"/>
|
||||
<div></div>
|
||||
<input type="submit" value="Submit" />
|
||||
</fieldset>
|
||||
@ -391,9 +415,11 @@ pub fn DisplayTemplates(
|
||||
configs: Vec<super::configs::BeaconConfig>,
|
||||
listeners: Vec<super::listeners::PubListener>,
|
||||
categories: Vec<super::categories::Category>,
|
||||
templates: Vec<BeaconTemplate>
|
||||
templates: Vec<BeaconTemplate>,
|
||||
) -> impl IntoView {
|
||||
let BeaconResources { remove_template, .. } = expect_context();
|
||||
let BeaconResources {
|
||||
remove_template, ..
|
||||
} = expect_context();
|
||||
|
||||
let templates_view = templates
|
||||
.iter()
|
||||
|
||||
@ -9,7 +9,7 @@ pub async fn handle_user_command(user_command: UC, db: SqlitePool) -> anyhow::Re
|
||||
match user_command {
|
||||
UC::List {} => list_users(db).await,
|
||||
UC::Create { user_name } => create_user(db, user_name).await,
|
||||
UC::ResetPassword { user_id } => reset_password(&db, user_id).await
|
||||
UC::ResetPassword { user_id } => reset_password(&db, user_id).await,
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ async fn create_user(db: SqlitePool, name: String) -> anyhow::Result<ExitCode> {
|
||||
|
||||
async fn reset_password<'a, E>(db: E, id: i16) -> anyhow::Result<ExitCode>
|
||||
where
|
||||
E: sqlx::SqliteExecutor<'a>
|
||||
E: sqlx::SqliteExecutor<'a>,
|
||||
{
|
||||
let password = get_password()?;
|
||||
|
||||
|
||||
@ -1,7 +1,13 @@
|
||||
use leptos::prelude::ServerFnError;
|
||||
use leptos::{prelude::expect_context, server_fn::error::NoCustomError};
|
||||
use leptos_axum::{extract, ResponseOptions};
|
||||
use leptos::prelude::ServerFnError;
|
||||
use pbkdf2::{Pbkdf2, password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, rand_core::{OsRng, RngCore}, SaltString}};
|
||||
use pbkdf2::{
|
||||
password_hash::{
|
||||
rand_core::{OsRng, RngCore},
|
||||
PasswordHash, PasswordHasher, PasswordVerifier, SaltString,
|
||||
},
|
||||
Pbkdf2,
|
||||
};
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
use crate::error::Error;
|
||||
@ -11,7 +17,7 @@ pub struct User {
|
||||
pub user_id: i64,
|
||||
pub user_name: String,
|
||||
password_hash: String,
|
||||
pub last_active: Option<i64>
|
||||
pub last_active: Option<i64>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for User {
|
||||
@ -29,12 +35,13 @@ async fn hash_password(pass: &[u8]) -> Result<String, Error> {
|
||||
let pass = pass.to_owned();
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
|
||||
move ||
|
||||
Pbkdf2.hash_password(
|
||||
&*pass,
|
||||
&salt,
|
||||
).map(|hash| hash.serialize().as_str().to_string())
|
||||
}).await??)
|
||||
move || {
|
||||
Pbkdf2
|
||||
.hash_password(&*pass, &salt)
|
||||
.map(|hash| hash.serialize().as_str().to_string())
|
||||
}
|
||||
})
|
||||
.await??)
|
||||
}
|
||||
|
||||
async fn verify_password(pass: &str, hash: &str) -> Result<bool, Error> {
|
||||
@ -42,22 +49,23 @@ async fn verify_password(pass: &str, hash: &str) -> Result<bool, Error> {
|
||||
let pass = pass.to_owned();
|
||||
let hash = hash.to_owned();
|
||||
|
||||
move ||
|
||||
move || {
|
||||
PasswordHash::new(&*hash)
|
||||
.map(|parsed| Pbkdf2.verify_password(
|
||||
&pass.as_bytes(),
|
||||
&parsed
|
||||
).is_ok())
|
||||
}).await??)
|
||||
.map(|parsed| Pbkdf2.verify_password(&pass.as_bytes(), &parsed).is_ok())
|
||||
}
|
||||
})
|
||||
.await??)
|
||||
}
|
||||
|
||||
pub async fn reset_password<'a, E>(pool: E, id: i16, password: String) -> Result<(), crate::error::Error>
|
||||
pub async fn reset_password<'a, E>(
|
||||
pool: E,
|
||||
id: i16,
|
||||
password: String,
|
||||
) -> Result<(), crate::error::Error>
|
||||
where
|
||||
E: sqlx::SqliteExecutor<'a>
|
||||
E: sqlx::SqliteExecutor<'a>,
|
||||
{
|
||||
let password_string = hash_password(
|
||||
password.as_bytes()
|
||||
).await?;
|
||||
let password_string = hash_password(password.as_bytes()).await?;
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE users SET password_hash = ? WHERE user_id = ?",
|
||||
@ -70,16 +78,18 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create_user<'a, E>(acq: E, name: String, password: String) -> Result<(), crate::error::Error>
|
||||
pub async fn create_user<'a, E>(
|
||||
acq: E,
|
||||
name: String,
|
||||
password: String,
|
||||
) -> Result<(), crate::error::Error>
|
||||
where
|
||||
E: sqlx::Acquire<'a, Database = sqlx::Sqlite>
|
||||
E: sqlx::Acquire<'a, Database = sqlx::Sqlite>,
|
||||
{
|
||||
let mut tx = acq.begin().await?;
|
||||
|
||||
let previous_user_check = sqlx::query_scalar!(
|
||||
"SELECT COUNT(*) FROM users WHERE user_name = ?",
|
||||
name
|
||||
)
|
||||
let previous_user_check =
|
||||
sqlx::query_scalar!("SELECT COUNT(*) FROM users WHERE user_name = ?", name)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?;
|
||||
|
||||
@ -108,37 +118,30 @@ const SESSION_ID_KEY: &'static str = "session_id";
|
||||
const SESSION_AGE: i64 = 30 * 60;
|
||||
|
||||
pub async fn create_auth_session(username: String, password: String) -> Result<(), ServerFnError> {
|
||||
use axum_extra::extract::cookie::{Cookie, SameSite};
|
||||
use axum::http::{header, HeaderValue};
|
||||
use axum_extra::extract::cookie::{Cookie, SameSite};
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
let resp = expect_context::<ResponseOptions>();
|
||||
|
||||
let user: Option<User> = sqlx::query_as!(
|
||||
User,
|
||||
"SELECT * FROM users WHERE user_name = ?",
|
||||
username
|
||||
)
|
||||
let user: Option<User> =
|
||||
sqlx::query_as!(User, "SELECT * FROM users WHERE user_name = ?", username)
|
||||
.fetch_optional(&db)
|
||||
.await?;
|
||||
|
||||
let Some(user) = user else {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("Invalid credentials".to_string()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Invalid credentials".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
let good_hash = verify_password(
|
||||
&password,
|
||||
&user.password_hash
|
||||
).await?;
|
||||
let good_hash = verify_password(&password, &user.password_hash).await?;
|
||||
|
||||
if good_hash {
|
||||
let now = chrono::Utc::now().timestamp();
|
||||
let expires = now + SESSION_AGE;
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE users SET last_active = ?",
|
||||
now
|
||||
)
|
||||
sqlx::query!("UPDATE users SET last_active = ?", now)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
@ -146,7 +149,8 @@ pub async fn create_auth_session(username: String, password: String) -> Result<(
|
||||
let mut key = [0u8; 32];
|
||||
OsRng.fill_bytes(&mut key);
|
||||
hex::encode(&key[..])
|
||||
}).await?;
|
||||
})
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO sessions (session_id, user_id, expires) VALUES (?, ?, ?)",
|
||||
@ -168,7 +172,9 @@ pub async fn create_auth_session(username: String, password: String) -> Result<(
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ServerFnError::<NoCustomError>::ServerError("Invalid credentials".to_string()))
|
||||
Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Invalid credentials".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,10 +190,7 @@ pub async fn destroy_auth_session() -> Result<(), ServerFnError> {
|
||||
|
||||
let session_id = cookie.value();
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM sessions WHERE session_id = ?",
|
||||
session_id
|
||||
)
|
||||
sqlx::query!("DELETE FROM sessions WHERE session_id = ?", session_id)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
@ -241,10 +244,7 @@ pub async fn get_auth_session() -> Result<Option<User>, ServerFnError> {
|
||||
.await?;
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM sessions WHERE expires < ?",
|
||||
now
|
||||
)
|
||||
sqlx::query!("DELETE FROM sessions WHERE expires < ?", now)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
|
||||
@ -1,36 +1,37 @@
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
mod cli;
|
||||
#[cfg(feature = "ssr")]
|
||||
mod webserver;
|
||||
#[cfg(feature = "ssr")]
|
||||
mod beacons;
|
||||
#[cfg(feature = "ssr")]
|
||||
pub mod users;
|
||||
pub mod error;
|
||||
mod cli;
|
||||
pub mod db;
|
||||
pub mod error;
|
||||
#[cfg(feature = "ssr")]
|
||||
pub mod users;
|
||||
#[cfg(feature = "ssr")]
|
||||
mod webserver;
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<std::process::ExitCode> {
|
||||
|
||||
use std::{path::PathBuf, process::ExitCode, str::FromStr};
|
||||
|
||||
use sqlx::sqlite::{SqliteConnectOptions, SqlitePool};
|
||||
use structopt::StructOpt;
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
use sqlx::sqlite::{SqlitePool, SqliteConnectOptions};
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| format!("{}=debug,sparse_handler=debug", env!("CARGO_CRATE_NAME")).into()),
|
||||
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||
format!("{}=debug,sparse_handler=debug", env!("CARGO_CRATE_NAME")).into()
|
||||
}),
|
||||
)
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.init();
|
||||
|
||||
let options = cli::Options::from_args();
|
||||
|
||||
let db_location = options.db_location.clone()
|
||||
let db_location = options
|
||||
.db_location
|
||||
.clone()
|
||||
.or(std::env::var("DATABASE_URL")
|
||||
.map(|p| p.replace("sqlite://", ""))
|
||||
.map(PathBuf::from)
|
||||
@ -45,7 +46,7 @@ async fn main() -> anyhow::Result<std::process::ExitCode> {
|
||||
if !options.init_ok {
|
||||
tracing::error!("Database doesn't exist, and initialization not allowed!");
|
||||
tracing::error!("{:?}", e);
|
||||
return Ok(ExitCode::FAILURE)
|
||||
return Ok(ExitCode::FAILURE);
|
||||
}
|
||||
|
||||
tracing::info!("Database doesn't exist, readying initialization");
|
||||
@ -53,14 +54,13 @@ async fn main() -> anyhow::Result<std::process::ExitCode> {
|
||||
|
||||
let pool = SqlitePool::connect_with(
|
||||
SqliteConnectOptions::from_str(&format!("sqlite://{}", db_location.to_string_lossy()))?
|
||||
.create_if_missing(options.init_ok)
|
||||
).await?;
|
||||
.create_if_missing(options.init_ok),
|
||||
)
|
||||
.await?;
|
||||
|
||||
tracing::info!("Running database migrations...");
|
||||
|
||||
sqlx::migrate!()
|
||||
.run(&pool)
|
||||
.await?;
|
||||
sqlx::migrate!().run(&pool).await?;
|
||||
|
||||
tracing::info!("Done running database migrations!");
|
||||
|
||||
@ -69,12 +69,8 @@ async fn main() -> anyhow::Result<std::process::ExitCode> {
|
||||
tracing::info!("Performing requested action, acting as web server");
|
||||
webserver::serve_web(management_address, pool).await
|
||||
}
|
||||
Some(cli::Command::ExtractPubKey { }) => {
|
||||
Ok(ExitCode::SUCCESS)
|
||||
}
|
||||
Some(cli::Command::User { command }) => {
|
||||
cli::user::handle_user_command(command, pool).await
|
||||
}
|
||||
Some(cli::Command::ExtractPubKey {}) => Ok(ExitCode::SUCCESS),
|
||||
Some(cli::Command::User { command }) => cli::user::handle_user_command(command, pool).await,
|
||||
None => {
|
||||
use std::net::{Ipv4Addr, SocketAddrV4};
|
||||
|
||||
|
||||
@ -1,29 +1,33 @@
|
||||
use chrono::{DateTime, offset::Utc};
|
||||
use chrono::{offset::Utc, DateTime};
|
||||
use leptos::prelude::*;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "ssr")]
|
||||
use {
|
||||
sqlx::SqlitePool,
|
||||
leptos::server_fn::error::NoCustomError,
|
||||
crate::db::user
|
||||
};
|
||||
use {crate::db::user, leptos::server_fn::error::NoCustomError, sqlx::SqlitePool};
|
||||
|
||||
pub fn format_delta(time: chrono::TimeDelta) -> String {
|
||||
let seconds = time.num_seconds();
|
||||
|
||||
match seconds {
|
||||
0..=59 => format!("{} second{} ago", seconds, if seconds == 1 {""} else {"s"}),
|
||||
0..=59 => format!(
|
||||
"{} second{} ago",
|
||||
seconds,
|
||||
if seconds == 1 { "" } else { "s" }
|
||||
),
|
||||
60..=3599 => {
|
||||
let minutes = seconds / 60;
|
||||
format!("{} minute{} ago", minutes, if minutes == 1 {""} else {"s"})
|
||||
format!(
|
||||
"{} minute{} ago",
|
||||
minutes,
|
||||
if minutes == 1 { "" } else { "s" }
|
||||
)
|
||||
}
|
||||
3600..=86399 => {
|
||||
let hours = seconds / 3600;
|
||||
format!("{} hours{} ago", hours, if hours == 1 {""} else {"s"})
|
||||
format!("{} hours{} ago", hours, if hours == 1 { "" } else { "s" })
|
||||
}
|
||||
_ => {
|
||||
let days = seconds / 86400;
|
||||
format!("{} day{} ago", days, if days == 1 {""} else {"s"})
|
||||
format!("{} day{} ago", days, if days == 1 { "" } else { "s" })
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -44,14 +48,14 @@ impl std::cmp::PartialEq for User {
|
||||
pub struct DbUser {
|
||||
user_id: i64,
|
||||
user_name: String,
|
||||
last_active: Option<i64>
|
||||
last_active: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct PubUser {
|
||||
user_id: i64,
|
||||
user_name: String,
|
||||
last_active: Option<DateTime<Utc>>
|
||||
last_active: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[server]
|
||||
@ -59,15 +63,14 @@ async fn delete_user(user_id: i64) -> Result<(), ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let pool = expect_context::<SqlitePool>();
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM users WHERE user_id = ?",
|
||||
user_id
|
||||
)
|
||||
sqlx::query!("DELETE FROM users WHERE user_id = ?", user_id)
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
|
||||
@ -79,7 +82,9 @@ async fn reset_password(user_id: i64, password: String) -> Result<(), ServerFnEr
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let pool = expect_context::<SqlitePool>();
|
||||
@ -96,15 +101,21 @@ pub fn RenderUser(refresh_user_list: Action<(), ()>, user: PubUser) -> impl Into
|
||||
#[cfg_attr(feature = "ssr", allow(unused_variables))]
|
||||
let UseIntervalReturn { counter, .. } = use_interval(1000);
|
||||
#[cfg_attr(feature = "ssr", allow(unused_variables))]
|
||||
let (time_ago, set_time_ago) = signal(user.last_active.map(|active| format_delta(Utc::now() - active)));
|
||||
let (time_ago, set_time_ago) = signal(
|
||||
user.last_active
|
||||
.map(|active| format_delta(Utc::now() - active)),
|
||||
);
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
Effect::watch(
|
||||
move || counter.get(),
|
||||
move |_, _, _| {
|
||||
set_time_ago(user.last_active.map(|active| format_delta(Utc::now() - active)));
|
||||
set_time_ago(
|
||||
user.last_active
|
||||
.map(|active| format_delta(Utc::now() - active)),
|
||||
);
|
||||
},
|
||||
false
|
||||
false,
|
||||
);
|
||||
|
||||
let dialog_ref = NodeRef::<leptos::html::Dialog>::new();
|
||||
@ -220,23 +231,27 @@ async fn list_users() -> Result<Vec<PubUser>, ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
use futures::stream::StreamExt;
|
||||
|
||||
let pool = expect_context::<SqlitePool>();
|
||||
|
||||
let users = sqlx::query_as!(
|
||||
DbUser,
|
||||
"SELECT user_id, user_name, last_active FROM users"
|
||||
)
|
||||
let users = sqlx::query_as!(DbUser, "SELECT user_id, user_name, last_active FROM users")
|
||||
.fetch(&pool)
|
||||
.map(|user| user.map(|u| PubUser {
|
||||
.map(|user| {
|
||||
user.map(|u| PubUser {
|
||||
user_id: u.user_id,
|
||||
user_name: u.user_name,
|
||||
last_active: u.last_active.map(|ts| DateTime::from_timestamp(ts, 0)).flatten()
|
||||
}))
|
||||
last_active: u
|
||||
.last_active
|
||||
.map(|ts| DateTime::from_timestamp(ts, 0))
|
||||
.flatten(),
|
||||
})
|
||||
})
|
||||
.collect::<Vec<Result<_, _>>>()
|
||||
.await;
|
||||
|
||||
@ -250,7 +265,9 @@ async fn add_user(name: String, password: String) -> Result<(), ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"You are not signed in!".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let pool = expect_context::<SqlitePool>();
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
use std::{net::SocketAddrV4, process::ExitCode};
|
||||
|
||||
use sqlx::sqlite::SqlitePool;
|
||||
use axum::{extract::{FromRef, Path, State}, response::IntoResponse, Router, routing::get};
|
||||
use axum::{
|
||||
extract::{FromRef, Path, State},
|
||||
response::IntoResponse,
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use leptos::prelude::*;
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
use sqlx::sqlite::SqlitePool;
|
||||
use tokio::signal;
|
||||
|
||||
use sparse_server::app::*;
|
||||
@ -11,8 +16,10 @@ use sparse_server::app::*;
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub(crate) mod beacon_binaries {
|
||||
pub const LINUX_INSTALLER: &'static [u8] = include_bytes!(std::env!("SPARSE_INSTALLER_LINUX"));
|
||||
pub const FREEBSD_INSTALLER: &'static [u8] = include_bytes!(std::env!("SPARSE_INSTALLER_FREEBSD"));
|
||||
pub const WINDOWS_INSTALLER: &'static [u8] = include_bytes!(std::env!("SPARSE_INSTALLER_WINDOWS"));
|
||||
pub const FREEBSD_INSTALLER: &'static [u8] =
|
||||
include_bytes!(std::env!("SPARSE_INSTALLER_FREEBSD"));
|
||||
pub const WINDOWS_INSTALLER: &'static [u8] =
|
||||
include_bytes!(std::env!("SPARSE_INSTALLER_WINDOWS"));
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
@ -21,7 +28,11 @@ pub async fn get_installer(btype: &str) -> Result<Vec<u8>, crate::error::Error>
|
||||
"linux" => "target/x86_64-unknown-linux-musl/debug/sparse-unix-installer",
|
||||
"freebsd" => "target/x86_64-unknown-freebsd/debug/sparse-unix-installer",
|
||||
"windows" => "target/x86_64-pc-windows-gnu/debug/sparse-unix-installer",
|
||||
other => return Err(crate::error::Error::Generic(format!("unknown beacon type: {other}"))),
|
||||
other => {
|
||||
return Err(crate::error::Error::Generic(format!(
|
||||
"unknown beacon type: {other}"
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(tokio::fs::read(path).await?)
|
||||
@ -32,20 +43,22 @@ pub async fn get_installer(btype: &str) -> Result<Vec<u8>, crate::error::Error>
|
||||
"linux" => Ok(beacon_binaries::LINUX_INSTALLER.to_owned()),
|
||||
"windows" => Ok(beacon_binaries::WINDOWS_INSTALLER.to_owned()),
|
||||
"freebsd" => Ok(beacon_binaries::FREEBSD_INSTALLER.to_owned()),
|
||||
other => Err(crate::error::Error::Generic(format!("unknown beacon type: {other}")))
|
||||
other => Err(crate::error::Error::Generic(format!(
|
||||
"unknown beacon type: {other}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromRef, Clone, Debug)]
|
||||
pub struct AppState {
|
||||
db: SqlitePool,
|
||||
leptos_options: leptos::config::LeptosOptions
|
||||
leptos_options: leptos::config::LeptosOptions,
|
||||
}
|
||||
|
||||
#[axum::debug_handler]
|
||||
pub async fn download_beacon_installer(
|
||||
Path(template_id): Path<i64>,
|
||||
State(db): State<AppState>
|
||||
State(db): State<AppState>,
|
||||
) -> Result<impl IntoResponse, crate::error::Error> {
|
||||
use rand::{rngs::OsRng, TryRngCore};
|
||||
use sparse_actions::payload_types::{Parameters_t, XOR_KEY};
|
||||
@ -53,11 +66,13 @@ pub async fn download_beacon_installer(
|
||||
let mut parameters_buffer = vec![0u8; std::mem::size_of::<Parameters_t>()];
|
||||
let _ = OsRng.try_fill_bytes(&mut parameters_buffer);
|
||||
|
||||
let parameters: &mut Parameters_t = unsafe { std::mem::transmute(parameters_buffer.as_mut_ptr()) };
|
||||
let parameters: &mut Parameters_t =
|
||||
unsafe { std::mem::transmute(parameters_buffer.as_mut_ptr()) };
|
||||
|
||||
let template = sqlx::query!(
|
||||
r"SELECT operating_system, source_ip, source_mac, source_mode, source_netmask,
|
||||
source_gateway, port, public_ip, domain_name, certificate, client_cert, client_key
|
||||
source_gateway, port, public_ip, domain_name, certificate, client_cert, client_key,
|
||||
source_interface
|
||||
FROM beacon_template JOIN beacon_listener"
|
||||
)
|
||||
.fetch_one(&db.db)
|
||||
@ -79,19 +94,29 @@ pub async fn download_beacon_installer(
|
||||
.map(|by| u8::from_str_radix(by, 16))
|
||||
.collect::<Result<Vec<u8>, _>>()
|
||||
.map_err(|_| crate::error::Error::Generic("Could not parse source MAC address".to_string()))
|
||||
.and_then(
|
||||
|bytes| bytes.try_into().map_err(|_| crate::error::Error::Generic("Could not parse source MAC address".to_string()))
|
||||
)?;
|
||||
.and_then(|bytes| {
|
||||
bytes.try_into().map_err(|_| {
|
||||
crate::error::Error::Generic("Could not parse source MAC address".to_string())
|
||||
})
|
||||
})?;
|
||||
|
||||
let src_octets = src_ip.octets();
|
||||
match (template.source_mode.as_deref(), template.source_netmask, template.source_gateway) {
|
||||
match (
|
||||
template.source_mode.as_deref(),
|
||||
template.source_netmask,
|
||||
template.source_gateway,
|
||||
) {
|
||||
(Some("custom"), Some(nm), Some(ip)) => unsafe {
|
||||
let gateway = ip.parse::<std::net::Ipv4Addr>()?;
|
||||
let gw_octets = gateway.octets();
|
||||
|
||||
parameters.source_ip.custom_networking.mode = 0;
|
||||
parameters.source_ip.custom_networking.source_mac.copy_from_slice(&src_mac[..]);
|
||||
parameters.source_ip.custom_networking.netmask = nm as u16;
|
||||
parameters
|
||||
.source_ip
|
||||
.custom_networking
|
||||
.source_mac
|
||||
.copy_from_slice(&src_mac[..]);
|
||||
parameters.source_ip.custom_networking.source_ip.a = src_octets[0];
|
||||
parameters.source_ip.custom_networking.source_ip.b = src_octets[1];
|
||||
parameters.source_ip.custom_networking.source_ip.c = src_octets[2];
|
||||
@ -100,17 +125,31 @@ pub async fn download_beacon_installer(
|
||||
parameters.source_ip.custom_networking.gateway.b = gw_octets[1];
|
||||
parameters.source_ip.custom_networking.gateway.c = gw_octets[2];
|
||||
parameters.source_ip.custom_networking.gateway.d = gw_octets[3];
|
||||
|
||||
if let Some(intf) = &template.source_interface {
|
||||
parameters.source_ip.custom_networking.interface[..intf.len()]
|
||||
.copy_from_slice(&intf[..]);
|
||||
parameters.source_ip.custom_networking.interface_len = intf.len() as u8;
|
||||
} else {
|
||||
parameters.source_ip.custom_networking.interface_len = 0;
|
||||
}
|
||||
},
|
||||
(Some("host"), _, _) => unsafe {
|
||||
parameters.source_ip.use_host_networking.mode = 1;
|
||||
parameters.source_ip.use_host_networking.source_mac.copy_from_slice(&src_mac[..]);
|
||||
parameters
|
||||
.source_ip
|
||||
.use_host_networking
|
||||
.source_mac
|
||||
.copy_from_slice(&src_mac[..]);
|
||||
parameters.source_ip.use_host_networking.source_ip.a = src_octets[0];
|
||||
parameters.source_ip.use_host_networking.source_ip.b = src_octets[1];
|
||||
parameters.source_ip.use_host_networking.source_ip.c = src_octets[2];
|
||||
parameters.source_ip.use_host_networking.source_ip.d = src_octets[3];
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return Err(crate::error::Error::Generic("Could not parse host networking configuration".to_string()));
|
||||
return Err(crate::error::Error::Generic(
|
||||
"Could not parse host networking configuration".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,10 +183,7 @@ pub async fn download_beacon_installer(
|
||||
|
||||
Ok((
|
||||
[
|
||||
(
|
||||
header::CONTENT_TYPE,
|
||||
"application/octet-stream".to_string()
|
||||
),
|
||||
(header::CONTENT_TYPE, "application/octet-stream".to_string()),
|
||||
(
|
||||
header::CONTENT_DISPOSITION,
|
||||
format!(
|
||||
@ -157,17 +193,17 @@ pub async fn download_beacon_installer(
|
||||
} else {
|
||||
""
|
||||
}
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
],
|
||||
[
|
||||
&installer_bytes[..],
|
||||
¶meters_bytes[..]
|
||||
].concat()
|
||||
[&installer_bytes[..], ¶meters_bytes[..]].concat(),
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn serve_web(management_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 leptos_options = conf.leptos_options;
|
||||
let routes = generate_route_list(App);
|
||||
@ -183,7 +219,7 @@ pub async fn serve_web(management_address: SocketAddrV4, db: SqlitePool) -> anyh
|
||||
|
||||
let state = AppState {
|
||||
leptos_options: leptos_options.clone(),
|
||||
db: db.clone()
|
||||
db: db.clone(),
|
||||
};
|
||||
|
||||
let app = Router::new()
|
||||
@ -198,20 +234,26 @@ pub async fn serve_web(management_address: SocketAddrV4, db: SqlitePool) -> anyh
|
||||
{
|
||||
let leptos_options = leptos_options.clone();
|
||||
move || shell(leptos_options.clone())
|
||||
}
|
||||
},
|
||||
)
|
||||
.fallback(leptos_axum::file_and_error_handler::<leptos::config::LeptosOptions, _>(shell))
|
||||
.fallback(leptos_axum::file_and_error_handler::<
|
||||
leptos::config::LeptosOptions,
|
||||
_,
|
||||
>(shell))
|
||||
.with_state(state)
|
||||
.layer(
|
||||
tower::ServiceBuilder::new()
|
||||
.layer(tower_http::trace::TraceLayer::new_for_http())
|
||||
.layer(compression_layer)
|
||||
.layer(compression_layer),
|
||||
);
|
||||
|
||||
// run our app with hyper
|
||||
// `axum::Server` is a re-export of `hyper::Server`
|
||||
let management_listener = tokio::net::TcpListener::bind(&management_address).await?;
|
||||
tracing::info!("management interface listening on http://{}", &management_address);
|
||||
tracing::info!(
|
||||
"management interface listening on http://{}",
|
||||
&management_address
|
||||
);
|
||||
|
||||
axum::serve(management_listener, app.into_make_service())
|
||||
.with_graceful_shutdown(shutdown_signal())
|
||||
|
||||
@ -94,11 +94,11 @@ where
|
||||
|
||||
let access_time = libc::timespec {
|
||||
tv_sec: metadata.atime(),
|
||||
tv_nsec: metadata.atime_nsec()
|
||||
tv_nsec: metadata.atime_nsec(),
|
||||
};
|
||||
let modify_time = libc::timespec {
|
||||
tv_sec: metadata.mtime(),
|
||||
tv_nsec: metadata.mtime_nsec()
|
||||
tv_nsec: metadata.mtime_nsec(),
|
||||
};
|
||||
|
||||
unsafe {
|
||||
@ -320,7 +320,6 @@ where
|
||||
cap_set_fd(binary.as_raw_fd(), current_caps);
|
||||
cap_free(current_caps);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
use std::{
|
||||
fs::OpenOptions,
|
||||
io::{prelude::*, Error, SeekFrom},
|
||||
io::{Error, SeekFrom, prelude::*},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use rand::{rngs::OsRng, TryRngCore};
|
||||
use rand::{TryRngCore, rngs::OsRng};
|
||||
use structopt::StructOpt;
|
||||
|
||||
use sparse_actions::payload_types::{Parameters, XOR_KEY};
|
||||
|
||||
@ -7,5 +7,7 @@ version.workspace = true
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
windows = { version = "0.59.0", features = ["Win32_System_SystemServices", "Win32_UI_WindowsAndMessaging"] }
|
||||
anyhow = "1.0.95"
|
||||
pcap-sys = { version = "0.1.0", path = "../pcap-sys" }
|
||||
windows = { version = "0.59.0", features = ["Win32_NetworkManagement_IpHelper", "Win32_NetworkManagement_Ndis", "Win32_Networking_WinSock", "Win32_System_SystemServices", "Win32_UI_WindowsAndMessaging"] }
|
||||
winreg = "0.55"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use windows::{
|
||||
core::*,
|
||||
Win32::{System::SystemServices::DLL_PROCESS_ATTACH, UI::WindowsAndMessaging::*},
|
||||
core::*,
|
||||
};
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
|
||||
144
sparse-windows-beacon/src/main.rs
Normal file
144
sparse-windows-beacon/src/main.rs
Normal file
@ -0,0 +1,144 @@
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let devs = pcap_sys::PcapDevIterator::new()?;
|
||||
|
||||
for dev in devs {
|
||||
println!("{dev}");
|
||||
}
|
||||
|
||||
unsafe {
|
||||
use std::ffi::CStr;
|
||||
|
||||
use windows::Win32::{
|
||||
NetworkManagement::IpHelper::{
|
||||
GAA_FLAG_INCLUDE_GATEWAYS, GET_ADAPTERS_ADDRESSES_FLAGS, GetAdaptersAddresses,
|
||||
IP_ADAPTER_ADDRESSES_LH,
|
||||
},
|
||||
Networking::WinSock::AF_INET,
|
||||
};
|
||||
|
||||
let mut size_pointer: u32 = 0;
|
||||
|
||||
let err = GetAdaptersAddresses(
|
||||
2,
|
||||
GET_ADAPTERS_ADDRESSES_FLAGS(0),
|
||||
None,
|
||||
None,
|
||||
&mut size_pointer as *mut _,
|
||||
);
|
||||
|
||||
let mut address_buffer = vec![0; size_pointer as usize];
|
||||
|
||||
let err2 = GetAdaptersAddresses(
|
||||
2,
|
||||
GAA_FLAG_INCLUDE_GATEWAYS,
|
||||
None,
|
||||
Some(address_buffer.as_mut_ptr() as *mut _),
|
||||
&mut size_pointer as *mut _,
|
||||
);
|
||||
|
||||
if err2 != 0 {
|
||||
eprintln!("Error code received for second one: {err2}");
|
||||
Err(std::io::Error::last_os_error())?;
|
||||
}
|
||||
|
||||
let mut current_address = address_buffer.as_mut_ptr() as *mut IP_ADAPTER_ADDRESSES_LH;
|
||||
|
||||
fn pwstr_to_string(pwstr: *mut u16) -> String {
|
||||
use std::ffi::OsString;
|
||||
use std::os::windows::ffi::OsStringExt;
|
||||
use std::slice;
|
||||
|
||||
if pwstr.is_null() {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
// Find the length of the null-terminated UTF-16 string
|
||||
let mut len = 0;
|
||||
unsafe {
|
||||
while *pwstr.add(len) != 0 {
|
||||
len += 1;
|
||||
}
|
||||
|
||||
// Convert UTF-16 slice to Rust String
|
||||
let wide_slice = slice::from_raw_parts(pwstr, len);
|
||||
OsString::from_wide(wide_slice)
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
}
|
||||
}
|
||||
|
||||
while !current_address.is_null() {
|
||||
println!("-----");
|
||||
println!(
|
||||
"Name: {:?} ({:?})",
|
||||
CStr::from_ptr((*current_address).AdapterName.0 as *const _),
|
||||
pwstr_to_string((*current_address).FriendlyName.0)
|
||||
);
|
||||
|
||||
println!("Mtu: {:?}", (*current_address).Mtu);
|
||||
println!(
|
||||
"Physical address: {:X?}",
|
||||
&(*current_address).PhysicalAddress
|
||||
[..(*current_address).PhysicalAddressLength as usize]
|
||||
);
|
||||
|
||||
println!("IP addresses:");
|
||||
let mut unicast_address = (*current_address).FirstUnicastAddress;
|
||||
|
||||
while !unicast_address.is_null() {
|
||||
let address = (*(*unicast_address).Address.lpSockaddr).sa_data;
|
||||
|
||||
println!(
|
||||
"\tIP address: {}.{}.{}.{}/{}",
|
||||
address[2] as u8,
|
||||
address[3] as u8,
|
||||
address[4] as u8,
|
||||
address[5] as u8,
|
||||
(*unicast_address).OnLinkPrefixLength
|
||||
);
|
||||
|
||||
unicast_address = (*unicast_address).Next;
|
||||
}
|
||||
|
||||
println!("Gateways:");
|
||||
let mut gateway = (*current_address).FirstGatewayAddress;
|
||||
|
||||
while !gateway.is_null() {
|
||||
let address = (*(*gateway).Address.lpSockaddr).sa_data;
|
||||
|
||||
println!(
|
||||
"\tIP address: {}.{}.{}.{}",
|
||||
address[2] as u8, address[3] as u8, address[4] as u8, address[5] as u8
|
||||
);
|
||||
|
||||
gateway = (*gateway).Next;
|
||||
}
|
||||
|
||||
println!("Routes:");
|
||||
let mut route = (*current_address).FirstPrefix;
|
||||
|
||||
while !route.is_null() {
|
||||
let address = (*(*route).Address.lpSockaddr).sa_data;
|
||||
|
||||
println!(
|
||||
"\tRoute: {}.{}.{}.{}/{}",
|
||||
address[2] as u8,
|
||||
address[3] as u8,
|
||||
address[4] as u8,
|
||||
address[5] as u8,
|
||||
(*route).PrefixLength
|
||||
);
|
||||
|
||||
route = (*route).Next;
|
||||
}
|
||||
|
||||
println!("\n");
|
||||
|
||||
current_address = (*current_address).Next;
|
||||
}
|
||||
|
||||
drop(address_buffer);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
io::{prelude::*, Error},
|
||||
io::{Error, prelude::*},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
use std::{
|
||||
fs::OpenOptions,
|
||||
io::{prelude::*, Error, SeekFrom},
|
||||
io::{Error, SeekFrom, prelude::*},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use rand::{rngs::OsRng, TryRngCore};
|
||||
use rand::{TryRngCore, rngs::OsRng};
|
||||
use structopt::StructOpt;
|
||||
|
||||
use sparse_actions::payload_types::{Parameters, XOR_KEY};
|
||||
@ -103,7 +103,7 @@ fn main() -> Result<(), Error> {
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn install_winpcap(load_winpcap: bool) -> Result<(), Error> {
|
||||
use winreg::{enums::*, RegKey, RegValue};
|
||||
use winreg::{RegKey, RegValue, enums::*};
|
||||
|
||||
std::fs::write(r"C:\Windows\System32\wpcap.dll", WPCAP_DLL)?;
|
||||
std::fs::write(r"C:\Windows\System32\Packet.dll", PACKET_DLL)?;
|
||||
@ -138,9 +138,9 @@ fn install_winpcap(load_winpcap: bool) -> Result<(), Error> {
|
||||
if load_winpcap {
|
||||
unsafe {
|
||||
use windows::Win32::System::Services::{
|
||||
CreateServiceW, OpenSCManagerW, OpenServiceW, StartServiceW, SC_MANAGER_ALL_ACCESS,
|
||||
CreateServiceW, OpenSCManagerW, OpenServiceW, SC_MANAGER_ALL_ACCESS,
|
||||
SERVICE_ALL_ACCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
|
||||
SERVICE_KERNEL_DRIVER, SERVICE_START,
|
||||
SERVICE_KERNEL_DRIVER, SERVICE_START, StartServiceW,
|
||||
};
|
||||
use windows_strings::*;
|
||||
|
||||
@ -159,7 +159,9 @@ fn install_winpcap(load_winpcap: bool) -> Result<(), Error> {
|
||||
|
||||
if let Ok(srvc) = npfsrvc {
|
||||
println!("Service already installed, starting");
|
||||
println!("(If it fails because it's already running, that's fine, everything has worked)");
|
||||
println!(
|
||||
"(If it fails because it's already running, that's fine, everything has worked)"
|
||||
);
|
||||
StartServiceW(srvc, None)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@ -16,9 +16,13 @@ typedef union SourceIp {
|
||||
struct {
|
||||
char mode; // set to 1
|
||||
unsigned char source_mac[6];
|
||||
unsigned char interface_len;
|
||||
unsigned short netmask;
|
||||
ipaddr_t source_ip;
|
||||
ipaddr_t gateway;
|
||||
// Windows references interfaces by GUID
|
||||
// I'm too lazy to pack it more efficiently
|
||||
unsigned char interface[40];
|
||||
} custom_networking;
|
||||
} SourceIp_t;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user