use std::collections::VecDeque;
use std::ffi::{CStr, CString};
use std::ptr;
use std::time::Duration;
use crate::ffi::libscamperctrl::{self, ScamperCtrlT, ScamperInstT, ScamperTaskT,
SCAMPER_CTRL_TYPE_DATA, SCAMPER_CTRL_TYPE_MORE, SCAMPER_CTRL_TYPE_ERR,
SCAMPER_CTRL_TYPE_EOF, SCAMPER_CTRL_TYPE_FATAL};
use crate::ffi::scamper_file;
use crate::inst::{ScamperInst, InstData};
use crate::task::ScamperTask;
use crate::mux::ScamperMux;
use crate::vp::ScamperVp;
use crate::file::{ScamperFile, ScamperObject, parse_scamper_obj};
struct CtrlItem {
inst_data: *mut InstData,
inst_name: Option<String>,
obj: ScamperObject,
}
pub struct ResponseItem {
pub obj: ScamperObject,
pub inst_name: Option<String>,
}
struct CtrlData {
objs: VecDeque<CtrlItem>,
errors: VecDeque<String>,
task_count: usize,
meta: bool,
outfile: Option<ScamperFile>,
}
unsafe extern "C" fn ctrl_cb(
c_inst: *mut ScamperInstT,
kind: u8,
c_task: *mut ScamperTaskT,
data: *const libc::c_void,
len: libc::size_t,
) {
let c_ctrl = unsafe { libscamperctrl::scamper_inst_ctrl_get(c_inst) };
let ctrl_data = unsafe { &mut *(libscamperctrl::scamper_ctrl_param_get(c_ctrl) as *mut CtrlData) };
let inst_data = unsafe { &mut *(libscamperctrl::scamper_inst_param_get(c_inst) as *mut InstData) };
if !c_task.is_null() {
if let Some(pos) = inst_data.tasks.iter().position(|&t| t == c_task) {
inst_data.tasks.swap_remove(pos);
if ctrl_data.task_count > 0 {
ctrl_data.task_count -= 1;
}
}
}
match kind {
SCAMPER_CTRL_TYPE_DATA => {
unsafe { scamper_file::scamper_file_readbuf_add(inst_data.c_rb, data, len) };
let mut o_type: u16 = 0;
let mut o_data: *mut libc::c_void = ptr::null_mut();
unsafe { scamper_file::scamper_file_read(inst_data.c_f, ptr::null(), &mut o_type, &mut o_data) };
if o_data.is_null() { return; }
if let Some(obj) = unsafe { parse_scamper_obj(o_type, o_data) } {
if let Some(ref mut outfile) = ctrl_data.outfile {
let _ = outfile.write(&obj);
}
let name_ptr = unsafe { libscamperctrl::scamper_inst_name_get(c_inst) };
let inst_name = if name_ptr.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(name_ptr) }.to_string_lossy().into_owned())
};
inst_data.queued += 1;
ctrl_data.objs.push_back(CtrlItem {
inst_data: inst_data as *mut InstData,
inst_name,
obj,
});
}
}
SCAMPER_CTRL_TYPE_ERR => {
let msg = if data.is_null() {
"error from instance".to_string()
} else {
let cstr = unsafe { CStr::from_ptr(data as *const libc::c_char) };
cstr.to_string_lossy().into_owned()
};
ctrl_data.errors.push_back(msg);
}
SCAMPER_CTRL_TYPE_FATAL => {
let c_ctrl2 = unsafe { libscamperctrl::scamper_inst_ctrl_get(c_inst) };
let errptr = unsafe { libscamperctrl::scamper_ctrl_strerror(c_ctrl2) };
let msg = if errptr.is_null() {
"fatal error".to_string()
} else {
unsafe { CStr::from_ptr(errptr) }.to_string_lossy().into_owned()
};
ctrl_data.errors.push_back(format!("fatal: {}", msg));
}
SCAMPER_CTRL_TYPE_EOF => {
inst_data.eof = true;
}
SCAMPER_CTRL_TYPE_MORE => {
}
_ => {}
}
}
pub struct AttachParams {
inner: *mut libscamperctrl::ScamperAttpT,
}
impl AttachParams {
pub fn new() -> Option<Self> {
let inner = unsafe { libscamperctrl::scamper_attp_alloc() };
if inner.is_null() { None } else { Some(AttachParams { inner }) }
}
pub fn set_list_id(&mut self, id: u32) {
unsafe { libscamperctrl::scamper_attp_listid_set(self.inner, id) };
}
pub fn set_cycle_id(&mut self, id: u32) {
unsafe { libscamperctrl::scamper_attp_cycleid_set(self.inner, id) };
}
pub fn set_priority(&mut self, priority: u32) {
unsafe { libscamperctrl::scamper_attp_priority_set(self.inner, priority) };
}
}
impl Drop for AttachParams {
fn drop(&mut self) {
unsafe { libscamperctrl::scamper_attp_free(self.inner) };
}
}
#[derive(Debug)]
pub struct ScamperCtrlError(pub String);
impl std::fmt::Display for ScamperCtrlError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for ScamperCtrlError {}
pub struct ScamperCtrl {
c: *mut ScamperCtrlT,
data: *mut CtrlData,
insts: Vec<ScamperInst>,
muxes: Vec<ScamperMux>,
}
impl ScamperCtrl {
pub fn new(meta: bool, outfile: Option<ScamperFile>) -> Result<Self, String> {
let data = Box::into_raw(Box::new(CtrlData {
objs: VecDeque::new(),
errors: VecDeque::new(),
task_count: 0,
meta,
outfile,
}));
let c = unsafe { libscamperctrl::scamper_ctrl_alloc(ctrl_cb) };
if c.is_null() {
unsafe { drop(Box::from_raw(data)) };
return Err("could not allocate ScamperCtrl".into());
}
unsafe { libscamperctrl::scamper_ctrl_param_set(c, data as *mut libc::c_void) };
Ok(ScamperCtrl { c, data, insts: Vec::new(), muxes: Vec::new() })
}
fn ctrl_strerror(&self) -> String {
let ptr = unsafe { libscamperctrl::scamper_ctrl_strerror(self.c) };
if ptr.is_null() {
"unknown error".into()
} else {
unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned()
}
}
pub fn add_unix(&mut self, path: &str) -> Result<ScamperInst, String> {
let c_path = CString::new(path).map_err(|e| e.to_string())?;
let c = unsafe {
libscamperctrl::scamper_inst_unix(self.c, ptr::null(), c_path.as_ptr())
};
if c.is_null() { return Err(self.ctrl_strerror()); }
let inst = unsafe { ScamperInst::from_ptr(c) };
Ok(inst)
}
pub fn add_inet(&mut self, port: u16, addr: Option<&str>) -> Result<ScamperInst, String> {
let c_addr = match addr {
Some(a) => Some(CString::new(a).map_err(|e| e.to_string())?),
None => None,
};
let addr_ptr = c_addr.as_ref().map_or(ptr::null(), |s| s.as_ptr());
let c = unsafe {
libscamperctrl::scamper_inst_inet(self.c, ptr::null(), addr_ptr, port)
};
if c.is_null() { return Err(self.ctrl_strerror()); }
let inst = unsafe { ScamperInst::from_ptr(c) };
Ok(inst)
}
pub fn add_remote(&mut self, path: &str) -> Result<ScamperInst, String> {
let c_path = CString::new(path).map_err(|e| e.to_string())?;
let c = unsafe { libscamperctrl::scamper_inst_remote(self.c, c_path.as_ptr()) };
if c.is_null() { return Err(self.ctrl_strerror()); }
let inst = unsafe { ScamperInst::from_ptr(c) };
Ok(inst)
}
pub fn add_mux(&mut self, path: &str) -> Result<&ScamperMux, String> {
let c_path = CString::new(path).map_err(|e| e.to_string())?;
let c = unsafe { libscamperctrl::scamper_mux_add(self.c, c_path.as_ptr()) };
if c.is_null() { return Err(self.ctrl_strerror()); }
let mux = unsafe { ScamperMux::from_ptr(c).ok_or("null mux")? };
self.muxes.push(mux);
Ok(self.muxes.last().unwrap())
}
pub fn add_vp(&mut self, vp: &ScamperVp) -> Result<ScamperInst, String> {
let c = unsafe { libscamperctrl::scamper_inst_vp(self.c, vp.inner) };
if c.is_null() { return Err(self.ctrl_strerror()); }
let inst = unsafe { ScamperInst::from_ptr(c) };
Ok(inst)
}
pub fn vps(&self) -> Vec<ScamperVp> {
self.muxes.iter().flat_map(|m| m.vps()).collect()
}
pub fn is_done(&self) -> bool {
let ctrl_data = unsafe { &*self.data };
if !ctrl_data.objs.is_empty() { return false; }
unsafe { libscamperctrl::scamper_ctrl_isdone(self.c) != 0 }
}
pub fn errors(&mut self) -> Vec<String> {
let ctrl_data = unsafe { &mut *self.data };
ctrl_data.errors.drain(..).collect()
}
pub fn do_cmd(&mut self, inst: &ScamperInst, cmd: &str) -> Result<ScamperTask, String> {
let cstr = CString::new(cmd).map_err(|e| e.to_string())?;
let task_ptr = unsafe {
libscamperctrl::scamper_inst_do(inst.c, cstr.as_ptr(), ptr::null_mut())
};
if task_ptr.is_null() {
return Err(format!("could not schedule command on {}", inst));
}
let inst_data = unsafe { &mut *inst.data };
inst_data.tasks.push(task_ptr);
let ctrl_data = unsafe { &mut *self.data };
ctrl_data.task_count += 1;
Ok(unsafe { ScamperTask::from_ptr(task_ptr) })
}
#[allow(clippy::too_many_arguments)]
pub fn do_trace(
&mut self,
inst: &ScamperInst,
dst: &str,
confidence: Option<u8>,
dport: Option<u16>,
icmp_id: Option<u16>,
icmp_sum: Option<u16>,
firsthop: Option<u8>,
gaplimit: Option<u8>,
loops: Option<u8>,
hoplimit: Option<u8>,
pmtud: Option<bool>,
squeries: Option<u8>,
ptr_lookup: Option<bool>,
payload: Option<&[u8]>,
method: Option<&str>,
attempts: Option<u8>,
all_attempts: Option<bool>,
rtr: Option<&str>,
sport: Option<u16>,
src: Option<&str>,
tos: Option<u8>,
userid: Option<u32>,
wait_timeout: Option<Duration>,
wait_probe: Option<Duration>,
) -> Result<ScamperTask, String> {
let mut args = vec!["trace".to_string()];
let m = method.map(|s| s.to_lowercase());
if sport.is_some() || dport.is_some() {
match &m {
None => return Err("specify method when specifying port".into()),
Some(mm) if !mm.starts_with("tcp") && !mm.starts_with("udp") =>
return Err(format!("cannot specify ports with method {}", mm)),
_ => {}
}
}
if let Some(s) = sport { args.push(format!("-s {}", s)); }
if let Some(d) = dport { args.push(format!("-d {}", d)); }
if let Some(cs) = icmp_sum { args.push(format!("-d {}", cs)); }
if let Some(id) = icmp_id { args.push(format!("-s {}", id)); }
if let Some(c) = confidence { args.push(format!("-C {}", c)); }
if let Some(f) = firsthop { args.push(format!("-f {}", f)); }
if let Some(g) = gaplimit { args.push(format!("-g {}", g)); }
if let Some(l) = loops { args.push(format!("-l {}", l)); }
if let Some(m) = hoplimit { args.push(format!("-m {}", m)); }
if pmtud == Some(true) { args.push("-M".into()); }
if let Some(n) = squeries { args.push(format!("-N {}", n)); }
if ptr_lookup == Some(true) { args.push("-O ptr".into()); }
if let Some(p) = payload {
args.push(format!("-p {}", hex::encode(p)));
}
if let Some(ref meth) = method { args.push(format!("-P {}", meth)); }
if let Some(q) = attempts { args.push(format!("-q {}", q)); }
if all_attempts == Some(true) { args.push("-Q".into()); }
if let Some(r) = rtr { args.push(format!("-r {}", r)); }
if let Some(s) = src { args.push(format!("-S {}", s)); }
if let Some(t) = tos { args.push(format!("-t {}", t)); }
if let Some(u) = userid { args.push(format!("-U {}", u)); }
if let Some(w) = wait_timeout {
args.push(format!("-w {}s", w.as_secs_f64()));
}
if let Some(w) = wait_probe {
args.push(format!("-W {}s", w.as_secs_f64()));
}
args.push(dst.to_string());
self.do_cmd(inst, &args.join(" "))
}
#[allow(clippy::too_many_arguments)]
pub fn do_tracelb(
&mut self,
inst: &ScamperInst,
dst: &str,
confidence: Option<u8>,
dport: Option<u16>,
firsthop: Option<u8>,
gaplimit: Option<u8>,
method: Option<&str>,
attempts: Option<u8>,
ptr_lookup: Option<bool>,
rtr: Option<&str>,
sport: Option<u16>,
tos: Option<u8>,
userid: Option<u32>,
wait_timeout: Option<Duration>,
wait_probe: Option<Duration>,
) -> Result<ScamperTask, String> {
let mut args = vec!["tracelb".to_string()];
if let Some(c) = confidence { args.push(format!("-c {}", c)); }
if let Some(d) = dport { args.push(format!("-d {}", d)); }
if let Some(f) = firsthop { args.push(format!("-f {}", f)); }
if let Some(g) = gaplimit { args.push(format!("-g {}", g)); }
if let Some(ref meth) = method { args.push(format!("-P {}", meth)); }
if let Some(q) = attempts { args.push(format!("-q {}", q)); }
if ptr_lookup == Some(true) { args.push("-O ptr".into()); }
if let Some(r) = rtr { args.push(format!("-r {}", r)); }
if let Some(s) = sport { args.push(format!("-s {}", s)); }
if let Some(t) = tos { args.push(format!("-t {}", t)); }
if let Some(u) = userid { args.push(format!("-U {}", u)); }
if let Some(w) = wait_timeout {
args.push(format!("-w {}s", w.as_secs_f64()));
}
if let Some(w) = wait_probe {
args.push(format!("-W {}s", w.as_secs_f64()));
}
args.push(dst.to_string());
self.do_cmd(inst, &args.join(" "))
}
#[allow(clippy::too_many_arguments)]
pub fn do_ping(
&mut self,
inst: &ScamperInst,
dst: &str,
tcp_ack: Option<u32>,
tcp_seq: Option<u32>,
attempts: Option<u16>,
icmp_id: Option<u16>,
icmp_seq: Option<u16>,
icmp_sum: Option<u16>,
dport: Option<u16>,
sport: Option<u16>,
wait_probe: Option<Duration>,
ttl: Option<u8>,
mtu: Option<u16>,
stop_count: Option<u16>,
method: Option<&str>,
payload: Option<&[u8]>,
rtr: Option<&str>,
recordroute: Option<bool>,
size: Option<u16>,
src: Option<&str>,
wait_timeout: Option<Duration>,
tos: Option<u8>,
userid: Option<u32>,
) -> Result<ScamperTask, String> {
let mut args = vec!["ping".to_string()];
if let Some(a) = tcp_ack { args.push(format!("-A {}", a)); }
if let Some(a) = tcp_seq { args.push(format!("-A {}", a)); }
if let Some(p) = payload {
args.push(format!("-B {}", hex::encode(p)));
}
if let Some(c) = attempts { args.push(format!("-c {}", c)); }
if let Some(cs) = icmp_sum { args.push(format!("-C {}", cs)); }
if let Some(d) = dport { args.push(format!("-d {}", d)); }
if let Some(s) = icmp_seq { args.push(format!("-d {}", s)); }
if let Some(f) = sport { args.push(format!("-F {}", f)); }
if let Some(id) = icmp_id { args.push(format!("-F {}", id)); }
if let Some(w) = wait_probe {
args.push(format!("-i {}s", w.as_secs_f64()));
}
if let Some(m) = ttl { args.push(format!("-m {}", m)); }
if let Some(m) = mtu { args.push(format!("-M {}", m)); }
if let Some(o) = stop_count { args.push(format!("-o {}", o)); }
if let Some(ref meth) = method { args.push(format!("-P {}", meth)); }
if let Some(r) = rtr { args.push(format!("-r {}", r)); }
if recordroute == Some(true) { args.push("-R".into()); }
if let Some(s) = size { args.push(format!("-s {}", s)); }
if let Some(s) = src { args.push(format!("-S {}", s)); }
if let Some(u) = userid { args.push(format!("-U {}", u)); }
if let Some(w) = wait_timeout {
args.push(format!("-W {}s", w.as_secs_f64()));
}
if let Some(t) = tos { args.push(format!("-z {}", t)); }
args.push(dst.to_string());
self.do_cmd(inst, &args.join(" "))
}
#[allow(clippy::too_many_arguments)]
pub fn do_dns(
&mut self,
inst: &ScamperInst,
qname: &str,
server: Option<&str>,
qclass: Option<&str>,
qtype: Option<&str>,
attempts: Option<u8>,
rd: Option<bool>,
wait_timeout: Option<Duration>,
tcp: Option<bool>,
nsid: Option<bool>,
ecs: Option<&str>,
userid: Option<u32>,
) -> Result<ScamperTask, String> {
let mut args = vec!["host".to_string()];
if let Some(s) = server { args.push(format!("-s {}", s)); }
if let Some(c) = qclass { args.push(format!("-c {}", c)); }
if let Some(t) = qtype { args.push(format!("-t {}", t)); }
if let Some(a) = attempts {
if a < 1 { return Err("attempts < 1".into()); }
args.push(format!("-R {}", a));
}
if let Some(w) = wait_timeout {
args.push(format!("-W {}s", w.as_secs_f64()));
}
if let Some(u) = userid { args.push(format!("-U {}", u)); }
if rd == Some(false) { args.push("-r".into()); }
if tcp == Some(true) { args.push("-T".into()); }
if nsid == Some(true) { args.push("-O nsid".into()); }
if let Some(e) = ecs { args.push(format!("-O subnet={}", e)); }
args.push(qname.to_string());
self.do_cmd(inst, &args.join(" "))
}
#[allow(clippy::too_many_arguments)]
pub fn do_ally(
&mut self,
inst: &ScamperInst,
dst1: &str,
dst2: &str,
fudge: Option<u16>,
icmp_sum: Option<u16>,
dport: Option<u16>,
sport: Option<u16>,
method: Option<&str>,
attempts: Option<u8>,
wait_probe: Option<Duration>,
wait_timeout: Option<Duration>,
userid: Option<u32>,
) -> Result<ScamperTask, String> {
let mut args = vec!["dealias -m ally".to_string()];
let m = method.map(|s| s.to_lowercase());
if let Some(ref mm) = m {
let mut pd = format!("-p '-P {}", mm);
if let Some(s) = sport { pd.push_str(&format!(" -F {}", s)); }
if let Some(d) = dport { pd.push_str(&format!(" -d {}", d)); }
if let Some(cs) = icmp_sum { pd.push_str(&format!(" -c {}", cs)); }
pd.push('\'');
args.push(pd);
}
if let Some(f) = fudge {
if f == 0 { args.push("-O inseq".into()); }
else { args.push(format!("-f {}", f)); }
}
if let Some(a) = attempts { args.push(format!("-q {}", a)); }
if let Some(u) = userid { args.push(format!("-U {}", u)); }
if let Some(w) = wait_probe {
args.push(format!("-W {}s", w.as_secs_f64()));
}
if let Some(w) = wait_timeout {
args.push(format!("-w {}s", w.as_secs_f64()));
}
args.push(format!("{} {}", dst1, dst2));
self.do_cmd(inst, &args.join(" "))
}
pub fn do_mercator(
&mut self,
inst: &ScamperInst,
dst: &str,
userid: Option<u32>,
) -> Result<ScamperTask, String> {
let mut args = vec!["dealias -m mercator".to_string()];
if let Some(u) = userid { args.push(format!("-U {}", u)); }
args.push(dst.to_string());
self.do_cmd(inst, &args.join(" "))
}
#[allow(clippy::too_many_arguments)]
pub fn do_prefixscan(
&mut self,
inst: &ScamperInst,
near: &str,
far: &str,
prefixlen: u8,
fudge: Option<u16>,
icmp_sum: Option<u16>,
dport: Option<u16>,
sport: Option<u16>,
method: Option<&str>,
attempts: Option<u8>,
wait_probe: Option<Duration>,
wait_timeout: Option<Duration>,
userid: Option<u32>,
) -> Result<ScamperTask, String> {
let mut args = vec!["dealias -m prefixscan".to_string()];
let m = method.map(|s| s.to_lowercase()).unwrap_or_else(|| "udp".to_string());
let mut pd = format!("-p '-P {}", m);
if let Some(s) = sport { pd.push_str(&format!(" -F {}", s)); }
if let Some(d) = dport { pd.push_str(&format!(" -d {}", d)); }
if let Some(cs) = icmp_sum { pd.push_str(&format!(" -c {}", cs)); }
pd.push('\'');
args.push(pd);
if let Some(f) = fudge {
if f == 0 { args.push("-O inseq".into()); }
else { args.push(format!("-f {}", f)); }
}
if let Some(a) = attempts { args.push(format!("-q {}", a)); }
if let Some(u) = userid { args.push(format!("-U {}", u)); }
if let Some(w) = wait_probe {
args.push(format!("-W {}s", w.as_secs_f64()));
}
if let Some(w) = wait_timeout {
args.push(format!("-w {}s", w.as_secs_f64()));
}
args.push(format!("{} {}/{}", near, far, prefixlen));
self.do_cmd(inst, &args.join(" "))
}
#[allow(clippy::too_many_arguments)]
pub fn do_radargun(
&mut self,
inst: &ScamperInst,
probedefs: &[&str],
addrs: Option<&[&str]>,
rounds: Option<u32>,
wait_probe: Option<Duration>,
wait_round: Option<Duration>,
wait_timeout: Option<Duration>,
userid: Option<u32>,
) -> Result<ScamperTask, String> {
let mut args = vec!["dealias -m radargun".to_string()];
if let Some(r) = rounds { args.push(format!("-q {}", r)); }
if let Some(u) = userid { args.push(format!("-U {}", u)); }
if let Some(w) = wait_probe {
args.push(format!("-W {}s", w.as_secs_f64()));
}
if let Some(w) = wait_round {
args.push(format!("-r {}s", w.as_secs_f64()));
}
if let Some(w) = wait_timeout {
args.push(format!("-w {}s", w.as_secs_f64()));
}
for pd in probedefs { args.push(format!("-p '{}'", pd)); }
if let Some(addrs) = addrs {
for a in addrs { args.push(a.to_string()); }
}
self.do_cmd(inst, &args.join(" "))
}
#[allow(clippy::too_many_arguments)]
pub fn do_midarest(
&mut self,
inst: &ScamperInst,
probedefs: &[&str],
addrs: &[&str],
rounds: Option<u32>,
wait_probe: Option<Duration>,
wait_round: Option<Duration>,
wait_timeout: Option<Duration>,
userid: Option<u32>,
) -> Result<ScamperTask, String> {
if probedefs.is_empty() { return Err("missing probedefs".into()); }
if addrs.is_empty() { return Err("missing addrs".into()); }
let mut args = vec!["dealias -m midarest".to_string()];
if let Some(r) = rounds { args.push(format!("-q {}", r)); }
if let Some(u) = userid { args.push(format!("-U {}", u)); }
if let Some(w) = wait_probe {
let ms = w.as_millis();
if ms == 0 { return Err("wait_probe must be at least 1ms".into()); }
args.push(format!("-W {}", ms));
}
if let Some(w) = wait_timeout {
let s = w.as_secs();
if s == 0 { return Err("wait_timeout must be at least 1s".into()); }
args.push(format!("-w {}", s));
}
if let Some(w) = wait_round {
let ms = w.as_millis();
if ms == 0 { return Err("wait_round must be at least 1ms".into()); }
args.push(format!("-r {}", ms));
}
for pd in probedefs { args.push(format!("-p '{}'", pd)); }
for addr in addrs { args.push(addr.to_string()); }
self.do_cmd(inst, &args.join(" "))
}
#[allow(clippy::too_many_arguments)]
pub fn do_midardisc(
&mut self,
inst: &ScamperInst,
probedefs: &[&str],
schedule: &[&str],
startat: Option<f64>,
wait_timeout: Option<Duration>,
userid: Option<u32>,
) -> Result<ScamperTask, String> {
if probedefs.is_empty() { return Err("missing probedefs".into()); }
if schedule.is_empty() { return Err("missing schedule".into()); }
let mut args = vec!["dealias -m midardisc".to_string()];
if let Some(u) = userid { args.push(format!("-U {}", u)); }
if let Some(w) = wait_timeout {
let s = w.as_secs();
if s == 0 { return Err("wait_timeout must be at least 1s".into()); }
args.push(format!("-w {}", s));
}
if let Some(t) = startat { args.push(format!("-@ {}", t)); }
for pd in probedefs { args.push(format!("-p '{}'", pd)); }
for s in schedule { args.push(format!("-S {}", s)); }
self.do_cmd(inst, &args.join(" "))
}
pub fn do_sniff(
&mut self,
inst: &ScamperInst,
src: &str,
icmp_id: u16,
limit_pkt_count: Option<u32>,
limit_time: Option<Duration>,
userid: Option<u32>,
) -> Result<ScamperTask, String> {
let mut args = vec![format!("sniff -S {}", src)];
if let Some(c) = limit_pkt_count { args.push(format!("-c {}", c)); }
if let Some(t) = limit_time {
args.push(format!("-G {}s", t.as_secs_f64()));
}
if let Some(u) = userid { args.push(format!("-U {}", u)); }
args.push(format!("icmp[icmpid] == {}", icmp_id));
self.do_cmd(inst, &args.join(" "))
}
pub fn do_http(
&mut self,
inst: &ScamperInst,
dst: &str,
url: &str,
headers: Option<&[(&str, &str)]>,
insecure: bool,
limit_time: Option<Duration>,
userid: Option<u32>,
) -> Result<ScamperTask, String> {
let mut args = vec!["http".to_string()];
if insecure { args.push("-O insecure".into()); }
if let Some(t) = limit_time {
args.push(format!("-m {}s", t.as_secs_f64()));
}
if let Some(hdrs) = headers {
for (name, val) in hdrs {
args.push(format!("-H '{}: {}'", name, val));
}
}
if let Some(u) = userid { args.push(format!("-U {}", u)); }
let esc_url = url.replace('\'', "\\'");
args.push(format!("-u '{}' {}", esc_url, dst));
self.do_cmd(inst, &args.join(" "))
}
pub fn do_udpprobe(
&mut self,
inst: &ScamperInst,
dst: &str,
dport: u16,
payload: &[u8],
attempts: Option<u16>,
src: Option<&str>,
stop_count: Option<u16>,
userid: Option<u32>,
) -> Result<ScamperTask, String> {
if dport == 0 { return Err("invalid destination port".into()); }
let mut args = vec!["udpprobe".to_string()];
args.push(format!("-d {}", dport));
args.push(format!("-p {}", hex::encode(payload)));
if let Some(a) = attempts { args.push(format!("-c {}", a)); }
if let Some(o) = stop_count { args.push(format!("-o {}", o)); }
if let Some(s) = src { args.push(format!("-S {}", s)); }
if let Some(u) = userid { args.push(format!("-U {}", u)); }
args.push(dst.to_string());
self.do_cmd(inst, &args.join(" "))
}
pub fn do_tbit(
&mut self,
inst: &ScamperInst,
dst: &str,
method: &str,
url: &str,
userid: Option<u32>,
) -> Result<ScamperTask, String> {
let mut args = vec!["tbit".to_string()];
if let Some(u) = userid { args.push(format!("-U {}", u)); }
let esc_url = url.replace('\'', "\\'");
args.push(format!("-u '{}' -t {} {}", esc_url, method, dst));
self.do_cmd(inst, &args.join(" "))
}
#[allow(clippy::too_many_arguments)]
pub fn do_owamp(
&mut self,
inst: &ScamperInst,
dst: &str,
direction: &str,
attempts: Option<u32>,
dscp: Option<u8>,
schedule: Option<&str>,
size: Option<u16>,
startat: Option<f64>,
ttl: Option<u8>,
wait_timeout: Option<Duration>,
userid: Option<u32>,
) -> Result<ScamperTask, String> {
if direction != "tx" && direction != "rx" {
return Err("direction must be tx or rx".into());
}
let mut args = vec!["owamp".to_string()];
if let Some(t) = startat { args.push(format!("-@ {}", t)); }
if let Some(w) = wait_timeout {
args.push(format!("-w {}s", w.as_secs_f64()));
}
if let Some(s) = schedule { args.push(format!("-i {}", s)); }
if let Some(c) = attempts { args.push(format!("-c {}", c)); }
if let Some(s) = size { args.push(format!("-s {}", s)); }
if let Some(m) = ttl { args.push(format!("-m {}", m)); }
if let Some(d) = dscp { args.push(format!("-D {}", d)); }
if let Some(u) = userid { args.push(format!("-U {}", u)); }
args.push(format!("-d {} {}", direction, dst));
self.do_cmd(inst, &args.join(" "))
}
fn wait(&mut self, timeout: Option<Duration>) {
match timeout {
None => {
unsafe { libscamperctrl::scamper_ctrl_wait(self.c, ptr::null_mut()) };
}
Some(d) => {
let mut tv = libc::timeval {
tv_sec: d.as_secs() as libc::time_t,
tv_usec: d.subsec_micros() as libc::suseconds_t,
};
unsafe { libscamperctrl::scamper_ctrl_wait(self.c, &mut tv) };
}
}
}
pub fn responses(&mut self, timeout: Option<Duration>) -> Responses<'_> {
let deadline = timeout.map(|t| {
std::time::Instant::now() + t
});
Responses { ctrl: self, deadline }
}
pub fn poll(&mut self) -> Option<ResponseItem> {
loop {
let data_ptr = self.data;
let task_count;
{
let ctrl_data = unsafe { &mut *data_ptr };
while let Some(item) = ctrl_data.objs.pop_front() {
let inst_data = unsafe { &mut *item.inst_data };
if inst_data.queued > 0 { inst_data.queued -= 1; }
let is_meta = is_meta_obj(&item.obj);
if !is_meta || ctrl_data.meta {
return Some(ResponseItem { obj: item.obj, inst_name: item.inst_name });
}
}
task_count = ctrl_data.task_count;
}
if task_count == 0 { return None; }
self.wait(None);
}
}
}
fn is_meta_obj(obj: &ScamperObject) -> bool {
matches!(obj,
ScamperObject::List(_) |
ScamperObject::CycleStart(_) |
ScamperObject::CycleDef(_) |
ScamperObject::CycleStop(_))
}
pub struct Responses<'a> {
ctrl: &'a mut ScamperCtrl,
deadline: Option<std::time::Instant>,
}
impl<'a> Iterator for Responses<'a> {
type Item = ResponseItem;
fn next(&mut self) -> Option<ResponseItem> {
loop {
let data_ptr = self.ctrl.data;
let task_count;
{
let ctrl_data = unsafe { &mut *data_ptr };
while let Some(item) = ctrl_data.objs.pop_front() {
let inst_data = unsafe { &mut *item.inst_data };
if inst_data.queued > 0 { inst_data.queued -= 1; }
let is_meta = is_meta_obj(&item.obj);
if !is_meta || ctrl_data.meta {
return Some(ResponseItem { obj: item.obj, inst_name: item.inst_name });
}
}
task_count = ctrl_data.task_count;
}
if task_count == 0 { return None; }
let remaining = match self.deadline {
None => None,
Some(dl) => {
let now = std::time::Instant::now();
if now >= dl { return None; }
Some(dl - now)
}
};
self.ctrl.wait(remaining);
}
}
}
impl Drop for ScamperCtrl {
fn drop(&mut self) {
if !self.data.is_null() {
unsafe { drop(Box::from_raw(self.data)) };
self.data = ptr::null_mut();
}
self.insts.clear();
self.muxes.clear();
if !self.c.is_null() {
unsafe { libscamperctrl::scamper_ctrl_free(self.c) };
}
}
}
unsafe impl Send for ScamperCtrl {}
unsafe impl Sync for ScamperCtrl {}
mod hex {
pub fn encode(data: &[u8]) -> String {
data.iter().map(|b| format!("{:02x}", b)).collect()
}
}