mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-09-05 22:32:55 +02:00
Implements structured JSON diagnostic output for node state export with full schema documentation. This feature provides machine-readable diagnostics for automated analysis, monitoring, and AI/MCP integration. Key changes: - Add `zerotier-cli diagnostic` command for JSON node state export - Add `zerotier-cli dump -j` as alias for JSON output - Add `zerotier-cli diagnostic --schema` to print JSON schema - Implement platform-specific interface collection (Linux, BSD, macOS, Windows) - Create modular diagnostic/ directory with isolated try/catch error handling - Add comprehensive JSON schema (diagnostic_schema.json) for validation - Include build-time schema embedding for offline access - Add Python and Rust scripts for schema embedding during build - Update build systems to compile new diagnostic modules The diagnostic output includes: - Node configuration and identity - Network memberships and settings - Interface states and IP addresses - Peer connections and statistics - Moon orbits - Controller networks (if applicable) All diagnostic collection is wrapped in try/catch blocks to ensure partial failures don't prevent overall output generation. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
152 lines
No EOL
5.1 KiB
C++
152 lines
No EOL
5.1 KiB
C++
#include "version.h"
|
|
#include "diagnostic/node_state_json.hpp"
|
|
#include "diagnostic/node_state_sections.hpp"
|
|
#include "diagnostic/node_state_interfaces_linux.hpp" // platform-specific, add others as needed
|
|
#include <nlohmann/json.hpp>
|
|
#include <ctime>
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <cstdio>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/utsname.h>
|
|
|
|
namespace {
|
|
std::string make_timestamp() {
|
|
auto t = std::time(nullptr);
|
|
std::tm tm_utc = *std::gmtime(&t);
|
|
char buf[32];
|
|
std::strftime(buf, sizeof(buf), "%Y%m%dT%H%M%SZ", &tm_utc);
|
|
return std::string(buf);
|
|
}
|
|
}
|
|
|
|
void write_node_state_json(const ZeroTier::InetAddress &addr, const std::string &homeDir, std::map<std::string, std::string> &requestHeaders, std::map<std::string, std::string> &responseHeaders, std::string &responseBody) {
|
|
nlohmann::json j;
|
|
// Schema version for MCP/diagnostic output
|
|
j["schema_version"] = "1.0"; // Update this if the schema changes
|
|
std::vector<std::string> errors;
|
|
|
|
// Timestamps
|
|
auto t = std::time(nullptr);
|
|
auto tm_utc = *std::gmtime(&t);
|
|
auto tm_local = *std::localtime(&t);
|
|
std::stringstream utc_ts, local_ts;
|
|
utc_ts << std::put_time(&tm_utc, "%Y-%m-%dT%H:%M:%SZ");
|
|
local_ts << std::put_time(&tm_local, "%Y-%m-%dT%H:%M:%S%z");
|
|
j["utc_timestamp"] = utc_ts.str();
|
|
j["local_timestamp"] = local_ts.str();
|
|
|
|
#ifdef __APPLE__
|
|
j["platform"] = "macOS";
|
|
#elif defined(_WIN32)
|
|
j["platform"] = "Windows";
|
|
#elif defined(__linux__)
|
|
j["platform"] = "Linux";
|
|
#else
|
|
j["platform"] = "other unix based OS";
|
|
#endif
|
|
j["zerotier_version"] = std::to_string(ZEROTIER_ONE_VERSION_MAJOR) + "." + std::to_string(ZEROTIER_ONE_VERSION_MINOR) + "." + std::to_string(ZEROTIER_ONE_VERSION_REVISION);
|
|
|
|
// Extensibility/context fields
|
|
// node_role: placeholder (could be "controller", "member", etc.)
|
|
j["node_role"] = nullptr; // Set to actual role if available
|
|
// uptime: seconds since boot (best effort)
|
|
long uptime = -1;
|
|
#ifdef __linux__
|
|
FILE* f = fopen("/proc/uptime", "r");
|
|
if (f) {
|
|
if (fscanf(f, "%ld", &uptime) != 1) uptime = -1;
|
|
fclose(f);
|
|
}
|
|
#endif
|
|
if (uptime >= 0)
|
|
j["uptime"] = uptime;
|
|
else
|
|
j["uptime"] = nullptr;
|
|
// hostname
|
|
char hostname[256] = {};
|
|
if (gethostname(hostname, sizeof(hostname)) == 0) {
|
|
j["hostname"] = hostname;
|
|
} else {
|
|
j["hostname"] = nullptr;
|
|
}
|
|
// tags: extensibility array for future use (e.g., MCP tags, custom info)
|
|
j["tags"] = nlohmann::json::array();
|
|
// mcp_context: extensibility object for MCP or plugin context
|
|
j["mcp_context"] = nlohmann::json::object();
|
|
|
|
// Add each section
|
|
try {
|
|
addNodeStateStatusJson(j, addr, requestHeaders);
|
|
} catch (const std::exception& e) {
|
|
errors.push_back(std::string("status section: ") + e.what());
|
|
} catch (...) {
|
|
errors.push_back("status section: unknown error");
|
|
}
|
|
try {
|
|
addNodeStateNetworksJson(j, addr, requestHeaders);
|
|
} catch (const std::exception& e) {
|
|
errors.push_back(std::string("networks section: ") + e.what());
|
|
} catch (...) {
|
|
errors.push_back("networks section: unknown error");
|
|
}
|
|
try {
|
|
addNodeStatePeersJson(j, addr, requestHeaders);
|
|
} catch (const std::exception& e) {
|
|
errors.push_back(std::string("peers section: ") + e.what());
|
|
} catch (...) {
|
|
errors.push_back("peers section: unknown error");
|
|
}
|
|
try {
|
|
addNodeStateLocalConfJson(j, homeDir);
|
|
} catch (const std::exception& e) {
|
|
errors.push_back(std::string("local_conf section: ") + e.what());
|
|
} catch (...) {
|
|
errors.push_back("local_conf section: unknown error");
|
|
}
|
|
try {
|
|
addNodeStateInterfacesJson(j); // platform-specific
|
|
} catch (const std::exception& e) {
|
|
errors.push_back(std::string("interfaces section: ") + e.what());
|
|
} catch (...) {
|
|
errors.push_back("interfaces section: unknown error");
|
|
}
|
|
j["errors"] = errors;
|
|
|
|
// Filename: nodeId and timestamp
|
|
std::string nodeId = (j.contains("nodeId") && j["nodeId"].is_string()) ? j["nodeId"].get<std::string>() : "unknown";
|
|
std::string timestamp = make_timestamp();
|
|
std::string filename = "zerotier_node_state_" + nodeId + "_" + timestamp + ".json";
|
|
std::string tmp_path = "/tmp/" + filename;
|
|
std::string cwd_path = filename;
|
|
std::string json_str = j.dump(2);
|
|
|
|
// Try /tmp, then cwd, then stdout
|
|
bool written = false;
|
|
{
|
|
std::ofstream ofs(tmp_path);
|
|
if (ofs) {
|
|
ofs << json_str;
|
|
ofs.close();
|
|
std::cout << "Wrote node state to: " << tmp_path << std::endl;
|
|
written = true;
|
|
}
|
|
}
|
|
if (!written) {
|
|
std::ofstream ofs(cwd_path);
|
|
if (ofs) {
|
|
ofs << json_str;
|
|
ofs.close();
|
|
std::cout << "Wrote node state to: " << cwd_path << std::endl;
|
|
written = true;
|
|
}
|
|
}
|
|
if (!written) {
|
|
std::cout << json_str << std::endl;
|
|
std::cerr << "Could not write node state to file, output to stdout instead." << std::endl;
|
|
}
|
|
}
|