mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 00:04:54 +00:00
Snapshot-pin all 18 control query handlers as v0.3.0 schema baseline
Add hand-rolled JSON snapshot harness in src/control/queries.rs to detect silent schema drift in operator-facing control-socket responses. Builds a Node with deterministic identity (Identity::from_secret_bytes(&[0xAB; 32])), invokes each of the 18 show_* handlers, redacts 17 volatile fields (version, pid, exe_path, control_socket, tun_name, allow_file, deny_file, *_ms / *_secs_ago / uptime_secs), sorts object keys recursively, and compares against a fixture in src/control/snapshots/. First run writes snapshots and passes; subsequent runs enforce. Future schema changes show as a snapshot diff that operators update intentionally — not a stability contract, just a tripwire so drift is never silent. A 19th meta-test dispatch_covers_all_snapshotted_handlers walks every name through dispatch() to confirm each returns status: ok and trips if a 19th handler is added without a matching snapshot. No new dependencies (insta deliberately not added; Cargo.toml [dev-dependencies] keeps tempfile + criterion only). 18 fixture files added, ~544 lines combined; harness is 367 added lines, all inside #[cfg(test)] mod tests.
This commit is contained in:
@@ -1091,3 +1091,370 @@ pub fn dispatch(node: &Node, command: &str, params: Option<&Value>) -> super::pr
|
||||
_ => super::protocol::Response::error(format!("unknown command: {}", command)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
//! Schema-stability snapshot tests for all 18 control-socket query
|
||||
//! handlers.
|
||||
//!
|
||||
//! Each handler is invoked against a deterministically-constructed
|
||||
//! `Node` (fixed identity seed, empty peer/link/transport/cache
|
||||
//! state). The resulting JSON is normalized — fields whose values
|
||||
//! depend on wall-clock, PID, build environment, or filesystem
|
||||
//! layout are replaced with the literal string `"<redacted>"` —
|
||||
//! and compared against versioned fixtures under
|
||||
//! `src/control/snapshots/`.
|
||||
//!
|
||||
//! The point is to catch accidental schema drift (renames, type
|
||||
//! changes, dropped fields) in the operator-facing wire format.
|
||||
//! Empty-state snapshots are sufficient because every top-level
|
||||
//! key still appears, and per-element shapes inside `[]` arrays
|
||||
//! are covered by the dispatcher contract test plus serde
|
||||
//! derives elsewhere.
|
||||
//!
|
||||
//! ## Updating snapshots
|
||||
//!
|
||||
//! When a schema change is intentional, regenerate fixtures by
|
||||
//! deleting the relevant `.json` files (or the whole
|
||||
//! `snapshots/` directory) and re-running this test. Missing
|
||||
//! fixtures are written from the current output rather than
|
||||
//! failing — the next run then enforces the new shape. Review
|
||||
//! the resulting diff before committing.
|
||||
//!
|
||||
//! ## Determinism
|
||||
//!
|
||||
//! The `Node` is built via `Node::with_identity` from a fixed
|
||||
//! 32-byte seed (`[0xAB; 32]`), so `npub`, `node_addr`, and
|
||||
//! `ipv6_addr` are stable across runs and machines.
|
||||
//! Time-dependent scalars are redacted in `normalize_value` —
|
||||
//! see the `VOLATILE_KEYS` list there for the exact set.
|
||||
//! Empty arrays/maps are intrinsically stable and need no
|
||||
//! redaction.
|
||||
//!
|
||||
//! Schnorr signatures are non-deterministic, but the only
|
||||
//! signature surfaced by these handlers is `declaration_signed:
|
||||
//! bool` (a flag, not the signature itself), so no redaction is
|
||||
//! needed for that.
|
||||
use super::*;
|
||||
use crate::config::Config;
|
||||
use crate::identity::Identity;
|
||||
use crate::node::Node;
|
||||
use serde_json::{Map, Value, json};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// 32-byte seed for the deterministic test identity.
|
||||
/// Any non-zero secret-key-shaped value works; 0xAB-fill is just
|
||||
/// readable in hex.
|
||||
const TEST_SEED: [u8; 32] = [0xAB; 32];
|
||||
|
||||
/// Fields whose value is environment-, time-, or build-dependent
|
||||
/// and therefore must be redacted before comparison. Matched by
|
||||
/// JSON key name anywhere in the document.
|
||||
const VOLATILE_KEYS: &[&str] = &[
|
||||
// Process / build environment
|
||||
"version",
|
||||
"pid",
|
||||
"exe_path",
|
||||
"control_socket",
|
||||
"tun_name",
|
||||
// Filesystem layout (ACL, hosts, etc.)
|
||||
"allow_file",
|
||||
"deny_file",
|
||||
// Wall-clock derived
|
||||
"uptime_secs",
|
||||
"started_at_ms",
|
||||
"session_start_ms",
|
||||
"authenticated_at_ms",
|
||||
"last_seen_ms",
|
||||
"last_activity_ms",
|
||||
"last_recv_ms",
|
||||
"created_at_ms",
|
||||
"initiated_ms",
|
||||
"last_sent_ms",
|
||||
"age_ms",
|
||||
"last_used_ms",
|
||||
"idle_ms",
|
||||
"first_seen_secs_ago",
|
||||
"last_contact_secs_ago",
|
||||
];
|
||||
|
||||
/// Build a Node with a fixed identity, default config, and empty
|
||||
/// runtime state (no peers, links, sessions, transports, or cache
|
||||
/// entries). This keeps every per-element list empty and every
|
||||
/// scalar deterministic modulo `VOLATILE_KEYS`.
|
||||
fn build_test_node() -> Node {
|
||||
let identity =
|
||||
Identity::from_secret_bytes(&TEST_SEED).expect("test seed is a valid secret key");
|
||||
let config = Config::new();
|
||||
Node::with_identity(identity, config).expect("default config is valid")
|
||||
}
|
||||
|
||||
/// Recursively walk a JSON value, replacing the value of any key
|
||||
/// listed in `VOLATILE_KEYS` with the literal string
|
||||
/// `"<redacted>"`. Array elements are recursed into.
|
||||
fn normalize_value(value: &mut Value) {
|
||||
match value {
|
||||
Value::Object(map) => {
|
||||
for (key, v) in map.iter_mut() {
|
||||
if VOLATILE_KEYS.contains(&key.as_str()) {
|
||||
*v = Value::String("<redacted>".to_string());
|
||||
} else {
|
||||
normalize_value(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Array(items) => {
|
||||
for item in items.iter_mut() {
|
||||
normalize_value(item);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap a handler value in the on-the-wire `Response` envelope so
|
||||
/// the snapshot reflects exactly what a control-socket client
|
||||
/// receives. Pretty-printed and sorted-keyed for readable diffs.
|
||||
fn render(value: Value) -> String {
|
||||
let mut wrapped = json!({ "status": "ok", "data": value });
|
||||
normalize_value(&mut wrapped);
|
||||
let sorted = sort_object_keys(&wrapped);
|
||||
serde_json::to_string_pretty(&sorted).expect("json serialization is infallible")
|
||||
}
|
||||
|
||||
/// Same as `render` but takes a `Response` directly (for handlers
|
||||
/// that return `Response`, not `Value`).
|
||||
fn render_response(resp: super::super::protocol::Response) -> String {
|
||||
let value = serde_json::to_value(&resp).expect("response always serializes");
|
||||
let mut value = value;
|
||||
normalize_value(&mut value);
|
||||
let sorted = sort_object_keys(&value);
|
||||
serde_json::to_string_pretty(&sorted).expect("json serialization is infallible")
|
||||
}
|
||||
|
||||
/// Recursively sort object keys for stable diff-friendly output.
|
||||
/// `serde_json::Value` preserves insertion order; handlers don't
|
||||
/// guarantee any particular emit order, so normalize here.
|
||||
fn sort_object_keys(value: &Value) -> Value {
|
||||
match value {
|
||||
Value::Object(map) => {
|
||||
let mut sorted: Map<String, Value> = Map::new();
|
||||
let mut keys: Vec<&String> = map.keys().collect();
|
||||
keys.sort();
|
||||
for key in keys {
|
||||
sorted.insert(key.clone(), sort_object_keys(&map[key]));
|
||||
}
|
||||
Value::Object(sorted)
|
||||
}
|
||||
Value::Array(items) => Value::Array(items.iter().map(sort_object_keys).collect()),
|
||||
other => other.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn snapshot_dir() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("src")
|
||||
.join("control")
|
||||
.join("snapshots")
|
||||
}
|
||||
|
||||
/// Compare `actual` against the on-disk fixture for `name`. If the
|
||||
/// fixture does not exist, write it (first-run convention) and
|
||||
/// pass. Any subsequent mismatch fails with an inline diff hint.
|
||||
fn assert_snapshot(name: &str, actual: &str) {
|
||||
let path = snapshot_dir().join(format!("{name}.json"));
|
||||
if !path.exists() {
|
||||
std::fs::create_dir_all(path.parent().unwrap())
|
||||
.expect("failed to create snapshots dir");
|
||||
std::fs::write(&path, actual).expect("failed to write new snapshot");
|
||||
// Newly written: nothing to compare. Subsequent runs enforce.
|
||||
return;
|
||||
}
|
||||
let expected = std::fs::read_to_string(&path)
|
||||
.unwrap_or_else(|e| panic!("failed to read snapshot {}: {e}", path.display()));
|
||||
// Tolerate trailing newline differences from editors.
|
||||
if expected.trim_end() != actual.trim_end() {
|
||||
panic!(
|
||||
"snapshot mismatch for {name}\n\
|
||||
fixture: {}\n\
|
||||
-- expected --\n{expected}\n\
|
||||
-- actual --\n{actual}\n\
|
||||
-- end --\n\
|
||||
If the schema change is intentional, delete the fixture \
|
||||
and re-run to regenerate.",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 18 handler snapshot tests --------------------------------------
|
||||
|
||||
#[test]
|
||||
fn snapshot_show_status() {
|
||||
let node = build_test_node();
|
||||
assert_snapshot("show_status", &render(show_status(&node)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_show_acl() {
|
||||
let node = build_test_node();
|
||||
assert_snapshot("show_acl", &render(show_acl(&node)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_show_peers() {
|
||||
let node = build_test_node();
|
||||
assert_snapshot("show_peers", &render(show_peers(&node)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_show_links() {
|
||||
let node = build_test_node();
|
||||
assert_snapshot("show_links", &render(show_links(&node)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_show_tree() {
|
||||
let node = build_test_node();
|
||||
assert_snapshot("show_tree", &render(show_tree(&node)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_show_sessions() {
|
||||
let node = build_test_node();
|
||||
assert_snapshot("show_sessions", &render(show_sessions(&node)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_show_bloom() {
|
||||
let node = build_test_node();
|
||||
assert_snapshot("show_bloom", &render(show_bloom(&node)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_show_mmp() {
|
||||
let node = build_test_node();
|
||||
assert_snapshot("show_mmp", &render(show_mmp(&node)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_show_cache() {
|
||||
let node = build_test_node();
|
||||
assert_snapshot("show_cache", &render(show_cache(&node)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_show_connections() {
|
||||
let node = build_test_node();
|
||||
assert_snapshot("show_connections", &render(show_connections(&node)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_show_transports() {
|
||||
let node = build_test_node();
|
||||
assert_snapshot("show_transports", &render(show_transports(&node)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_show_routing() {
|
||||
let node = build_test_node();
|
||||
assert_snapshot("show_routing", &render(show_routing(&node)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_show_identity_cache() {
|
||||
let node = build_test_node();
|
||||
assert_snapshot("show_identity_cache", &render(show_identity_cache(&node)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_show_stats_list() {
|
||||
// Static — no Node needed.
|
||||
assert_snapshot("show_stats_list", &render(show_stats_list()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_show_stats_history() {
|
||||
let node = build_test_node();
|
||||
// Pin the empty-history series shape for one node-level metric.
|
||||
let params = json!({ "metric": "mesh_size", "window": "10s", "granularity": "1s" });
|
||||
let resp = show_stats_history(&node, Some(¶ms));
|
||||
assert_snapshot("show_stats_history", &render_response(resp));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_show_stats_all_history() {
|
||||
let node = build_test_node();
|
||||
// Empty-history all-node series; small window keeps the
|
||||
// per-series `values` arrays short and stable.
|
||||
let params = json!({ "window": "10s", "granularity": "1s" });
|
||||
let resp = show_stats_all_history(&node, Some(¶ms));
|
||||
assert_snapshot("show_stats_all_history", &render_response(resp));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_show_stats_peers() {
|
||||
let node = build_test_node();
|
||||
assert_snapshot("show_stats_peers", &render(show_stats_peers(&node)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_show_stats_history_all_peers() {
|
||||
let node = build_test_node();
|
||||
// No peers tracked → empty `peers: []` envelope. Per-peer
|
||||
// `values` shape is exercised once a real peer is wired in;
|
||||
// here we only pin the envelope.
|
||||
let params = json!({ "metric": "srtt_ms", "window": "10s", "granularity": "1s" });
|
||||
let resp = show_stats_history_all_peers(&node, Some(¶ms));
|
||||
assert_snapshot("show_stats_history_all_peers", &render_response(resp));
|
||||
}
|
||||
|
||||
/// Sanity check: every handler advertised in `dispatch` is also
|
||||
/// covered by a snapshot test above. If a new handler is added
|
||||
/// without a matching snapshot, this test fails.
|
||||
#[test]
|
||||
fn dispatch_covers_all_snapshotted_handlers() {
|
||||
let expected = [
|
||||
"show_status",
|
||||
"show_acl",
|
||||
"show_peers",
|
||||
"show_links",
|
||||
"show_tree",
|
||||
"show_sessions",
|
||||
"show_bloom",
|
||||
"show_mmp",
|
||||
"show_cache",
|
||||
"show_connections",
|
||||
"show_transports",
|
||||
"show_routing",
|
||||
"show_identity_cache",
|
||||
"show_stats_list",
|
||||
"show_stats_history",
|
||||
"show_stats_all_history",
|
||||
"show_stats_peers",
|
||||
"show_stats_history_all_peers",
|
||||
];
|
||||
assert_eq!(expected.len(), 18, "expected exactly 18 query handlers");
|
||||
let node = build_test_node();
|
||||
for cmd in expected {
|
||||
// Each must dispatch successfully (status == "ok") with
|
||||
// minimal params. Handlers requiring params get them.
|
||||
let params = match cmd {
|
||||
"show_stats_history" => Some(json!({
|
||||
"metric": "mesh_size", "window": "10s", "granularity": "1s"
|
||||
})),
|
||||
"show_stats_all_history" => Some(json!({ "window": "10s", "granularity": "1s" })),
|
||||
"show_stats_history_all_peers" => Some(json!({
|
||||
"metric": "srtt_ms", "window": "10s", "granularity": "1s"
|
||||
})),
|
||||
_ => None,
|
||||
};
|
||||
let resp = dispatch(&node, cmd, params.as_ref());
|
||||
assert_eq!(
|
||||
resp.status, "ok",
|
||||
"dispatch({cmd}) returned status={} message={:?}",
|
||||
resp.status, resp.message
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"data": {
|
||||
"allow_all": false,
|
||||
"allow_entries": [],
|
||||
"allow_file": "<redacted>",
|
||||
"allow_file_entries": [],
|
||||
"default_decision": "allow",
|
||||
"deny_all": false,
|
||||
"deny_entries": [],
|
||||
"deny_file": "<redacted>",
|
||||
"deny_file_entries": [],
|
||||
"effective_mode": "default_open",
|
||||
"enforcement_active": false
|
||||
},
|
||||
"status": "ok"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"data": {
|
||||
"is_leaf_only": false,
|
||||
"leaf_dependent_count": 0,
|
||||
"leaf_dependents": [],
|
||||
"own_node_addr": "1b4788b7ab7a436a611fc59fb1e34c6e",
|
||||
"peer_filters": [],
|
||||
"sequence": 0,
|
||||
"stats": {
|
||||
"accepted": 0,
|
||||
"debounce_suppressed": 0,
|
||||
"decode_error": 0,
|
||||
"fill_exceeded": 0,
|
||||
"invalid": 0,
|
||||
"non_v1": 0,
|
||||
"received": 0,
|
||||
"send_failed": 0,
|
||||
"sent": 0,
|
||||
"stale": 0,
|
||||
"unknown_peer": 0
|
||||
}
|
||||
},
|
||||
"status": "ok"
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"data": {
|
||||
"avg_age_ms": 0,
|
||||
"count": 0,
|
||||
"default_ttl_ms": 300000,
|
||||
"entries": [],
|
||||
"expired": 0,
|
||||
"fill_ratio": 0.0,
|
||||
"max_entries": 50000
|
||||
},
|
||||
"status": "ok"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"data": {
|
||||
"connections": []
|
||||
},
|
||||
"status": "ok"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"data": {
|
||||
"count": 0,
|
||||
"entries": [],
|
||||
"max_entries": 10000
|
||||
},
|
||||
"status": "ok"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"data": {
|
||||
"links": []
|
||||
},
|
||||
"status": "ok"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"data": {
|
||||
"peers": [],
|
||||
"sessions": []
|
||||
},
|
||||
"status": "ok"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"data": {
|
||||
"peers": []
|
||||
},
|
||||
"status": "ok"
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"data": {
|
||||
"congestion": {
|
||||
"ce_forwarded": 0,
|
||||
"ce_received": 0,
|
||||
"congestion_detected": 0,
|
||||
"kernel_drop_events": 0
|
||||
},
|
||||
"coord_cache_entries": 0,
|
||||
"discovery": {
|
||||
"req_backoff_suppressed": 0,
|
||||
"req_bloom_miss": 0,
|
||||
"req_decode_error": 0,
|
||||
"req_deduplicated": 0,
|
||||
"req_duplicate": 0,
|
||||
"req_fallback_forwarded": 0,
|
||||
"req_forward_rate_limited": 0,
|
||||
"req_forwarded": 0,
|
||||
"req_initiated": 0,
|
||||
"req_no_tree_peer": 0,
|
||||
"req_received": 0,
|
||||
"req_target_is_us": 0,
|
||||
"req_ttl_exhausted": 0,
|
||||
"resp_accepted": 0,
|
||||
"resp_decode_error": 0,
|
||||
"resp_forwarded": 0,
|
||||
"resp_identity_miss": 0,
|
||||
"resp_proof_failed": 0,
|
||||
"resp_received": 0,
|
||||
"resp_timed_out": 0
|
||||
},
|
||||
"error_signals": {
|
||||
"coords_required": 0,
|
||||
"mtu_exceeded": 0,
|
||||
"path_broken": 0
|
||||
},
|
||||
"forwarding": {
|
||||
"decode_error_bytes": 0,
|
||||
"decode_error_packets": 0,
|
||||
"delivered_bytes": 0,
|
||||
"delivered_packets": 0,
|
||||
"drop_mtu_exceeded_bytes": 0,
|
||||
"drop_mtu_exceeded_packets": 0,
|
||||
"drop_no_route_bytes": 0,
|
||||
"drop_no_route_packets": 0,
|
||||
"drop_send_error_bytes": 0,
|
||||
"drop_send_error_packets": 0,
|
||||
"forwarded_bytes": 0,
|
||||
"forwarded_packets": 0,
|
||||
"originated_bytes": 0,
|
||||
"originated_packets": 0,
|
||||
"received_bytes": 0,
|
||||
"received_packets": 0,
|
||||
"ttl_exhausted_bytes": 0,
|
||||
"ttl_exhausted_packets": 0
|
||||
},
|
||||
"identity_cache_entries": 0,
|
||||
"pending_lookups": [],
|
||||
"pending_tun_destinations": 0,
|
||||
"pending_tun_packets": 0,
|
||||
"recent_requests": 0,
|
||||
"retries": []
|
||||
},
|
||||
"status": "ok"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"data": {
|
||||
"sessions": []
|
||||
},
|
||||
"status": "ok"
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
{
|
||||
"data": {
|
||||
"granularity_seconds": 1,
|
||||
"peer": null,
|
||||
"series": [
|
||||
{
|
||||
"granularity_seconds": 1,
|
||||
"metric": "mesh_size",
|
||||
"unit": "nodes",
|
||||
"values": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"granularity_seconds": 1,
|
||||
"metric": "tree_depth",
|
||||
"unit": "hops",
|
||||
"values": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"granularity_seconds": 1,
|
||||
"metric": "peer_count",
|
||||
"unit": "peers",
|
||||
"values": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"granularity_seconds": 1,
|
||||
"metric": "parent_switches",
|
||||
"unit": "events/s",
|
||||
"values": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"granularity_seconds": 1,
|
||||
"metric": "bytes_in",
|
||||
"unit": "bytes/s",
|
||||
"values": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"granularity_seconds": 1,
|
||||
"metric": "bytes_out",
|
||||
"unit": "bytes/s",
|
||||
"values": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"granularity_seconds": 1,
|
||||
"metric": "packets_in",
|
||||
"unit": "packets/s",
|
||||
"values": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"granularity_seconds": 1,
|
||||
"metric": "packets_out",
|
||||
"unit": "packets/s",
|
||||
"values": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"granularity_seconds": 1,
|
||||
"metric": "loss_rate",
|
||||
"unit": "fraction",
|
||||
"values": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"granularity_seconds": 1,
|
||||
"metric": "active_sessions",
|
||||
"unit": "sessions",
|
||||
"values": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
}
|
||||
],
|
||||
"window_seconds": 10
|
||||
},
|
||||
"status": "ok"
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"data": {
|
||||
"granularity_seconds": 1,
|
||||
"metric": "mesh_size",
|
||||
"unit": "nodes",
|
||||
"values": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"status": "ok"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"data": {
|
||||
"granularity_seconds": 1,
|
||||
"metric": "srtt_ms",
|
||||
"peers": [],
|
||||
"unit": "ms",
|
||||
"window_seconds": 10
|
||||
},
|
||||
"status": "ok"
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"data": {
|
||||
"fast_ring_seconds": 3600,
|
||||
"metrics": [
|
||||
{
|
||||
"name": "mesh_size",
|
||||
"scope": "node",
|
||||
"unit": "nodes"
|
||||
},
|
||||
{
|
||||
"name": "tree_depth",
|
||||
"scope": "node",
|
||||
"unit": "hops"
|
||||
},
|
||||
{
|
||||
"name": "peer_count",
|
||||
"scope": "node",
|
||||
"unit": "peers"
|
||||
},
|
||||
{
|
||||
"name": "parent_switches",
|
||||
"scope": "node",
|
||||
"unit": "events/s"
|
||||
},
|
||||
{
|
||||
"name": "bytes_in",
|
||||
"scope": "node",
|
||||
"unit": "bytes/s"
|
||||
},
|
||||
{
|
||||
"name": "bytes_out",
|
||||
"scope": "node",
|
||||
"unit": "bytes/s"
|
||||
},
|
||||
{
|
||||
"name": "packets_in",
|
||||
"scope": "node",
|
||||
"unit": "packets/s"
|
||||
},
|
||||
{
|
||||
"name": "packets_out",
|
||||
"scope": "node",
|
||||
"unit": "packets/s"
|
||||
},
|
||||
{
|
||||
"name": "loss_rate",
|
||||
"scope": "node",
|
||||
"unit": "fraction"
|
||||
},
|
||||
{
|
||||
"name": "active_sessions",
|
||||
"scope": "node",
|
||||
"unit": "sessions"
|
||||
},
|
||||
{
|
||||
"name": "srtt_ms",
|
||||
"scope": "peer",
|
||||
"unit": "ms"
|
||||
},
|
||||
{
|
||||
"name": "loss_rate",
|
||||
"scope": "peer",
|
||||
"unit": "fraction"
|
||||
},
|
||||
{
|
||||
"name": "bytes_in",
|
||||
"scope": "peer",
|
||||
"unit": "bytes/s"
|
||||
},
|
||||
{
|
||||
"name": "bytes_out",
|
||||
"scope": "peer",
|
||||
"unit": "bytes/s"
|
||||
},
|
||||
{
|
||||
"name": "packets_in",
|
||||
"scope": "peer",
|
||||
"unit": "packets/s"
|
||||
},
|
||||
{
|
||||
"name": "packets_out",
|
||||
"scope": "peer",
|
||||
"unit": "packets/s"
|
||||
},
|
||||
{
|
||||
"name": "ecn_ce",
|
||||
"scope": "peer",
|
||||
"unit": "events/s"
|
||||
}
|
||||
],
|
||||
"peer_retention_seconds": 86400,
|
||||
"slow_ring_minutes": 1440
|
||||
},
|
||||
"status": "ok"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"data": {
|
||||
"count": 0,
|
||||
"peers": []
|
||||
},
|
||||
"status": "ok"
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"data": {
|
||||
"connection_count": 0,
|
||||
"control_socket": "<redacted>",
|
||||
"effective_ipv6_mtu": 1203,
|
||||
"estimated_mesh_size": null,
|
||||
"exe_path": "<redacted>",
|
||||
"forwarding": {
|
||||
"decode_error_bytes": 0,
|
||||
"decode_error_packets": 0,
|
||||
"delivered_bytes": 0,
|
||||
"delivered_packets": 0,
|
||||
"drop_mtu_exceeded_bytes": 0,
|
||||
"drop_mtu_exceeded_packets": 0,
|
||||
"drop_no_route_bytes": 0,
|
||||
"drop_no_route_packets": 0,
|
||||
"drop_send_error_bytes": 0,
|
||||
"drop_send_error_packets": 0,
|
||||
"forwarded_bytes": 0,
|
||||
"forwarded_packets": 0,
|
||||
"originated_bytes": 0,
|
||||
"originated_packets": 0,
|
||||
"received_bytes": 0,
|
||||
"received_packets": 0,
|
||||
"ttl_exhausted_bytes": 0,
|
||||
"ttl_exhausted_packets": 0
|
||||
},
|
||||
"ipv6_addr": "fd1b:4788:b7ab:7a43:6a61:1fc5:9fb1:e34c",
|
||||
"is_leaf_only": false,
|
||||
"link_count": 0,
|
||||
"node_addr": "1b4788b7ab7a436a611fc59fb1e34c6e",
|
||||
"npub": "npub1sx42mj99aql52aklsg70y2jmr95u7uz2p40k769aw46ppjv302kqkhmu5r",
|
||||
"peer_count": 0,
|
||||
"pid": "<redacted>",
|
||||
"session_count": 0,
|
||||
"sparklines": {
|
||||
"bytes_in": [],
|
||||
"bytes_out": [],
|
||||
"loss_rate": [],
|
||||
"mesh_size": [],
|
||||
"peer_count": [],
|
||||
"tree_depth": []
|
||||
},
|
||||
"state": "created",
|
||||
"transport_count": 0,
|
||||
"tun_name": "<redacted>",
|
||||
"tun_state": "disabled",
|
||||
"uptime_secs": "<redacted>",
|
||||
"version": "<redacted>"
|
||||
},
|
||||
"status": "ok"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"data": {
|
||||
"transports": []
|
||||
},
|
||||
"status": "ok"
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"data": {
|
||||
"declaration_sequence": 1,
|
||||
"declaration_signed": true,
|
||||
"depth": 0,
|
||||
"is_root": true,
|
||||
"my_coords": [
|
||||
"1b4788b7ab7a436a611fc59fb1e34c6e"
|
||||
],
|
||||
"my_node_addr": "1b4788b7ab7a436a611fc59fb1e34c6e",
|
||||
"parent": "1b4788b7ab7a436a611fc59fb1e34c6e",
|
||||
"parent_display_name": "1b4788b7...",
|
||||
"peer_tree_count": 0,
|
||||
"peers": [],
|
||||
"root": "1b4788b7ab7a436a611fc59fb1e34c6e",
|
||||
"stats": {
|
||||
"accepted": 0,
|
||||
"addr_mismatch": 0,
|
||||
"ancestry_changed": 0,
|
||||
"decode_error": 0,
|
||||
"flap_dampened": 0,
|
||||
"loop_detected": 0,
|
||||
"parent_losses": 0,
|
||||
"parent_switched": 0,
|
||||
"parent_switches": 0,
|
||||
"rate_limited": 0,
|
||||
"received": 0,
|
||||
"send_failed": 0,
|
||||
"sent": 0,
|
||||
"sig_failed": 0,
|
||||
"stale": 0,
|
||||
"unknown_peer": 0
|
||||
}
|
||||
},
|
||||
"status": "ok"
|
||||
}
|
||||
Reference in New Issue
Block a user