mirror of
https://gitlab.com/veilid/veilid.git
synced 2025-08-25 06:49:32 -04:00
2336 lines
77 KiB
Rust
2336 lines
77 KiB
Rust
////////////////////////////////////////////////////////////////
|
|
// Debugging
|
|
|
|
use super::*;
|
|
use data_encoding::BASE64URL_NOPAD;
|
|
use hashlink::LinkedHashMap;
|
|
use network_manager::*;
|
|
use once_cell::sync::Lazy;
|
|
use routing_table::*;
|
|
use std::fmt::Write;
|
|
|
|
#[derive(Default)]
|
|
pub(crate) struct DebugCache {
|
|
pub imported_routes: Vec<RouteId>,
|
|
pub opened_record_contexts: Lazy<LinkedHashMap<TypedKey, RoutingContext>>,
|
|
}
|
|
|
|
pub fn format_opt_ts(ts: Option<TimestampDuration>) -> String {
|
|
let Some(ts) = ts else {
|
|
return "---".to_owned();
|
|
};
|
|
let ts = ts.as_u64();
|
|
let secs = timestamp_to_secs(ts);
|
|
if secs >= 1.0 {
|
|
format!("{:.2}s", timestamp_to_secs(ts))
|
|
} else {
|
|
format!("{:.2}ms", timestamp_to_secs(ts) * 1000.0)
|
|
}
|
|
}
|
|
|
|
pub fn format_opt_bps(bps: Option<ByteCount>) -> String {
|
|
let Some(bps) = bps else {
|
|
return "---".to_owned();
|
|
};
|
|
let bps = bps.as_u64();
|
|
if bps >= 1024u64 * 1024u64 * 1024u64 {
|
|
format!("{:.2}GB/s", (bps / (1024u64 * 1024u64)) as f64 / 1024.0)
|
|
} else if bps >= 1024u64 * 1024u64 {
|
|
format!("{:.2}MB/s", (bps / 1024u64) as f64 / 1024.0)
|
|
} else if bps >= 1024u64 {
|
|
format!("{:.2}KB/s", bps as f64 / 1024.0)
|
|
} else {
|
|
format!("{:.2}B/s", bps as f64)
|
|
}
|
|
}
|
|
|
|
fn get_bucket_entry_state(text: &str) -> Option<BucketEntryState> {
|
|
if text == "punished" {
|
|
Some(BucketEntryState::Punished)
|
|
} else if text == "dead" {
|
|
Some(BucketEntryState::Dead)
|
|
} else if text == "reliable" {
|
|
Some(BucketEntryState::Reliable)
|
|
} else if text == "unreliable" {
|
|
Some(BucketEntryState::Unreliable)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn get_string(text: &str) -> Option<String> {
|
|
Some(text.to_owned())
|
|
}
|
|
|
|
fn get_data(text: &str) -> Option<Vec<u8>> {
|
|
if let Some(stripped_text) = text.strip_prefix('#') {
|
|
hex::decode(stripped_text).ok()
|
|
} else if text.starts_with('"') || text.starts_with('\'') {
|
|
json::parse(text)
|
|
.ok()?
|
|
.as_str()
|
|
.map(|x| x.to_owned().as_bytes().to_vec())
|
|
} else {
|
|
Some(text.as_bytes().to_vec())
|
|
}
|
|
}
|
|
|
|
fn get_subkeys(text: &str) -> Option<ValueSubkeyRangeSet> {
|
|
if let Some(n) = get_number::<u32>(text) {
|
|
Some(ValueSubkeyRangeSet::single(n))
|
|
} else {
|
|
ValueSubkeyRangeSet::from_str(text).ok()
|
|
}
|
|
}
|
|
|
|
fn get_dht_report_scope(text: &str) -> Option<DHTReportScope> {
|
|
match text.to_ascii_lowercase().trim() {
|
|
"local" => Some(DHTReportScope::Local),
|
|
"syncget" => Some(DHTReportScope::SyncGet),
|
|
"syncset" => Some(DHTReportScope::SyncSet),
|
|
"updateget" => Some(DHTReportScope::UpdateGet),
|
|
"updateset" => Some(DHTReportScope::UpdateSet),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn get_route_id(
|
|
rss: RouteSpecStore,
|
|
allow_allocated: bool,
|
|
allow_remote: bool,
|
|
) -> impl Fn(&str) -> Option<RouteId> {
|
|
move |text: &str| {
|
|
if text.is_empty() {
|
|
return None;
|
|
}
|
|
match RouteId::from_str(text).ok() {
|
|
Some(key) => {
|
|
if allow_allocated {
|
|
let routes = rss.list_allocated_routes(|k, _| Some(*k));
|
|
if routes.contains(&key) {
|
|
return Some(key);
|
|
}
|
|
}
|
|
if allow_remote {
|
|
let rroutes = rss.list_remote_routes(|k, _| Some(*k));
|
|
if rroutes.contains(&key) {
|
|
return Some(key);
|
|
}
|
|
}
|
|
}
|
|
None => {
|
|
if allow_allocated {
|
|
let routes = rss.list_allocated_routes(|k, _| Some(*k));
|
|
for r in routes {
|
|
let rkey = r.encode();
|
|
if rkey.starts_with(text) {
|
|
return Some(r);
|
|
}
|
|
}
|
|
}
|
|
if allow_remote {
|
|
let routes = rss.list_remote_routes(|k, _| Some(*k));
|
|
for r in routes {
|
|
let rkey = r.encode();
|
|
if rkey.starts_with(text) {
|
|
return Some(r);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
}
|
|
|
|
fn get_dht_schema(text: &str) -> Option<VeilidAPIResult<DHTSchema>> {
|
|
if text.is_empty() {
|
|
return None;
|
|
}
|
|
if let Ok(n) = u16::from_str(text) {
|
|
return Some(DHTSchema::dflt(n));
|
|
}
|
|
Some(deserialize_json::<DHTSchema>(text))
|
|
}
|
|
|
|
fn get_safety_selection(routing_table: RoutingTable) -> impl Fn(&str) -> Option<SafetySelection> {
|
|
move |text| {
|
|
let rss = routing_table.route_spec_store();
|
|
let default_route_hop_count =
|
|
routing_table.with_config(|c| c.network.rpc.default_route_hop_count as usize);
|
|
|
|
if !text.is_empty() && &text[0..1] == "-" {
|
|
// Unsafe
|
|
let text = &text[1..];
|
|
let seq = get_sequencing(text).unwrap_or_default();
|
|
Some(SafetySelection::Unsafe(seq))
|
|
} else {
|
|
// Safe
|
|
let mut preferred_route = None;
|
|
let mut hop_count = default_route_hop_count;
|
|
let mut stability = Stability::default();
|
|
let mut sequencing = Sequencing::default();
|
|
for x in text.split(',') {
|
|
let x = x.trim();
|
|
if let Some(pr) = get_route_id(rss.clone(), true, false)(x) {
|
|
preferred_route = Some(pr)
|
|
}
|
|
if let Some(n) = get_number(x) {
|
|
hop_count = n;
|
|
}
|
|
if let Some(s) = get_stability(x) {
|
|
stability = s;
|
|
}
|
|
if let Some(s) = get_sequencing(x) {
|
|
sequencing = s;
|
|
}
|
|
}
|
|
let ss = SafetySpec {
|
|
preferred_route,
|
|
hop_count,
|
|
stability,
|
|
sequencing,
|
|
};
|
|
Some(SafetySelection::Safe(ss))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_node_ref_modifiers(node_ref: NodeRef) -> impl FnOnce(&str) -> Option<FilteredNodeRef> {
|
|
move |text| {
|
|
let mut node_ref = node_ref.default_filtered();
|
|
for m in text.split('/') {
|
|
if let Some(pt) = get_protocol_type(m) {
|
|
node_ref.merge_filter(NodeRefFilter::new().with_protocol_type(pt));
|
|
} else if let Some(at) = get_address_type(m) {
|
|
node_ref.merge_filter(NodeRefFilter::new().with_address_type(at));
|
|
} else if let Some(rd) = get_routing_domain(m) {
|
|
node_ref.merge_filter(NodeRefFilter::new().with_routing_domain(rd));
|
|
} else {
|
|
return None;
|
|
}
|
|
}
|
|
Some(node_ref)
|
|
}
|
|
}
|
|
|
|
fn get_number<T: num_traits::Num + FromStr>(text: &str) -> Option<T> {
|
|
T::from_str(text).ok()
|
|
}
|
|
|
|
fn get_typed_key(text: &str) -> Option<TypedKey> {
|
|
TypedKey::from_str(text).ok()
|
|
}
|
|
fn get_public_key(text: &str) -> Option<PublicKey> {
|
|
PublicKey::from_str(text).ok()
|
|
}
|
|
fn get_keypair(text: &str) -> Option<KeyPair> {
|
|
KeyPair::from_str(text).ok()
|
|
}
|
|
|
|
fn get_crypto_system_version(crypto: Crypto) -> impl FnOnce(&str) -> Option<CryptoSystemVersion> {
|
|
move |text| {
|
|
let kindstr = get_string(text)?;
|
|
let kind = CryptoKind::from_str(&kindstr).ok()?;
|
|
crypto.get(kind)
|
|
}
|
|
}
|
|
|
|
fn get_dht_key_no_safety(text: &str) -> Option<TypedKey> {
|
|
let key = if let Some(key) = get_public_key(text) {
|
|
TypedKey::new(best_crypto_kind(), key)
|
|
} else if let Some(key) = get_typed_key(text) {
|
|
key
|
|
} else {
|
|
return None;
|
|
};
|
|
|
|
Some(key)
|
|
}
|
|
|
|
fn get_dht_key(
|
|
routing_table: RoutingTable,
|
|
) -> impl FnOnce(&str) -> Option<(TypedKey, Option<SafetySelection>)> {
|
|
move |text| {
|
|
// Safety selection
|
|
let (text, ss) = if let Some((first, second)) = text.split_once('+') {
|
|
let ss = get_safety_selection(routing_table.clone())(second)?;
|
|
(first, Some(ss))
|
|
} else {
|
|
(text, None)
|
|
};
|
|
if text.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
let key = if let Some(key) = get_public_key(text) {
|
|
TypedKey::new(best_crypto_kind(), key)
|
|
} else if let Some(key) = get_typed_key(text) {
|
|
key
|
|
} else {
|
|
return None;
|
|
};
|
|
|
|
Some((key, ss))
|
|
}
|
|
}
|
|
|
|
fn resolve_filtered_node_ref(
|
|
routing_table: RoutingTable,
|
|
safety_selection: SafetySelection,
|
|
) -> impl FnOnce(&str) -> SendPinBoxFuture<Option<FilteredNodeRef>> {
|
|
move |text| {
|
|
let text = text.to_owned();
|
|
Box::pin(async move {
|
|
let (text, mods) = text
|
|
.split_once('/')
|
|
.map(|x| (x.0, Some(x.1)))
|
|
.unwrap_or((&text, None));
|
|
|
|
let nr = if let Some(key) = get_public_key(text) {
|
|
let node_id = TypedKey::new(best_crypto_kind(), key);
|
|
routing_table
|
|
.rpc_processor()
|
|
.resolve_node(node_id, safety_selection)
|
|
.await
|
|
.ok()
|
|
.flatten()?
|
|
} else if let Some(node_id) = get_typed_key(text) {
|
|
routing_table
|
|
.rpc_processor()
|
|
.resolve_node(node_id, safety_selection)
|
|
.await
|
|
.ok()
|
|
.flatten()?
|
|
} else {
|
|
return None;
|
|
};
|
|
if let Some(mods) = mods {
|
|
Some(get_node_ref_modifiers(nr)(mods)?)
|
|
} else {
|
|
Some(nr.default_filtered())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
fn get_node_ref(routing_table: RoutingTable) -> impl FnOnce(&str) -> Option<NodeRef> {
|
|
move |text| {
|
|
let nr = if let Some(key) = get_public_key(text) {
|
|
routing_table.lookup_any_node_ref(key).ok().flatten()?
|
|
} else if let Some(node_id) = get_typed_key(text) {
|
|
routing_table.lookup_node_ref(node_id).ok().flatten()?
|
|
} else {
|
|
return None;
|
|
};
|
|
Some(nr)
|
|
}
|
|
}
|
|
|
|
fn get_filtered_node_ref(
|
|
routing_table: RoutingTable,
|
|
) -> impl FnOnce(&str) -> Option<FilteredNodeRef> {
|
|
move |text| {
|
|
// Safety selection
|
|
let (text, seq) = if let Some((first, second)) = text.split_once('+') {
|
|
let seq = get_sequencing(second)?;
|
|
(first, Some(seq))
|
|
} else {
|
|
(text, None)
|
|
};
|
|
if text.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
let (text, mods) = text
|
|
.split_once('/')
|
|
.map(|x| (x.0, Some(x.1)))
|
|
.unwrap_or((text, None));
|
|
|
|
let nr = if let Some(key) = get_public_key(text) {
|
|
routing_table.lookup_any_node_ref(key).ok().flatten()?
|
|
} else if let Some(node_id) = get_typed_key(text) {
|
|
routing_table.lookup_node_ref(node_id).ok().flatten()?
|
|
} else {
|
|
return None;
|
|
};
|
|
let nr = if let Some(mods) = mods {
|
|
get_node_ref_modifiers(nr)(mods)?
|
|
} else {
|
|
nr.default_filtered()
|
|
};
|
|
if let Some(seq) = seq {
|
|
Some(nr.sequencing_clone(seq))
|
|
} else {
|
|
Some(nr)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_protocol_type(text: &str) -> Option<ProtocolType> {
|
|
let lctext = text.to_ascii_lowercase();
|
|
if lctext == "udp" {
|
|
Some(ProtocolType::UDP)
|
|
} else if lctext == "tcp" {
|
|
Some(ProtocolType::TCP)
|
|
} else if lctext == "ws" {
|
|
Some(ProtocolType::WS)
|
|
} else if lctext == "wss" {
|
|
Some(ProtocolType::WSS)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
fn get_sequencing(text: &str) -> Option<Sequencing> {
|
|
let seqtext = text.to_ascii_lowercase();
|
|
if seqtext == "np" {
|
|
Some(Sequencing::NoPreference)
|
|
} else if seqtext == "ord" {
|
|
Some(Sequencing::PreferOrdered)
|
|
} else if seqtext == "*ord" {
|
|
Some(Sequencing::EnsureOrdered)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
fn get_stability(text: &str) -> Option<Stability> {
|
|
let sttext = text.to_ascii_lowercase();
|
|
if sttext == "ll" {
|
|
Some(Stability::LowLatency)
|
|
} else if sttext == "rel" {
|
|
Some(Stability::Reliable)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
fn get_direction_set(text: &str) -> Option<DirectionSet> {
|
|
let dstext = text.to_ascii_lowercase();
|
|
if dstext == "in" {
|
|
Some(Direction::Inbound.into())
|
|
} else if dstext == "out" {
|
|
Some(Direction::Outbound.into())
|
|
} else if dstext == "inout" {
|
|
Some(DirectionSet::all())
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn get_address_type(text: &str) -> Option<AddressType> {
|
|
let lctext = text.to_ascii_lowercase();
|
|
if lctext == "ipv4" {
|
|
Some(AddressType::IPV4)
|
|
} else if lctext == "ipv6" {
|
|
Some(AddressType::IPV6)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
fn get_routing_domain(text: &str) -> Option<RoutingDomain> {
|
|
let lctext = text.to_ascii_lowercase();
|
|
if "publicinternet".starts_with(&lctext) {
|
|
Some(RoutingDomain::PublicInternet)
|
|
} else if "localnetwork".starts_with(&lctext) {
|
|
Some(RoutingDomain::LocalNetwork)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn get_published(text: &str) -> Option<bool> {
|
|
let ptext = text.to_ascii_lowercase();
|
|
if ptext == "published" {
|
|
Some(true)
|
|
} else if ptext == "current" {
|
|
Some(false)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn get_debug_argument<T, G: FnOnce(&str) -> Option<T>>(
|
|
value: &str,
|
|
context: &str,
|
|
argument: &str,
|
|
getter: G,
|
|
) -> VeilidAPIResult<T> {
|
|
let Some(val) = getter(value) else {
|
|
apibail_invalid_argument!(context, argument, value);
|
|
};
|
|
Ok(val)
|
|
}
|
|
|
|
async fn async_get_debug_argument<T, G: FnOnce(&str) -> SendPinBoxFuture<Option<T>>>(
|
|
value: &str,
|
|
context: &str,
|
|
argument: &str,
|
|
getter: G,
|
|
) -> VeilidAPIResult<T> {
|
|
let Some(val) = getter(value).await else {
|
|
apibail_invalid_argument!(context, argument, value);
|
|
};
|
|
Ok(val)
|
|
}
|
|
|
|
fn get_debug_argument_at<T, G: FnOnce(&str) -> Option<T>>(
|
|
debug_args: &[String],
|
|
pos: usize,
|
|
context: &str,
|
|
argument: &str,
|
|
getter: G,
|
|
) -> VeilidAPIResult<T> {
|
|
if pos >= debug_args.len() {
|
|
apibail_missing_argument!(context, argument);
|
|
}
|
|
let value = &debug_args[pos];
|
|
let Some(val) = getter(value) else {
|
|
apibail_invalid_argument!(context, argument, value);
|
|
};
|
|
Ok(val)
|
|
}
|
|
|
|
async fn async_get_debug_argument_at<T, G: FnOnce(&str) -> SendPinBoxFuture<Option<T>>>(
|
|
debug_args: &[String],
|
|
pos: usize,
|
|
context: &str,
|
|
argument: &str,
|
|
getter: G,
|
|
) -> VeilidAPIResult<T> {
|
|
if pos >= debug_args.len() {
|
|
apibail_missing_argument!(context, argument);
|
|
}
|
|
let value = &debug_args[pos];
|
|
let Some(val) = getter(value).await else {
|
|
apibail_invalid_argument!(context, argument, value);
|
|
};
|
|
Ok(val)
|
|
}
|
|
|
|
pub fn print_data(data: &[u8], truncate_len: Option<usize>) -> String {
|
|
// check if message body is ascii printable
|
|
let mut printable = true;
|
|
for c in data {
|
|
if *c < 32 || *c > 126 {
|
|
printable = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
let (data, truncated) = if let Some(truncate_len) = truncate_len {
|
|
if data.len() > truncate_len {
|
|
(&data[0..truncate_len], true)
|
|
} else {
|
|
(data, false)
|
|
}
|
|
} else {
|
|
(data, false)
|
|
};
|
|
|
|
let strdata = if printable {
|
|
String::from_utf8_lossy(data).to_string()
|
|
} else {
|
|
let sw = shell_words::quote(String::from_utf8_lossy(data).as_ref()).to_string();
|
|
let h = hex::encode(data);
|
|
if h.len() < sw.len() {
|
|
h
|
|
} else {
|
|
sw
|
|
}
|
|
};
|
|
if truncated {
|
|
format!("{}...", strdata)
|
|
} else {
|
|
strdata
|
|
}
|
|
}
|
|
|
|
impl VeilidAPI {
|
|
async fn debug_buckets(&self, args: String) -> VeilidAPIResult<String> {
|
|
let args: Vec<String> = args.split_whitespace().map(|s| s.to_owned()).collect();
|
|
let mut min_state = BucketEntryState::Unreliable;
|
|
if args.len() == 1 {
|
|
min_state = get_debug_argument(
|
|
&args[0],
|
|
"debug_buckets",
|
|
"min_state",
|
|
get_bucket_entry_state,
|
|
)?;
|
|
}
|
|
// Dump routing table bucket info
|
|
let routing_table = self.network_manager()?.routing_table();
|
|
Ok(routing_table.debug_info_buckets(min_state))
|
|
}
|
|
|
|
async fn debug_dialinfo(&self, _args: String) -> VeilidAPIResult<String> {
|
|
// Dump routing table dialinfo
|
|
let routing_table = self.network_manager()?.routing_table();
|
|
Ok(routing_table.debug_info_dialinfo())
|
|
}
|
|
async fn debug_peerinfo(&self, args: String) -> VeilidAPIResult<String> {
|
|
// Dump routing table peerinfo
|
|
let args: Vec<String> = args.split_whitespace().map(|s| s.to_owned()).collect();
|
|
let routing_table = self.network_manager()?.routing_table();
|
|
|
|
let mut ai = 0;
|
|
let mut opt_routing_domain = None;
|
|
let mut opt_published = None;
|
|
|
|
while ai < args.len() {
|
|
if let Ok(routing_domain) = get_debug_argument_at(
|
|
&args,
|
|
ai,
|
|
"debug_peerinfo",
|
|
"routing_domain",
|
|
get_routing_domain,
|
|
) {
|
|
opt_routing_domain = Some(routing_domain);
|
|
} else if let Ok(published) =
|
|
get_debug_argument_at(&args, ai, "debug_peerinfo", "published", get_published)
|
|
{
|
|
opt_published = Some(published);
|
|
}
|
|
ai += 1;
|
|
}
|
|
|
|
let routing_domain = opt_routing_domain.unwrap_or(RoutingDomain::PublicInternet);
|
|
let published = opt_published.unwrap_or(true);
|
|
|
|
Ok(routing_table.debug_info_peerinfo(routing_domain, published))
|
|
}
|
|
|
|
async fn debug_txtrecord(&self, _args: String) -> VeilidAPIResult<String> {
|
|
// Dump routing table txt record
|
|
let routing_table = self.network_manager()?.routing_table();
|
|
Ok(routing_table.debug_info_txtrecord().await)
|
|
}
|
|
|
|
async fn debug_keypair(&self, args: String) -> VeilidAPIResult<String> {
|
|
let args: Vec<String> = args.split_whitespace().map(|s| s.to_owned()).collect();
|
|
let crypto = self.crypto()?;
|
|
|
|
let vcrypto = get_debug_argument_at(
|
|
&args,
|
|
0,
|
|
"debug_keypair",
|
|
"kind",
|
|
get_crypto_system_version(crypto.clone()),
|
|
)
|
|
.unwrap_or_else(|_| crypto.best());
|
|
|
|
// Generate a keypair
|
|
let out = TypedKeyPair::new(vcrypto.kind(), vcrypto.generate_keypair()).to_string();
|
|
Ok(out)
|
|
}
|
|
|
|
async fn debug_entries(&self, args: String) -> VeilidAPIResult<String> {
|
|
let args: Vec<String> = args.split_whitespace().map(|s| s.to_owned()).collect();
|
|
|
|
let mut min_state = BucketEntryState::Unreliable;
|
|
let mut capabilities = vec![];
|
|
let mut fastest = false;
|
|
for arg in args {
|
|
if let Some(ms) = get_bucket_entry_state(&arg) {
|
|
min_state = ms;
|
|
} else if arg == "fastest" {
|
|
fastest = true;
|
|
} else {
|
|
for cap in arg.split(',') {
|
|
if let Ok(capfcc) = FourCC::from_str(cap) {
|
|
capabilities.push(capfcc);
|
|
} else {
|
|
apibail_invalid_argument!("debug_entries", "unknown", arg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Dump routing table entries
|
|
let routing_table = self.network_manager()?.routing_table();
|
|
Ok(match fastest {
|
|
true => routing_table.debug_info_entries_fastest(min_state, capabilities, 100000),
|
|
false => routing_table.debug_info_entries(min_state, capabilities),
|
|
})
|
|
}
|
|
|
|
async fn debug_entry(&self, args: String) -> VeilidAPIResult<String> {
|
|
let args: Vec<String> = args.split_whitespace().map(|s| s.to_owned()).collect();
|
|
let routing_table = self.network_manager()?.routing_table();
|
|
|
|
let node_ref = get_debug_argument_at(
|
|
&args,
|
|
0,
|
|
"debug_entry",
|
|
"node_id",
|
|
get_node_ref(routing_table),
|
|
)?;
|
|
|
|
// Dump routing table entry
|
|
let routing_table = self.network_manager()?.routing_table();
|
|
Ok(routing_table.debug_info_entry(node_ref))
|
|
}
|
|
|
|
async fn debug_relay(&self, args: String) -> VeilidAPIResult<String> {
|
|
let args: Vec<String> = args.split_whitespace().map(|s| s.to_owned()).collect();
|
|
let routing_table = self.network_manager()?.routing_table();
|
|
|
|
let relay_node = get_debug_argument_at(
|
|
&args,
|
|
0,
|
|
"debug_relay",
|
|
"node_id",
|
|
get_node_ref(routing_table),
|
|
)
|
|
.ok();
|
|
|
|
let routing_domain = get_debug_argument_at(
|
|
&args,
|
|
0,
|
|
"debug_relay",
|
|
"routing_domain",
|
|
get_routing_domain,
|
|
)
|
|
.ok()
|
|
.unwrap_or(RoutingDomain::PublicInternet);
|
|
|
|
// Dump routing table entry
|
|
let routing_table = self.network_manager()?.routing_table();
|
|
match routing_domain {
|
|
RoutingDomain::LocalNetwork => {
|
|
let mut editor = routing_table.edit_local_network_routing_domain();
|
|
if editor.set_relay_node(relay_node).commit(true).await {
|
|
editor.publish();
|
|
}
|
|
}
|
|
RoutingDomain::PublicInternet => {
|
|
let mut editor = routing_table.edit_public_internet_routing_domain();
|
|
if editor.set_relay_node(relay_node).commit(true).await {
|
|
editor.publish();
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok("Relay changed".to_owned())
|
|
}
|
|
|
|
async fn debug_nodeinfo(&self, _args: String) -> VeilidAPIResult<String> {
|
|
// Dump routing table entry
|
|
let routing_table = self.network_manager()?.routing_table();
|
|
let nodeinfo = routing_table.debug_info_nodeinfo();
|
|
|
|
// Dump core state
|
|
let state = self.get_state().await?;
|
|
|
|
let mut peertable = format!(
|
|
"Recent Peers: {} (max {})\n",
|
|
state.network.peers.len(),
|
|
RECENT_PEERS_TABLE_SIZE
|
|
);
|
|
for peer in state.network.peers {
|
|
peertable += &format!(
|
|
" {} | {} | {} | {} down | {} up\n",
|
|
peer.node_ids.first().unwrap(),
|
|
peer.peer_address,
|
|
format_opt_ts(peer.peer_stats.latency.map(|l| l.average)),
|
|
format_opt_bps(Some(peer.peer_stats.transfer.down.average)),
|
|
format_opt_bps(Some(peer.peer_stats.transfer.up.average)),
|
|
);
|
|
}
|
|
|
|
// Dump connection table
|
|
let connman =
|
|
if let Some(connection_manager) = self.network_manager()?.opt_connection_manager() {
|
|
connection_manager.debug_print().await
|
|
} else {
|
|
"Connection manager unavailable when detached".to_owned()
|
|
};
|
|
|
|
Ok(format!("{}\n{}\n{}\n", nodeinfo, peertable, connman))
|
|
}
|
|
|
|
async fn debug_nodeid(&self, _args: String) -> VeilidAPIResult<String> {
|
|
// Dump routing table entry
|
|
let routing_table = self.network_manager()?.routing_table();
|
|
let nodeid = routing_table.debug_info_nodeid();
|
|
Ok(nodeid)
|
|
}
|
|
|
|
async fn debug_config(&self, args: String) -> VeilidAPIResult<String> {
|
|
let mut args = args.as_str();
|
|
let mut config = self.config()?;
|
|
if !args.starts_with("insecure") {
|
|
config = config.safe_config();
|
|
} else {
|
|
args = &args[8..];
|
|
}
|
|
let args = args.trim_start();
|
|
|
|
if args.is_empty() {
|
|
return config.get_key_json("", true);
|
|
}
|
|
let (arg, rest) = args.split_once(' ').unwrap_or((args, ""));
|
|
let rest = rest.trim_start().to_owned();
|
|
|
|
// One argument is 'config get'
|
|
if rest.is_empty() {
|
|
return config.get_key_json(arg, true);
|
|
}
|
|
|
|
// More than one argument is 'config set'
|
|
|
|
// Must be detached
|
|
if !matches!(
|
|
self.get_state().await?.attachment.state,
|
|
AttachmentState::Detached
|
|
) {
|
|
apibail_internal!("Must be detached to change config");
|
|
}
|
|
|
|
// Change the config key
|
|
config.set_key_json(arg, &rest)?;
|
|
Ok("Config value set".to_owned())
|
|
}
|
|
|
|
async fn debug_restart(&self, args: String) -> VeilidAPIResult<String> {
|
|
let args = args.trim_start();
|
|
if args.is_empty() {
|
|
apibail_missing_argument!("debug_restart", "arg_0");
|
|
}
|
|
let (arg, _rest) = args.split_once(' ').unwrap_or((args, ""));
|
|
// let rest = rest.trim_start().to_owned();
|
|
|
|
if arg == "network" {
|
|
// Must be attached
|
|
if matches!(
|
|
self.get_state().await?.attachment.state,
|
|
AttachmentState::Detached
|
|
) {
|
|
apibail_internal!("Must be attached to restart network");
|
|
}
|
|
|
|
let netman = self.network_manager()?;
|
|
netman.restart_network();
|
|
|
|
Ok("Network restarted".to_owned())
|
|
} else {
|
|
apibail_invalid_argument!("debug_restart", "arg_1", arg);
|
|
}
|
|
}
|
|
|
|
async fn debug_purge(&self, args: String) -> VeilidAPIResult<String> {
|
|
let args: Vec<String> = args.split_whitespace().map(|s| s.to_owned()).collect();
|
|
if !args.is_empty() {
|
|
if args[0] == "buckets" {
|
|
// Must be detached
|
|
if !matches!(
|
|
self.get_state().await?.attachment.state,
|
|
AttachmentState::Detached | AttachmentState::Detaching
|
|
) {
|
|
apibail_internal!("Must be detached to purge");
|
|
}
|
|
self.network_manager()?.routing_table().purge_buckets();
|
|
Ok("Buckets purged".to_owned())
|
|
} else if args[0] == "connections" {
|
|
// Purge connection table
|
|
let opt_connection_manager = self.network_manager()?.opt_connection_manager();
|
|
|
|
if let Some(connection_manager) = &opt_connection_manager {
|
|
connection_manager.shutdown().await;
|
|
}
|
|
|
|
// Eliminate last_connections from routing table entries
|
|
self.network_manager()?
|
|
.routing_table()
|
|
.purge_last_connections();
|
|
|
|
if let Some(connection_manager) = &opt_connection_manager {
|
|
connection_manager
|
|
.startup()
|
|
.await
|
|
.map_err(VeilidAPIError::internal)?;
|
|
}
|
|
Ok("Connections purged".to_owned())
|
|
} else if args[0] == "routes" {
|
|
// Purge route spec store
|
|
self.inner.lock().debug_cache.imported_routes.clear();
|
|
let rss = self.network_manager()?.routing_table().route_spec_store();
|
|
match rss.purge().await {
|
|
Ok(_) => Ok("Routes purged".to_owned()),
|
|
Err(e) => Ok(format!("Routes purged but failed to save: {}", e)),
|
|
}
|
|
} else {
|
|
Err(VeilidAPIError::InvalidArgument {
|
|
context: "debug_purge".to_owned(),
|
|
argument: "parameter".to_owned(),
|
|
value: args[0].clone(),
|
|
})
|
|
}
|
|
} else {
|
|
Err(VeilidAPIError::MissingArgument {
|
|
context: "debug_purge".to_owned(),
|
|
argument: "parameter".to_owned(),
|
|
})
|
|
}
|
|
}
|
|
|
|
async fn debug_attach(&self, _args: String) -> VeilidAPIResult<String> {
|
|
if !matches!(
|
|
self.get_state().await?.attachment.state,
|
|
AttachmentState::Detached
|
|
) {
|
|
apibail_internal!("Not detached");
|
|
}
|
|
|
|
self.attach().await?;
|
|
|
|
Ok("Attached".to_owned())
|
|
}
|
|
|
|
async fn debug_detach(&self, _args: String) -> VeilidAPIResult<String> {
|
|
if matches!(
|
|
self.get_state().await?.attachment.state,
|
|
AttachmentState::Detaching
|
|
) {
|
|
apibail_internal!("Not attached");
|
|
};
|
|
|
|
self.detach().await?;
|
|
|
|
Ok("Detached".to_owned())
|
|
}
|
|
|
|
async fn debug_contact(&self, args: String) -> VeilidAPIResult<String> {
|
|
let args: Vec<String> = args.split_whitespace().map(|s| s.to_owned()).collect();
|
|
|
|
let network_manager = self.network_manager()?;
|
|
let routing_table = network_manager.routing_table();
|
|
|
|
let node_ref = get_debug_argument_at(
|
|
&args,
|
|
0,
|
|
"debug_contact",
|
|
"node_ref",
|
|
get_filtered_node_ref(routing_table),
|
|
)?;
|
|
|
|
let cm = network_manager
|
|
.get_node_contact_method(node_ref)
|
|
.map_err(VeilidAPIError::internal)?;
|
|
|
|
Ok(format!("{:#?}", cm))
|
|
}
|
|
|
|
async fn debug_resolve(&self, args: String) -> VeilidAPIResult<String> {
|
|
let netman = self.network_manager()?;
|
|
let routing_table = netman.routing_table();
|
|
let Some(_rpc) = netman.opt_rpc_processor() else {
|
|
apibail_internal!("Must be attached first");
|
|
};
|
|
|
|
let args: Vec<String> = args.split_whitespace().map(|s| s.to_owned()).collect();
|
|
|
|
let dest = async_get_debug_argument_at(
|
|
&args,
|
|
0,
|
|
"debug_resolve",
|
|
"destination",
|
|
self.clone().get_destination(routing_table.clone()),
|
|
)
|
|
.await?;
|
|
|
|
match &dest {
|
|
Destination::Direct {
|
|
node: target,
|
|
safety_selection: _,
|
|
} => Ok(format!(
|
|
"Destination: {:#?}\nTarget Entry:\n{}\n",
|
|
&dest,
|
|
routing_table.debug_info_entry(target.unfiltered())
|
|
)),
|
|
Destination::Relay {
|
|
relay,
|
|
node: target,
|
|
safety_selection: _,
|
|
} => Ok(format!(
|
|
"Destination: {:#?}\nTarget Entry:\n{}\nRelay Entry:\n{}\n",
|
|
&dest,
|
|
routing_table.clone().debug_info_entry(target.clone()),
|
|
routing_table.debug_info_entry(relay.unfiltered())
|
|
)),
|
|
Destination::PrivateRoute {
|
|
private_route: _,
|
|
safety_selection: _,
|
|
} => Ok(format!("Destination: {:#?}", &dest)),
|
|
}
|
|
}
|
|
|
|
async fn debug_ping(&self, args: String) -> VeilidAPIResult<String> {
|
|
let netman = self.network_manager()?;
|
|
let routing_table = netman.routing_table();
|
|
let Some(rpc) = netman.opt_rpc_processor() else {
|
|
apibail_internal!("Must be attached first");
|
|
};
|
|
|
|
let args: Vec<String> = args.split_whitespace().map(|s| s.to_owned()).collect();
|
|
|
|
let dest = async_get_debug_argument_at(
|
|
&args,
|
|
0,
|
|
"debug_ping",
|
|
"destination",
|
|
self.clone().get_destination(routing_table),
|
|
)
|
|
.await?;
|
|
|
|
// Send a StatusQ
|
|
let out = match rpc
|
|
.rpc_call_status(dest)
|
|
.await
|
|
.map_err(VeilidAPIError::internal)?
|
|
{
|
|
NetworkResult::Value(v) => v,
|
|
r => {
|
|
return Ok(r.to_string());
|
|
}
|
|
};
|
|
|
|
Ok(format!("{:#?}", out))
|
|
}
|
|
|
|
async fn debug_app_message(&self, args: String) -> VeilidAPIResult<String> {
|
|
let netman = self.network_manager()?;
|
|
let routing_table = netman.routing_table();
|
|
let Some(rpc) = netman.opt_rpc_processor() else {
|
|
apibail_internal!("Must be attached first");
|
|
};
|
|
|
|
let (arg, rest) = args.split_once(' ').unwrap_or((&args, ""));
|
|
let rest = rest.trim_start().to_owned();
|
|
|
|
let dest = async_get_debug_argument(
|
|
arg,
|
|
"debug_app_message",
|
|
"destination",
|
|
self.clone().get_destination(routing_table),
|
|
)
|
|
.await?;
|
|
|
|
let data = get_debug_argument(&rest, "debug_app_message", "data", get_data)?;
|
|
let data_len = data.len();
|
|
|
|
// Send a AppMessage
|
|
let out = match rpc
|
|
.rpc_call_app_message(dest, data)
|
|
.await
|
|
.map_err(VeilidAPIError::internal)?
|
|
{
|
|
NetworkResult::Value(_) => format!("Sent {} bytes", data_len),
|
|
r => {
|
|
return Ok(r.to_string());
|
|
}
|
|
};
|
|
|
|
Ok(out)
|
|
}
|
|
|
|
async fn debug_app_call(&self, args: String) -> VeilidAPIResult<String> {
|
|
let netman = self.network_manager()?;
|
|
let routing_table = netman.routing_table();
|
|
let Some(rpc) = netman.opt_rpc_processor() else {
|
|
apibail_internal!("Must be attached first");
|
|
};
|
|
|
|
let (arg, rest) = args.split_once(' ').unwrap_or((&args, ""));
|
|
let rest = rest.trim_start().to_owned();
|
|
|
|
let dest = async_get_debug_argument(
|
|
arg,
|
|
"debug_app_call",
|
|
"destination",
|
|
self.clone().get_destination(routing_table),
|
|
)
|
|
.await?;
|
|
|
|
let data = get_debug_argument(&rest, "debug_app_call", "data", get_data)?;
|
|
let data_len = data.len();
|
|
|
|
// Send a AppMessage
|
|
let out = match rpc
|
|
.rpc_call_app_call(dest, data)
|
|
.await
|
|
.map_err(VeilidAPIError::internal)?
|
|
{
|
|
NetworkResult::Value(v) => format!(
|
|
"Sent {} bytes, received: {}",
|
|
data_len,
|
|
print_data(&v.answer, Some(512))
|
|
),
|
|
r => {
|
|
return Ok(r.to_string());
|
|
}
|
|
};
|
|
|
|
Ok(out)
|
|
}
|
|
|
|
async fn debug_app_reply(&self, args: String) -> VeilidAPIResult<String> {
|
|
let netman = self.network_manager()?;
|
|
let Some(rpc) = netman.opt_rpc_processor() else {
|
|
apibail_internal!("Must be attached first");
|
|
};
|
|
|
|
let (call_id, data) = if let Some(stripped_args) = args.strip_prefix('#') {
|
|
let (arg, rest) = stripped_args.split_once(' ').unwrap_or((&args, ""));
|
|
let call_id =
|
|
OperationId::new(u64::from_str_radix(arg, 16).map_err(VeilidAPIError::generic)?);
|
|
let rest = rest.trim_start().to_owned();
|
|
let data = get_debug_argument(&rest, "debug_app_reply", "data", get_data)?;
|
|
(call_id, data)
|
|
} else {
|
|
let call_id = rpc
|
|
.get_app_call_ids()
|
|
.first()
|
|
.cloned()
|
|
.ok_or_else(|| VeilidAPIError::generic("no app calls waiting"))?;
|
|
let data = get_debug_argument(&args, "debug_app_reply", "data", get_data)?;
|
|
(call_id, data)
|
|
};
|
|
|
|
let data_len = data.len();
|
|
|
|
// Send a AppCall Reply
|
|
self.app_call_reply(call_id, data)
|
|
.await
|
|
.map_err(VeilidAPIError::internal)?;
|
|
|
|
Ok(format!("Replied with {} bytes", data_len))
|
|
}
|
|
|
|
async fn debug_route_allocate(&self, args: Vec<String>) -> VeilidAPIResult<String> {
|
|
// [ord|*ord] [rel] [<count>] [in|out] [avoid_node_id]
|
|
|
|
let netman = self.network_manager()?;
|
|
let routing_table = netman.routing_table();
|
|
let rss = routing_table.route_spec_store();
|
|
let config = self.config().unwrap();
|
|
let default_route_hop_count = {
|
|
let c = config.get();
|
|
c.network.rpc.default_route_hop_count as usize
|
|
};
|
|
|
|
let mut ai = 1;
|
|
let mut sequencing = Sequencing::default();
|
|
let mut stability = Stability::default();
|
|
let mut hop_count = default_route_hop_count;
|
|
let mut directions = DirectionSet::all();
|
|
|
|
while ai < args.len() {
|
|
if let Ok(seq) =
|
|
get_debug_argument_at(&args, ai, "debug_route", "sequencing", get_sequencing)
|
|
{
|
|
sequencing = seq;
|
|
} else if let Ok(sta) =
|
|
get_debug_argument_at(&args, ai, "debug_route", "stability", get_stability)
|
|
{
|
|
stability = sta;
|
|
} else if let Ok(hc) =
|
|
get_debug_argument_at(&args, ai, "debug_route", "hop_count", get_number)
|
|
{
|
|
hop_count = hc;
|
|
} else if let Ok(ds) =
|
|
get_debug_argument_at(&args, ai, "debug_route", "direction_set", get_direction_set)
|
|
{
|
|
directions = ds;
|
|
} else {
|
|
return Ok(format!("Invalid argument specified: {}", args[ai]));
|
|
}
|
|
ai += 1;
|
|
}
|
|
|
|
// Allocate route
|
|
let out = match rss.allocate_route(
|
|
&VALID_CRYPTO_KINDS,
|
|
stability,
|
|
sequencing,
|
|
hop_count,
|
|
directions,
|
|
&[],
|
|
false,
|
|
) {
|
|
Ok(v) => v.to_string(),
|
|
Err(e) => {
|
|
format!("Route allocation failed: {}", e)
|
|
}
|
|
};
|
|
|
|
Ok(out)
|
|
}
|
|
async fn debug_route_release(&self, args: Vec<String>) -> VeilidAPIResult<String> {
|
|
// <route id>
|
|
let netman = self.network_manager()?;
|
|
let routing_table = netman.routing_table();
|
|
let rss = routing_table.route_spec_store();
|
|
|
|
let route_id = get_debug_argument_at(
|
|
&args,
|
|
1,
|
|
"debug_route",
|
|
"route_id",
|
|
get_route_id(rss.clone(), true, true),
|
|
)?;
|
|
|
|
// Release route
|
|
let out = match rss.release_route(route_id) {
|
|
true => {
|
|
// release imported
|
|
let mut inner = self.inner.lock();
|
|
let dc = &mut inner.debug_cache;
|
|
for (n, ir) in dc.imported_routes.iter().enumerate() {
|
|
if *ir == route_id {
|
|
dc.imported_routes.remove(n);
|
|
break;
|
|
}
|
|
}
|
|
"Released".to_owned()
|
|
}
|
|
false => "Route does not exist".to_owned(),
|
|
};
|
|
|
|
Ok(out)
|
|
}
|
|
async fn debug_route_publish(&self, args: Vec<String>) -> VeilidAPIResult<String> {
|
|
// <route id> [full]
|
|
let netman = self.network_manager()?;
|
|
let routing_table = netman.routing_table();
|
|
let rss = routing_table.route_spec_store();
|
|
|
|
let route_id = get_debug_argument_at(
|
|
&args,
|
|
1,
|
|
"debug_route",
|
|
"route_id",
|
|
get_route_id(rss.clone(), true, false),
|
|
)?;
|
|
let full = {
|
|
if args.len() > 2 {
|
|
let full_val = get_debug_argument_at(&args, 2, "debug_route", "full", get_string)?
|
|
.to_ascii_lowercase();
|
|
if full_val == "full" {
|
|
true
|
|
} else {
|
|
apibail_invalid_argument!("debug_route", "full", full_val);
|
|
}
|
|
} else {
|
|
false
|
|
}
|
|
};
|
|
|
|
// Publish route
|
|
let out = match rss.assemble_private_routes(&route_id, Some(!full)) {
|
|
Ok(private_routes) => {
|
|
if let Err(e) = rss.mark_route_published(&route_id, true) {
|
|
return Ok(format!("Couldn't mark route published: {}", e));
|
|
}
|
|
// Convert to blob
|
|
let blob_data = RouteSpecStore::private_routes_to_blob(&private_routes)
|
|
.map_err(VeilidAPIError::internal)?;
|
|
let out = BASE64URL_NOPAD.encode(&blob_data);
|
|
info!(
|
|
"Published route {} as {} bytes:\n{}",
|
|
route_id.encode(),
|
|
blob_data.len(),
|
|
out
|
|
);
|
|
format!("Published route {}", route_id.encode())
|
|
}
|
|
Err(e) => {
|
|
format!("Couldn't assemble private route: {}", e)
|
|
}
|
|
};
|
|
|
|
Ok(out)
|
|
}
|
|
async fn debug_route_unpublish(&self, args: Vec<String>) -> VeilidAPIResult<String> {
|
|
// <route id>
|
|
let netman = self.network_manager()?;
|
|
let routing_table = netman.routing_table();
|
|
let rss = routing_table.route_spec_store();
|
|
|
|
let route_id = get_debug_argument_at(
|
|
&args,
|
|
1,
|
|
"debug_route",
|
|
"route_id",
|
|
get_route_id(rss.clone(), true, false),
|
|
)?;
|
|
|
|
// Unpublish route
|
|
let out = if let Err(e) = rss.mark_route_published(&route_id, false) {
|
|
return Ok(format!("Couldn't mark route unpublished: {}", e));
|
|
} else {
|
|
"Route unpublished".to_owned()
|
|
};
|
|
Ok(out)
|
|
}
|
|
async fn debug_route_print(&self, args: Vec<String>) -> VeilidAPIResult<String> {
|
|
// <route id>
|
|
let netman = self.network_manager()?;
|
|
let routing_table = netman.routing_table();
|
|
let rss = routing_table.route_spec_store();
|
|
|
|
let route_id = get_debug_argument_at(
|
|
&args,
|
|
1,
|
|
"debug_route",
|
|
"route_id",
|
|
get_route_id(rss.clone(), true, true),
|
|
)?;
|
|
|
|
match rss.debug_route(&route_id) {
|
|
Some(s) => Ok(s),
|
|
None => Ok("Route does not exist".to_owned()),
|
|
}
|
|
}
|
|
async fn debug_route_list(&self, _args: Vec<String>) -> VeilidAPIResult<String> {
|
|
//
|
|
let netman = self.network_manager()?;
|
|
let routing_table = netman.routing_table();
|
|
let rss = routing_table.route_spec_store();
|
|
|
|
let routes = rss.list_allocated_routes(|k, _| Some(*k));
|
|
let mut out = format!("Allocated Routes: (count = {}):\n", routes.len());
|
|
for r in routes {
|
|
out.push_str(&format!("{}\n", r.encode()));
|
|
}
|
|
|
|
let remote_routes = rss.list_remote_routes(|k, _| Some(*k));
|
|
out.push_str(&format!(
|
|
"Remote Routes: (count = {}):\n",
|
|
remote_routes.len()
|
|
));
|
|
for r in remote_routes {
|
|
out.push_str(&format!("{}\n", r.encode()));
|
|
}
|
|
|
|
Ok(out)
|
|
}
|
|
async fn debug_route_import(&self, args: Vec<String>) -> VeilidAPIResult<String> {
|
|
// <blob>
|
|
|
|
let blob = get_debug_argument_at(&args, 1, "debug_route", "blob", get_string)?;
|
|
let blob_dec = BASE64URL_NOPAD
|
|
.decode(blob.as_bytes())
|
|
.map_err(VeilidAPIError::generic)?;
|
|
let rss = self.routing_table()?.route_spec_store();
|
|
let route_id = rss
|
|
.import_remote_private_route_blob(blob_dec)
|
|
.map_err(VeilidAPIError::generic)?;
|
|
|
|
let mut inner = self.inner.lock();
|
|
let dc = &mut inner.debug_cache;
|
|
let n = dc.imported_routes.len();
|
|
let out = format!("Private route #{} imported: {}", n, route_id);
|
|
dc.imported_routes.push(route_id);
|
|
|
|
Ok(out)
|
|
}
|
|
|
|
async fn debug_route_test(&self, args: Vec<String>) -> VeilidAPIResult<String> {
|
|
// <route id>
|
|
let netman = self.network_manager()?;
|
|
let routing_table = netman.routing_table();
|
|
let rss = routing_table.route_spec_store();
|
|
|
|
let route_id = get_debug_argument_at(
|
|
&args,
|
|
1,
|
|
"debug_route",
|
|
"route_id",
|
|
get_route_id(rss.clone(), true, true),
|
|
)?;
|
|
|
|
let success = rss
|
|
.test_route(route_id)
|
|
.await
|
|
.map_err(VeilidAPIError::internal)?;
|
|
|
|
let out = match success {
|
|
Some(true) => "SUCCESS".to_owned(),
|
|
Some(false) => "FAILED".to_owned(),
|
|
None => "UNTESTED".to_owned(),
|
|
};
|
|
|
|
Ok(out)
|
|
}
|
|
|
|
async fn debug_route(&self, args: String) -> VeilidAPIResult<String> {
|
|
let args: Vec<String> = args.split_whitespace().map(|s| s.to_owned()).collect();
|
|
|
|
let command = get_debug_argument_at(&args, 0, "debug_route", "command", get_string)?;
|
|
|
|
if command == "allocate" {
|
|
self.debug_route_allocate(args).await
|
|
} else if command == "release" {
|
|
self.debug_route_release(args).await
|
|
} else if command == "publish" {
|
|
self.debug_route_publish(args).await
|
|
} else if command == "unpublish" {
|
|
self.debug_route_unpublish(args).await
|
|
} else if command == "print" {
|
|
self.debug_route_print(args).await
|
|
} else if command == "list" {
|
|
self.debug_route_list(args).await
|
|
} else if command == "import" {
|
|
self.debug_route_import(args).await
|
|
} else if command == "test" {
|
|
self.debug_route_test(args).await
|
|
} else {
|
|
Ok(">>> Unknown command\n".to_owned())
|
|
}
|
|
}
|
|
|
|
async fn debug_record_list(&self, args: Vec<String>) -> VeilidAPIResult<String> {
|
|
// <local|remote>
|
|
let storage_manager = self.storage_manager()?;
|
|
|
|
let scope = get_debug_argument_at(&args, 1, "debug_record_list", "scope", get_string)?;
|
|
let out = match scope.as_str() {
|
|
"local" => {
|
|
let mut out = "Local Records:\n".to_string();
|
|
out += &storage_manager.debug_local_records().await;
|
|
out
|
|
}
|
|
"remote" => {
|
|
let mut out = "Remote Records:\n".to_string();
|
|
out += &storage_manager.debug_remote_records().await;
|
|
out
|
|
}
|
|
"opened" => {
|
|
let mut out = "Opened Records:\n".to_string();
|
|
out += &storage_manager.debug_opened_records().await;
|
|
out
|
|
}
|
|
"offline" => {
|
|
let mut out = "Offline Records:\n".to_string();
|
|
out += &storage_manager.debug_offline_records().await;
|
|
out
|
|
}
|
|
_ => "Invalid scope\n".to_owned(),
|
|
};
|
|
Ok(out)
|
|
}
|
|
|
|
async fn debug_record_purge(&self, args: Vec<String>) -> VeilidAPIResult<String> {
|
|
// <local|remote> [bytes]
|
|
let storage_manager = self.storage_manager()?;
|
|
|
|
let scope = get_debug_argument_at(&args, 1, "debug_record_purge", "scope", get_string)?;
|
|
let bytes = get_debug_argument_at(&args, 2, "debug_record_purge", "bytes", get_number).ok();
|
|
let out = match scope.as_str() {
|
|
"local" => storage_manager.purge_local_records(bytes).await,
|
|
"remote" => storage_manager.purge_remote_records(bytes).await,
|
|
_ => "Invalid scope\n".to_owned(),
|
|
};
|
|
Ok(out)
|
|
}
|
|
|
|
async fn debug_record_create(&self, args: Vec<String>) -> VeilidAPIResult<String> {
|
|
let netman = self.network_manager()?;
|
|
let routing_table = netman.routing_table();
|
|
let crypto = self.crypto()?;
|
|
|
|
let schema = get_debug_argument_at(
|
|
&args,
|
|
1,
|
|
"debug_record_create",
|
|
"dht_schema",
|
|
get_dht_schema,
|
|
)
|
|
.unwrap_or_else(|_| Ok(DHTSchema::default()))?;
|
|
|
|
let csv = get_debug_argument_at(
|
|
&args,
|
|
2,
|
|
"debug_record_create",
|
|
"kind",
|
|
get_crypto_system_version(crypto.clone()),
|
|
)
|
|
.unwrap_or_else(|_| crypto.best());
|
|
|
|
let ss = get_debug_argument_at(
|
|
&args,
|
|
3,
|
|
"debug_record_create",
|
|
"safety_selection",
|
|
get_safety_selection(routing_table),
|
|
)
|
|
.ok();
|
|
|
|
// Get routing context with optional safety
|
|
let rc = self.routing_context()?;
|
|
let rc = if let Some(ss) = ss {
|
|
match rc.with_safety(ss) {
|
|
Err(e) => return Ok(format!("Can't use safety selection: {}", e)),
|
|
Ok(v) => v,
|
|
}
|
|
} else {
|
|
rc
|
|
};
|
|
|
|
// Do a record create
|
|
let record = match rc.create_dht_record(schema, Some(csv.kind())).await {
|
|
Err(e) => return Ok(format!("Can't open DHT record: {}", e)),
|
|
Ok(v) => v,
|
|
};
|
|
|
|
// Save routing context for record
|
|
let mut inner = self.inner.lock();
|
|
let dc = &mut inner.debug_cache;
|
|
dc.opened_record_contexts.insert(*record.key(), rc);
|
|
|
|
Ok(format!(
|
|
"Created: {} {}:{}\n{:?}",
|
|
record.key(),
|
|
record.owner(),
|
|
record.owner_secret().unwrap(),
|
|
record
|
|
))
|
|
}
|
|
|
|
async fn debug_record_open(&self, args: Vec<String>) -> VeilidAPIResult<String> {
|
|
let netman = self.network_manager()?;
|
|
let routing_table = netman.routing_table();
|
|
|
|
let (key, ss) = get_debug_argument_at(
|
|
&args,
|
|
1,
|
|
"debug_record_open",
|
|
"key",
|
|
get_dht_key(routing_table),
|
|
)?;
|
|
let writer =
|
|
get_debug_argument_at(&args, 2, "debug_record_open", "writer", get_keypair).ok();
|
|
|
|
// Get routing context with optional safety
|
|
let rc = self.routing_context()?;
|
|
let rc = if let Some(ss) = ss {
|
|
match rc.with_safety(ss) {
|
|
Err(e) => return Ok(format!("Can't use safety selection: {}", e)),
|
|
Ok(v) => v,
|
|
}
|
|
} else {
|
|
rc
|
|
};
|
|
|
|
// Do a record open
|
|
let record = match rc.open_dht_record(key, writer).await {
|
|
Err(e) => return Ok(format!("Can't open DHT record: {}", e)),
|
|
Ok(v) => v,
|
|
};
|
|
|
|
// Save routing context for record
|
|
let mut inner = self.inner.lock();
|
|
let dc = &mut inner.debug_cache;
|
|
dc.opened_record_contexts.insert(*record.key(), rc);
|
|
|
|
Ok(format!("Opened: {} : {:?}", key, record))
|
|
}
|
|
|
|
async fn debug_record_close(&self, args: Vec<String>) -> VeilidAPIResult<String> {
|
|
let (key, rc) =
|
|
self.clone()
|
|
.get_opened_dht_record_context(&args, "debug_record_close", "key", 1)?;
|
|
|
|
// Do a record close
|
|
if let Err(e) = rc.close_dht_record(key).await {
|
|
return Ok(format!("Can't close DHT record: {}", e));
|
|
};
|
|
|
|
Ok(format!("Closed: {:?}", key))
|
|
}
|
|
|
|
async fn debug_record_set(&self, args: Vec<String>) -> VeilidAPIResult<String> {
|
|
let opt_arg_add = if args.len() >= 2 && get_dht_key_no_safety(&args[1]).is_some() {
|
|
1
|
|
} else {
|
|
0
|
|
};
|
|
let (key, rc) =
|
|
self.clone()
|
|
.get_opened_dht_record_context(&args, "debug_record_set", "key", 1)?;
|
|
let subkey = get_debug_argument_at(
|
|
&args,
|
|
1 + opt_arg_add,
|
|
"debug_record_set",
|
|
"subkey",
|
|
get_number::<u32>,
|
|
)?;
|
|
let data =
|
|
get_debug_argument_at(&args, 2 + opt_arg_add, "debug_record_set", "data", get_data)?;
|
|
let writer = get_debug_argument_at(
|
|
&args,
|
|
3 + opt_arg_add,
|
|
"debug_record_set",
|
|
"writer",
|
|
get_keypair,
|
|
)
|
|
.ok();
|
|
|
|
// Do a record set
|
|
let value = match rc
|
|
.set_dht_value(key, subkey as ValueSubkey, data, writer)
|
|
.await
|
|
{
|
|
Err(e) => {
|
|
return Ok(format!("Can't set DHT value: {}", e));
|
|
}
|
|
Ok(v) => v,
|
|
};
|
|
let out = if let Some(value) = value {
|
|
format!("Newer value found: {:?}", value)
|
|
} else {
|
|
"Success".to_owned()
|
|
};
|
|
Ok(out)
|
|
}
|
|
|
|
async fn debug_record_get(&self, args: Vec<String>) -> VeilidAPIResult<String> {
|
|
let opt_arg_add = if args.len() >= 2 && get_dht_key_no_safety(&args[1]).is_some() {
|
|
1
|
|
} else {
|
|
0
|
|
};
|
|
|
|
let (key, rc) =
|
|
self.clone()
|
|
.get_opened_dht_record_context(&args, "debug_record_get", "key", 1)?;
|
|
let subkey = get_debug_argument_at(
|
|
&args,
|
|
1 + opt_arg_add,
|
|
"debug_record_get",
|
|
"subkey",
|
|
get_number::<u32>,
|
|
)?;
|
|
let force_refresh = if args.len() >= 3 + opt_arg_add {
|
|
Some(get_debug_argument_at(
|
|
&args,
|
|
2 + opt_arg_add,
|
|
"debug_record_get",
|
|
"force_refresh",
|
|
get_string,
|
|
)?)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let force_refresh = if let Some(force_refresh) = force_refresh {
|
|
if &force_refresh == "force" {
|
|
true
|
|
} else {
|
|
return Ok(format!("Unknown force: {}", force_refresh));
|
|
}
|
|
} else {
|
|
false
|
|
};
|
|
|
|
// Do a record get
|
|
let value = match rc
|
|
.get_dht_value(key, subkey as ValueSubkey, force_refresh)
|
|
.await
|
|
{
|
|
Err(e) => {
|
|
return Ok(format!("Can't get DHT value: {}", e));
|
|
}
|
|
Ok(v) => v,
|
|
};
|
|
let out = if let Some(value) = value {
|
|
format!("{:?}", value)
|
|
} else {
|
|
"No value data returned".to_owned()
|
|
};
|
|
Ok(out)
|
|
}
|
|
|
|
async fn debug_record_delete(&self, args: Vec<String>) -> VeilidAPIResult<String> {
|
|
let key = get_debug_argument_at(
|
|
&args,
|
|
1,
|
|
"debug_record_delete",
|
|
"key",
|
|
get_dht_key_no_safety,
|
|
)?;
|
|
|
|
// Do a record delete (can use any routing context here)
|
|
let rc = self.routing_context()?;
|
|
match rc.delete_dht_record(key).await {
|
|
Err(e) => return Ok(format!("Can't delete DHT record: {}", e)),
|
|
Ok(v) => v,
|
|
};
|
|
Ok("DHT record deleted".to_string())
|
|
}
|
|
|
|
async fn debug_record_info(&self, args: Vec<String>) -> VeilidAPIResult<String> {
|
|
let storage_manager = self.storage_manager()?;
|
|
|
|
let key =
|
|
get_debug_argument_at(&args, 1, "debug_record_info", "key", get_dht_key_no_safety)?;
|
|
|
|
let subkey = get_debug_argument_at(
|
|
&args,
|
|
2,
|
|
"debug_record_info",
|
|
"subkey",
|
|
get_number::<ValueSubkey>,
|
|
)
|
|
.ok();
|
|
|
|
let out = if let Some(subkey) = subkey {
|
|
let li = storage_manager
|
|
.debug_local_record_subkey_info(key, subkey)
|
|
.await;
|
|
let ri = storage_manager
|
|
.debug_remote_record_subkey_info(key, subkey)
|
|
.await;
|
|
format!(
|
|
"Local Subkey Info:\n{}\n\nRemote Subkey Info:\n{}\n",
|
|
li, ri
|
|
)
|
|
} else {
|
|
let li = storage_manager.debug_local_record_info(key).await;
|
|
let ri = storage_manager.debug_remote_record_info(key).await;
|
|
format!("Local Info:\n{}\n\nRemote Info:\n{}\n", li, ri)
|
|
};
|
|
Ok(out)
|
|
}
|
|
|
|
async fn debug_record_watch(&self, args: Vec<String>) -> VeilidAPIResult<String> {
|
|
let opt_arg_add = if args.len() >= 2 && get_dht_key_no_safety(&args[1]).is_some() {
|
|
1
|
|
} else {
|
|
0
|
|
};
|
|
|
|
let (key, rc) =
|
|
self.clone()
|
|
.get_opened_dht_record_context(&args, "debug_record_watch", "key", 1)?;
|
|
|
|
let mut rest_defaults = false;
|
|
let subkeys = get_debug_argument_at(
|
|
&args,
|
|
1 + opt_arg_add,
|
|
"debug_record_watch",
|
|
"subkeys",
|
|
get_subkeys,
|
|
)
|
|
.ok()
|
|
.unwrap_or_else(|| {
|
|
rest_defaults = true;
|
|
Default::default()
|
|
});
|
|
|
|
let expiration = if rest_defaults {
|
|
Default::default()
|
|
} else {
|
|
get_debug_argument_at(
|
|
&args,
|
|
2 + opt_arg_add,
|
|
"debug_record_watch",
|
|
"expiration",
|
|
parse_duration,
|
|
)
|
|
.ok()
|
|
.map(|dur| dur + get_timestamp())
|
|
.unwrap_or_else(|| {
|
|
rest_defaults = true;
|
|
Default::default()
|
|
})
|
|
};
|
|
let count = if rest_defaults {
|
|
u32::MAX
|
|
} else {
|
|
get_debug_argument_at(
|
|
&args,
|
|
3 + opt_arg_add,
|
|
"debug_record_watch",
|
|
"count",
|
|
get_number,
|
|
)
|
|
.ok()
|
|
.unwrap_or_else(|| {
|
|
rest_defaults = true;
|
|
u32::MAX
|
|
})
|
|
};
|
|
|
|
// Do a record watch
|
|
let ts = match rc
|
|
.watch_dht_values(key, subkeys, Timestamp::new(expiration), count)
|
|
.await
|
|
{
|
|
Err(e) => {
|
|
return Ok(format!("Can't watch DHT value: {}", e));
|
|
}
|
|
Ok(v) => v,
|
|
};
|
|
if ts.as_u64() == 0 {
|
|
return Ok("Failed to watch value".to_owned());
|
|
}
|
|
Ok(format!("Success: expiration={:?}", display_ts(ts.as_u64())))
|
|
}
|
|
|
|
async fn debug_record_cancel(&self, args: Vec<String>) -> VeilidAPIResult<String> {
|
|
let opt_arg_add = if args.len() >= 2 && get_dht_key_no_safety(&args[1]).is_some() {
|
|
1
|
|
} else {
|
|
0
|
|
};
|
|
|
|
let (key, rc) =
|
|
self.clone()
|
|
.get_opened_dht_record_context(&args, "debug_record_watch", "key", 1)?;
|
|
let subkeys = get_debug_argument_at(
|
|
&args,
|
|
1 + opt_arg_add,
|
|
"debug_record_watch",
|
|
"subkeys",
|
|
get_subkeys,
|
|
)
|
|
.ok()
|
|
.unwrap_or_default();
|
|
|
|
// Do a record watch cancel
|
|
let still_active = match rc.cancel_dht_watch(key, subkeys).await {
|
|
Err(e) => {
|
|
return Ok(format!("Can't cancel DHT watch: {}", e));
|
|
}
|
|
Ok(v) => v,
|
|
};
|
|
|
|
Ok(if still_active {
|
|
"Watch partially cancelled".to_owned()
|
|
} else {
|
|
"Watch cancelled".to_owned()
|
|
})
|
|
}
|
|
|
|
async fn debug_record_inspect(&self, args: Vec<String>) -> VeilidAPIResult<String> {
|
|
let opt_arg_add = if args.len() >= 2 && get_dht_key_no_safety(&args[1]).is_some() {
|
|
1
|
|
} else {
|
|
0
|
|
};
|
|
|
|
let (key, rc) =
|
|
self.clone()
|
|
.get_opened_dht_record_context(&args, "debug_record_watch", "key", 1)?;
|
|
|
|
let mut rest_defaults = false;
|
|
|
|
let scope = if rest_defaults {
|
|
Default::default()
|
|
} else {
|
|
get_debug_argument_at(
|
|
&args,
|
|
1 + opt_arg_add,
|
|
"debug_record_inspect",
|
|
"scope",
|
|
get_dht_report_scope,
|
|
)
|
|
.ok()
|
|
.unwrap_or_else(|| {
|
|
rest_defaults = true;
|
|
Default::default()
|
|
})
|
|
};
|
|
|
|
let subkeys = get_debug_argument_at(
|
|
&args,
|
|
2 + opt_arg_add,
|
|
"debug_record_inspect",
|
|
"subkeys",
|
|
get_subkeys,
|
|
)
|
|
.ok()
|
|
.unwrap_or_else(|| {
|
|
rest_defaults = true;
|
|
Default::default()
|
|
});
|
|
|
|
// Do a record inspect
|
|
let report = match rc.inspect_dht_record(key, subkeys, scope).await {
|
|
Err(e) => {
|
|
return Ok(format!("Can't inspect DHT record: {}", e));
|
|
}
|
|
Ok(v) => v,
|
|
};
|
|
|
|
Ok(format!("Success: report={:?}", report))
|
|
}
|
|
|
|
async fn debug_record(&self, args: String) -> VeilidAPIResult<String> {
|
|
let args: Vec<String> =
|
|
shell_words::split(&args).map_err(|e| VeilidAPIError::parse_error(e, args))?;
|
|
|
|
let command = get_debug_argument_at(&args, 0, "debug_record", "command", get_string)?;
|
|
|
|
if command == "list" {
|
|
self.debug_record_list(args).await
|
|
} else if command == "purge" {
|
|
self.debug_record_purge(args).await
|
|
} else if command == "create" {
|
|
self.debug_record_create(args).await
|
|
} else if command == "open" {
|
|
self.debug_record_open(args).await
|
|
} else if command == "close" {
|
|
self.debug_record_close(args).await
|
|
} else if command == "get" {
|
|
self.debug_record_get(args).await
|
|
} else if command == "set" {
|
|
self.debug_record_set(args).await
|
|
} else if command == "delete" {
|
|
self.debug_record_delete(args).await
|
|
} else if command == "info" {
|
|
self.debug_record_info(args).await
|
|
} else if command == "watch" {
|
|
self.debug_record_watch(args).await
|
|
} else if command == "cancel" {
|
|
self.debug_record_cancel(args).await
|
|
} else if command == "inspect" {
|
|
self.debug_record_inspect(args).await
|
|
} else {
|
|
Ok(">>> Unknown command\n".to_owned())
|
|
}
|
|
}
|
|
|
|
async fn debug_table_list(&self, _args: Vec<String>) -> VeilidAPIResult<String> {
|
|
//
|
|
let table_store = self.table_store()?;
|
|
let table_names = table_store.list_all();
|
|
let out = format!(
|
|
"TableStore tables:\n{}",
|
|
table_names
|
|
.iter()
|
|
.map(|(k, v)| format!("{} ({})", k, v))
|
|
.collect::<Vec<String>>()
|
|
.join("\n")
|
|
);
|
|
Ok(out)
|
|
}
|
|
|
|
fn _format_columns(columns: &[table_store::ColumnInfo]) -> String {
|
|
let mut out = String::new();
|
|
for (n, col) in columns.iter().enumerate() {
|
|
//
|
|
out += &format!("Column {}:\n", n);
|
|
out += &format!(" Key Count: {}\n", col.key_count);
|
|
}
|
|
out
|
|
}
|
|
|
|
async fn debug_table_info(&self, args: Vec<String>) -> VeilidAPIResult<String> {
|
|
//
|
|
let table_store = self.table_store()?;
|
|
|
|
let table_name = get_debug_argument_at(&args, 1, "debug_table_info", "name", get_string)?;
|
|
|
|
let Some(info) = table_store.info(&table_name).await? else {
|
|
return Ok(format!("Table '{}' does not exist", table_name));
|
|
};
|
|
|
|
let info_str = format!(
|
|
"Table Name: {}\n\
|
|
Column Count: {}\n\
|
|
IO Stats (since previous query):\n{}\n\
|
|
IO Stats (overall):\n{}\n\
|
|
Columns:\n{}\n",
|
|
info.table_name,
|
|
info.column_count,
|
|
indent::indent_all_by(4, format!("{:#?}", info.io_stats_since_previous)),
|
|
indent::indent_all_by(4, format!("{:#?}", info.io_stats_overall)),
|
|
Self::_format_columns(&info.columns),
|
|
);
|
|
|
|
let out = format!("Table info for '{}':\n{}", table_name, info_str);
|
|
Ok(out)
|
|
}
|
|
|
|
async fn debug_table(&self, args: String) -> VeilidAPIResult<String> {
|
|
let args: Vec<String> =
|
|
shell_words::split(&args).map_err(|e| VeilidAPIError::parse_error(e, args))?;
|
|
|
|
let command = get_debug_argument_at(&args, 0, "debug_table", "command", get_string)?;
|
|
|
|
if command == "list" {
|
|
self.debug_table_list(args).await
|
|
} else if command == "info" {
|
|
self.debug_table_info(args).await
|
|
} else {
|
|
Ok(">>> Unknown command\n".to_owned())
|
|
}
|
|
}
|
|
|
|
async fn debug_punish_list(&self, _args: Vec<String>) -> VeilidAPIResult<String> {
|
|
//
|
|
let network_manager = self.network_manager()?;
|
|
let address_filter = network_manager.address_filter();
|
|
|
|
let out = format!("Address filter punishments:\n{:#?}", address_filter);
|
|
Ok(out)
|
|
}
|
|
|
|
async fn debug_punish_clear(&self, _args: Vec<String>) -> VeilidAPIResult<String> {
|
|
//
|
|
let network_manager = self.network_manager()?;
|
|
let address_filter = network_manager.address_filter();
|
|
|
|
address_filter.clear_punishments();
|
|
|
|
Ok("Address Filter punishments cleared\n".to_owned())
|
|
}
|
|
|
|
async fn debug_punish(&self, args: String) -> VeilidAPIResult<String> {
|
|
let args: Vec<String> =
|
|
shell_words::split(&args).map_err(|e| VeilidAPIError::parse_error(e, args))?;
|
|
|
|
let command = get_debug_argument_at(&args, 0, "debug_punish", "command", get_string)?;
|
|
|
|
if command == "list" {
|
|
self.debug_punish_list(args).await
|
|
} else if command == "clear" {
|
|
self.debug_punish_clear(args).await
|
|
} else {
|
|
Ok(">>> Unknown command\n".to_owned())
|
|
}
|
|
}
|
|
|
|
/// Get the help text for 'internal debug' commands.
|
|
pub async fn debug_help(&self, _args: String) -> VeilidAPIResult<String> {
|
|
Ok(r#"Node Information:
|
|
nodeid - display a node's id(s)
|
|
nodeinfo - display detailed information about this node
|
|
dialinfo - display the dialinfo in the routing domains of this node
|
|
peerinfo [routingdomain] [published|current] - display the full PeerInfo for a routing domain of this node
|
|
uptime - display node uptime
|
|
|
|
Routing:
|
|
buckets [dead|reliable] - Display the routing table bucket statistics (default is only non-dead nodes)
|
|
entries [dead|reliable] [<capabilities>] - Display the index of nodes in the routing table
|
|
entry <node> - Display all the details about a particular node in the routing table
|
|
contact <node>[+<sequencing>][<modifiers>] - Explain what mechanism would be used to contact a particular node
|
|
resolve <destination> - Search the network for a particular node or private route
|
|
relay <relay> [public|local] - Change the relay in use for this node
|
|
punish list - List all punishments this node has assigned to other nodes / networks
|
|
clear - Clear all punishments from this node
|
|
route allocate [<sequencing>] [rel] [<count>] [in|out] - Allocate a route
|
|
release <route> - Release a route
|
|
publish <route> [full] - Publish a route 'blob' that can be imported on another machine
|
|
unpublish <route> - Mark a route as 'no longer published'
|
|
print <route> - Display details about a route
|
|
list - List allocated routes
|
|
import <blob> - Import a remote route blob generated by another node's 'publish' command.
|
|
test <route> - Test an allocated or imported remote route
|
|
|
|
Utilities:
|
|
config [insecure] [configkey [new value]] - Display or temporarily change the node config
|
|
(most values should not be changed this way, careful!)
|
|
txtrecord - Generate a TXT record for making this node into a bootstrap node capable of DNS bootstrap
|
|
keypair [cryptokind] - Generate and display a random public/private keypair
|
|
purge <buckets|connections|routes> - Throw away the node's routing table, connections, or routes
|
|
attach - Attach the node to the network if it is detached
|
|
detach - Detach the node from the network if it is attached
|
|
restart network - Restart the low level network
|
|
|
|
RPC Operations:
|
|
ping <destination> - Send a 'Status' RPC question to a destination node and display the returned ping status
|
|
appmessage <destination> <data> - Send an 'App Message' RPC statement to a destination node
|
|
appcall <destination> <data> - Send a 'App Call' RPC question to a destination node and display the answer
|
|
appreply [#id] <data> - Reply to an 'App Call' RPC received by this node
|
|
|
|
DHT Operations:
|
|
record list <local|remote|opened|offline> - display the dht records in the store
|
|
purge <local|remote> [bytes] - clear all dht records optionally down to some total size
|
|
create <dhtschema> [<cryptokind> [<safety>]] - create a new dht record
|
|
open <key>[+<safety>] [<writer>] - open an existing dht record
|
|
close [<key>] - close an opened/created dht record
|
|
set [<key>] <subkey> <data> - write a value to a dht record subkey
|
|
get [<key>] <subkey> [force] - read a value from a dht record subkey
|
|
delete <key> - delete the local copy of a dht record (not from the network)
|
|
info [<key>] [subkey] - display information about a dht record or subkey
|
|
watch [<key>] [<subkeys> [<expiration> [<count>]]] - watch a record for changes
|
|
cancel [<key>] [<subkeys>] - cancel a dht record watch
|
|
inspect [<key>] [<scope> [<subkeys>]] - display a dht record's subkey status
|
|
|
|
TableDB Operations:
|
|
table list - list the names of all the tables in the TableDB
|
|
|
|
--------------------------------------------------------------------
|
|
<key> is: VLD0:GsgXCRPrzSK6oBNgxhNpm-rTYFd02R0ySx6j9vbQBG4
|
|
* also <node>, <relay>, <target>, <route>
|
|
<capabilities> is: a list of FourCC codes: ROUT,SGNL,RLAY,DIAL,DHTV,DHTW,APPM etc.
|
|
<configkey> is: dot path like network.protocol.udp.enabled
|
|
<destination> is:
|
|
* direct: <node>[+<safety>][<modifiers>]
|
|
* relay: <relay>@<target>[+<safety>][<modifiers>]
|
|
* private: #<id>[+<safety>]
|
|
<sequencing> is:
|
|
* prefer_ordered: ord
|
|
* ensure_ordered: *ord
|
|
<safety> is:
|
|
* unsafe: -<sequencing>
|
|
* safe: [route][,<sequencing>][,rel][,<count>]
|
|
<modifiers> is: [/<protocoltype>][/<addresstype>][/<routingdomain>]
|
|
<protocoltype> is: udp|tcp|ws|wss
|
|
<addresstype> is: ipv4|ipv6
|
|
<routingdomain> is: public|local
|
|
<cryptokind> is: VLD0
|
|
<dhtschema> is:
|
|
* a single-quoted json dht schema, or
|
|
* an integer number for a DFLT schema subkey count.
|
|
default is '{"kind":"DFLT","o_cnt":1}'
|
|
<scope> is: local, syncget, syncset, updateget, updateset
|
|
<subkey> is: a number: 2
|
|
<subkeys> is:
|
|
* a number: 2
|
|
* a comma-separated inclusive range list: 1..=3,5..=8
|
|
<data> is:
|
|
* a single-word string: foobar
|
|
* a shell-quoted string: "foo\nbar\n"
|
|
* a '#' followed by hex data: #12AB34CD...
|
|
"#
|
|
.to_owned())
|
|
}
|
|
|
|
/// Get node uptime info.
|
|
pub async fn debug_uptime(&self, _args: String) -> VeilidAPIResult<String> {
|
|
let mut result = String::new();
|
|
|
|
writeln!(result, "Uptime...").ok();
|
|
|
|
let state = self.get_state().await?;
|
|
|
|
let uptime = state.attachment.uptime;
|
|
writeln!(result, " since launch: {uptime}").ok();
|
|
|
|
if let Some(attached_uptime) = state.attachment.attached_uptime {
|
|
writeln!(result, " since attachment: {attached_uptime}").ok();
|
|
}
|
|
|
|
Ok(result)
|
|
}
|
|
|
|
/// Execute an 'internal debug command'.
|
|
pub async fn debug(&self, args: String) -> VeilidAPIResult<String> {
|
|
let res = {
|
|
let args = args.trim_start();
|
|
if args.is_empty() {
|
|
// No arguments runs help command
|
|
return self.debug_help("".to_owned()).await;
|
|
}
|
|
let (arg, rest) = args.split_once(' ').unwrap_or((args, ""));
|
|
let rest = rest.trim_start().to_owned();
|
|
|
|
if arg == "help" {
|
|
self.debug_help(rest).await
|
|
} else if arg == "nodeid" {
|
|
self.debug_nodeid(rest).await
|
|
} else if arg == "buckets" {
|
|
self.debug_buckets(rest).await
|
|
} else if arg == "dialinfo" {
|
|
self.debug_dialinfo(rest).await
|
|
} else if arg == "peerinfo" {
|
|
self.debug_peerinfo(rest).await
|
|
} else if arg == "txtrecord" {
|
|
self.debug_txtrecord(rest).await
|
|
} else if arg == "keypair" {
|
|
self.debug_keypair(rest).await
|
|
} else if arg == "entries" {
|
|
self.debug_entries(rest).await
|
|
} else if arg == "entry" {
|
|
self.debug_entry(rest).await
|
|
} else if arg == "relay" {
|
|
self.debug_relay(rest).await
|
|
} else if arg == "ping" {
|
|
self.debug_ping(rest).await
|
|
} else if arg == "appmessage" {
|
|
self.debug_app_message(rest).await
|
|
} else if arg == "appcall" {
|
|
self.debug_app_call(rest).await
|
|
} else if arg == "appreply" {
|
|
self.debug_app_reply(rest).await
|
|
} else if arg == "resolve" {
|
|
self.debug_resolve(rest).await
|
|
} else if arg == "contact" {
|
|
self.debug_contact(rest).await
|
|
} else if arg == "nodeinfo" {
|
|
self.debug_nodeinfo(rest).await
|
|
} else if arg == "purge" {
|
|
self.debug_purge(rest).await
|
|
} else if arg == "attach" {
|
|
self.debug_attach(rest).await
|
|
} else if arg == "detach" {
|
|
self.debug_detach(rest).await
|
|
} else if arg == "config" {
|
|
self.debug_config(rest).await
|
|
} else if arg == "restart" {
|
|
self.debug_restart(rest).await
|
|
} else if arg == "route" {
|
|
self.debug_route(rest).await
|
|
} else if arg == "record" {
|
|
self.debug_record(rest).await
|
|
} else if arg == "punish" {
|
|
self.debug_punish(rest).await
|
|
} else if arg == "table" {
|
|
self.debug_table(rest).await
|
|
} else if arg == "uptime" {
|
|
self.debug_uptime(rest).await
|
|
} else {
|
|
Err(VeilidAPIError::generic("Unknown debug command"))
|
|
}
|
|
};
|
|
res
|
|
}
|
|
|
|
fn get_destination(
|
|
self,
|
|
routing_table: RoutingTable,
|
|
) -> impl FnOnce(&str) -> SendPinBoxFuture<Option<Destination>> {
|
|
move |text| {
|
|
let text = text.to_owned();
|
|
Box::pin(async move {
|
|
// Safety selection
|
|
let (text, ss) = if let Some((first, second)) = text.split_once('+') {
|
|
let ss = get_safety_selection(routing_table.clone())(second)?;
|
|
(first, Some(ss))
|
|
} else {
|
|
(text.as_str(), None)
|
|
};
|
|
if text.is_empty() {
|
|
return None;
|
|
}
|
|
if &text[0..1] == "#" {
|
|
let rss = routing_table.route_spec_store();
|
|
|
|
// Private route
|
|
let text = &text[1..];
|
|
|
|
let private_route =
|
|
if let Some(prid) = get_route_id(rss.clone(), false, true)(text) {
|
|
rss.best_remote_private_route(&prid)?
|
|
} else {
|
|
let mut inner = self.inner.lock();
|
|
let dc = &mut inner.debug_cache;
|
|
let n = get_number(text)?;
|
|
let prid = *dc.imported_routes.get(n)?;
|
|
let Some(private_route) = rss.best_remote_private_route(&prid) else {
|
|
// Remove imported route
|
|
dc.imported_routes.remove(n);
|
|
info!("removed dead imported route {}", n);
|
|
return None;
|
|
};
|
|
private_route
|
|
};
|
|
|
|
Some(Destination::private_route(
|
|
private_route,
|
|
ss.unwrap_or(SafetySelection::Unsafe(Sequencing::default())),
|
|
))
|
|
} else if let Some((first, second)) = text.split_once('@') {
|
|
// Relay
|
|
let relay_nr = resolve_filtered_node_ref(
|
|
routing_table.clone(),
|
|
ss.unwrap_or_default(),
|
|
)(second)
|
|
.await?;
|
|
let target_nr = get_node_ref(routing_table)(first)?;
|
|
|
|
let mut d = Destination::relay(relay_nr, target_nr);
|
|
if let Some(ss) = ss {
|
|
d = d.with_safety(ss)
|
|
}
|
|
|
|
Some(d)
|
|
} else {
|
|
// Direct
|
|
let target_nr =
|
|
resolve_filtered_node_ref(routing_table, ss.unwrap_or_default())(text)
|
|
.await?;
|
|
|
|
let mut d = Destination::direct(target_nr);
|
|
if let Some(ss) = ss {
|
|
d = d.with_safety(ss)
|
|
}
|
|
|
|
Some(d)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
fn get_opened_dht_record_context(
|
|
self,
|
|
args: &[String],
|
|
context: &str,
|
|
key: &str,
|
|
arg: usize,
|
|
) -> VeilidAPIResult<(TypedKey, RoutingContext)> {
|
|
let mut inner = self.inner.lock();
|
|
let dc = &mut inner.debug_cache;
|
|
|
|
let key = match get_debug_argument_at(args, arg, context, key, get_dht_key_no_safety)
|
|
.ok()
|
|
.or_else(|| {
|
|
// If unspecified, use the most recent key opened or created
|
|
dc.opened_record_contexts.back().map(|kv| kv.0).copied()
|
|
}) {
|
|
Some(k) => k,
|
|
None => {
|
|
apibail_missing_argument!("no keys are opened", "key");
|
|
}
|
|
};
|
|
|
|
// Get routing context for record
|
|
let Some(rc) = dc.opened_record_contexts.get(&key).cloned() else {
|
|
apibail_missing_argument!("key is not opened", "key");
|
|
};
|
|
|
|
Ok((key, rc))
|
|
}
|
|
}
|
|
|
|
const DEFAULT_INDENT: usize = 4;
|
|
pub fn indent_string<S: ToString>(s: &S) -> String {
|
|
indent_by(DEFAULT_INDENT, s.to_string())
|
|
}
|
|
pub fn indent_all_string<S: ToString>(s: &S) -> String {
|
|
indent_all_by(DEFAULT_INDENT, s.to_string())
|
|
}
|
|
|
|
pub trait ToMultilineString {
|
|
fn to_multiline_string(&self) -> String;
|
|
}
|
|
|
|
impl<T> ToMultilineString for Vec<T>
|
|
where
|
|
T: fmt::Display,
|
|
{
|
|
fn to_multiline_string(&self) -> String {
|
|
let mut out = String::new();
|
|
for x in self {
|
|
out += &x.to_string();
|
|
out += "\n";
|
|
}
|
|
out
|
|
}
|
|
}
|
|
|
|
pub trait StripTrailingNewline {
|
|
fn strip_trailing_newline(&self) -> &str;
|
|
}
|
|
|
|
impl<T: AsRef<str>> StripTrailingNewline for T {
|
|
fn strip_trailing_newline(&self) -> &str {
|
|
self.as_ref()
|
|
.strip_suffix("\r\n")
|
|
.or(self.as_ref().strip_suffix("\n"))
|
|
.unwrap_or(self.as_ref())
|
|
}
|
|
}
|