diff --git a/Cargo.lock b/Cargo.lock index b0fd7b3..611ee6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -143,6 +143,8 @@ dependencies = [ "pnet_datalink", "rand", "rhai", + "serde", + "serde_json", "socket2", "tracing", "tracing-subscriber", @@ -158,6 +160,7 @@ dependencies = [ "hex", "ipnet", "rand", + "serde", "thiserror", "trust-dns-proto", "url", @@ -288,6 +291,9 @@ name = "ipnet" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" +dependencies = [ + "serde", +] [[package]] name = "ipnetwork" @@ -569,6 +575,20 @@ name = "serde" version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "serde_json" @@ -779,6 +799,7 @@ dependencies = [ "ipnet", "lazy_static", "rand", + "serde", "smallvec", "thiserror", "tinyvec", @@ -823,6 +844,7 @@ dependencies = [ "idna", "matches", "percent-encoding", + "serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8d6a01b..d30babb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dhcproto = "0.12.0" +dhcproto = { version = "0.12.0", features = ["serde"] } anyhow = "1.0" argh = "=0.1.7" argh_derive = "=0.1.7" @@ -31,6 +31,8 @@ hex = "0.4" rhai = { version = "1.5.0", optional = true } socket2 = { version = "0.5", features = ["all"] } pnet_datalink = "0.31.0" +serde = "1.0" +serde_json = "1.0" # rhai-rand = { version = "0.1", optional = true } [features] diff --git a/README.md b/README.md index 0dffe44..b7c5f47 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ cargo install dhcpm --features "script" --locked ## Use +There are two levels of options, run `dhcpm --help` to see which commands are available and `dhcpm --help` to see the options for each command. + ``` > dhcpm --help @@ -145,6 +147,32 @@ Specify an interface with v6, it is necessary to join the multicast group. > sudo dhcpm ff02::1:2 -i enp6s0 inforeq ``` +### Logging + +Use `dhcpm --output json ` to output JSON formatted logs. If you want just a JSON formatted version of the message received, you can use `jq`: + +``` +dhcpm 255.255.255.255 --output json dora | jq -r ' .fields.msg | fromjson' +``` + +```json +{ + "type": "V4", + "opcode": "BootRequest", + "htype": "Eth", + "hlen": 6, + "hops": 0, + "xid": 2734656336, + "secs": 0, + "flags": 0, + "ciaddr": "0.0.0.0", + "yiaddr": "0.0.0.0", + "siaddr": "0.0.0.0", + "giaddr": "192.168.5.1" + // fields omitted ... +} +``` + ### Scripting Scripting support with [rhai](https://github.com/rhaiscript/rhai). Compile `dhcpm` with the `script` feature and give it a path with `--script`: diff --git a/src/main.rs b/src/main.rs index f10b39e..0c941f9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -167,7 +167,7 @@ fn main() -> Result<()> { // messages coming from `recv_rx` were received from the socket let (recv_tx, recv_rx) = crossbeam_channel::bounded(1); - runner::sender_thread(send_rx, soc.clone()); + runner::sender_thread(send_rx, soc.clone(), args.output); runner::recv_thread(recv_tx, soc); let start = Instant::now(); @@ -418,8 +418,12 @@ pub mod util { use anyhow::Result; use dhcproto::{v4, v6, Encodable}; + use serde::Serialize; - #[derive(Clone, PartialEq, Eq)] + use crate::opts::LogStructure; + + #[derive(Clone, PartialEq, Eq, Serialize)] + #[serde(tag = "type")] pub enum Msg { V4(v4::Message), V6(v6::Message), @@ -475,17 +479,27 @@ pub mod util { } #[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)] - pub struct PrettyPrint(pub T); + pub struct PrettyPrint(pub T, pub LogStructure); - impl fmt::Display for PrettyPrint { + impl fmt::Display for PrettyPrint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:#?}", &self.0) + match self.1 { + LogStructure::Json => { + write!(f, "{}", serde_json::to_string_pretty(&self.0).unwrap()) + } + _ => write!(f, "{:#?}", &self.0), + } } } - impl fmt::Debug for PrettyPrint { + impl fmt::Debug for PrettyPrint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self.0) + match self.1 { + LogStructure::Json => { + write!(f, "{}", serde_json::to_string_pretty(&self.0).unwrap()) + } + _ => write!(f, "{:#?}", &self.0), + } } } diff --git a/src/opts.rs b/src/opts.rs index 3fdc65f..c3214be 100644 --- a/src/opts.rs +++ b/src/opts.rs @@ -26,13 +26,19 @@ pub fn get_mac() -> MacAddress { .unwrap() } -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub enum LogStructure { Debug, Pretty, Json, } +impl Default for LogStructure { + fn default() -> Self { + Self::Pretty + } +} + impl FromStr for LogStructure { type Err = Error; fn from_str(s: &str) -> Result { diff --git a/src/runner.rs b/src/runner.rs index 4db5031..d873259 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -15,6 +15,7 @@ use dhcproto::{ }; use crate::{ + opts::LogStructure, util::{Msg, PrettyPrint, PrettyTime}, Args, MsgType, }; @@ -53,7 +54,7 @@ impl TimeoutRunner { recv(self.recv_rx) -> res => { match res { Ok((msg, _addr)) => { - info!(msg_type = %msg.get_type(), elapsed = %PrettyTime(start.elapsed()), msg = %PrettyPrint(&msg), "RECEIVED"); + info!(msg_type = %msg.get_type(), elapsed = %PrettyTime(start.elapsed()), msg = %PrettyPrint(&msg, self.args.output), "RECEIVED"); return Ok(msg); } Err(err) => { @@ -118,10 +119,13 @@ impl TimeoutRunner { } } -pub fn sender_thread(send_rx: Receiver<(Msg, SocketAddr, bool)>, soc: Arc) { +pub fn sender_thread( + send_rx: Receiver<(Msg, SocketAddr, bool)>, + soc: Arc, + output: LogStructure, +) { thread::spawn(move || { while let Ok((msg, target, brd)) = send_rx.recv() { - trace!("got"); let port = target.port(); // set broadcast appropriately let target: SocketAddr = match target.ip() { @@ -134,7 +138,7 @@ pub fn sender_thread(send_rx: Receiver<(Msg, SocketAddr, bool)>, soc: Arc (IpAddr::V6(addr), port).into(), }; soc.send_to(&msg.to_vec()?[..], target)?; - info!(msg_type = %msg.get_type(), ?target, msg = %PrettyPrint(&msg), "SENT"); + info!(msg_type = %msg.get_type(), ?target, msg = %PrettyPrint(&msg, output), "SENT"); } trace!("sender thread exited"); Ok::<_, anyhow::Error>(())