From 01fa99bee2f5f0052bc5e7b14f3bfaf7929128da Mon Sep 17 00:00:00 2001 From: Lee Smet Date: Tue, 19 May 2026 15:29:17 +0200 Subject: [PATCH] Add node ip and node start time to node info Signed-off-by: Lee Smet --- CHANGELOG.md | 4 ++++ docs/api.yaml | 9 +++++++++ docs/openrpc.json | 11 +++++++++++ mycelium-api/src/lib.rs | 6 ++++++ mycelium-api/src/rpc.rs | 2 ++ mycelium/src/ffi/exports.rs | 2 ++ mycelium/src/ffi/types.rs | 10 ++++++++-- mycelium/src/lib.rs | 14 ++++++++++++++ 8 files changed, 56 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f10100d..7e825d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add a node ip and node start time field to the node info struct. + ## [0.7.7] - 2026-05-06 ### Added diff --git a/docs/api.yaml b/docs/api.yaml index 3da4e63..6eba7d1 100644 --- a/docs/api.yaml +++ b/docs/api.yaml @@ -733,6 +733,10 @@ components: description: The subnet owned by the node and advertised to peers type: string example: 54f:b680:ba6e:7ced::/64 + nodeIp: + description: The full overlay IP of the node, derived from its public key + type: string + example: 54f:b680:ba6e:7ced:abcd:1234:5678:9abc nodePubkey: description: The public key of the node type: string @@ -740,6 +744,11 @@ components: minLength: 64 maxLength: 64 example: 02468ace13579bdf02468ace13579bdf02468ace13579bdf02468ace13579bdf + nodeStartTime: + description: The time the node started, as seconds since the Unix epoch + type: integer + format: int64 + example: 1716100000 PacketStatEntry: description: Statistics for packets routed for a single IP address diff --git a/docs/openrpc.json b/docs/openrpc.json index c0d0bb3..8fa25b4 100644 --- a/docs/openrpc.json +++ b/docs/openrpc.json @@ -871,6 +871,11 @@ "type": "string", "example": "54f:b680:ba6e:7ced::/64" }, + "nodeIp": { + "description": "The full overlay IP of the node, derived from its public key", + "type": "string", + "example": "54f:b680:ba6e:7ced:abcd:1234:5678:9abc" + }, "nodePubkey": { "description": "The public key of the node", "type": "string", @@ -878,6 +883,12 @@ "minLength": 64, "maxLength": 64, "example": "02468ace13579bdf02468ace13579bdf02468ace13579bdf02468ace13579bdf" + }, + "nodeStartTime": { + "description": "The time the node started, as seconds since the Unix epoch", + "type": "integer", + "format": "int64", + "example": 1716100000 } } }, diff --git a/mycelium-api/src/lib.rs b/mycelium-api/src/lib.rs index 01584ee..885cbd9 100644 --- a/mycelium-api/src/lib.rs +++ b/mycelium-api/src/lib.rs @@ -404,8 +404,12 @@ where pub struct Info { /// The overlay subnet in use by the node. pub node_subnet: String, + /// The full overlay IP of the node. + pub node_ip: String, /// The public key of the node pub node_pubkey: PublicKey, + /// Node start time, as seconds since the Unix epoch. + pub node_start_time: u64, } /// Get general info about the node. @@ -416,7 +420,9 @@ where let info = state.node.lock().await.info(); Json(Info { node_subnet: info.node_subnet.to_string(), + node_ip: info.node_ip.to_string(), node_pubkey: info.node_pubkey, + node_start_time: info.node_start_time, }) } diff --git a/mycelium-api/src/rpc.rs b/mycelium-api/src/rpc.rs index 36ee222..079c8e6 100644 --- a/mycelium-api/src/rpc.rs +++ b/mycelium-api/src/rpc.rs @@ -169,7 +169,9 @@ where let node_info = self.state.node.lock().await.info(); Ok(Info { node_subnet: node_info.node_subnet.to_string(), + node_ip: node_info.node_ip.to_string(), node_pubkey: node_info.node_pubkey, + node_start_time: node_info.node_start_time, }) } diff --git a/mycelium/src/ffi/exports.rs b/mycelium/src/ffi/exports.rs index 6edebe9..7ad1520 100644 --- a/mycelium/src/ffi/exports.rs +++ b/mycelium/src/ffi/exports.rs @@ -320,7 +320,9 @@ pub unsafe extern "C" fn mycelium_get_node_info( let info = h.node().lock().await.info(); *out = mycelium_node_info_t { subnet: cstring(info.node_subnet.to_string()), + ip: cstring(info.node_ip.to_string()), pubkey: cstring(info.node_pubkey.to_string()), + start_time: info.node_start_time, }; MYCELIUM_OK }) diff --git a/mycelium/src/ffi/types.rs b/mycelium/src/ffi/types.rs index 60fb7a7..e73d42a 100644 --- a/mycelium/src/ffi/types.rs +++ b/mycelium/src/ffi/types.rs @@ -70,7 +70,11 @@ pub struct mycelium_secret_key_t { #[repr(C)] pub struct mycelium_node_info_t { pub subnet: *mut c_char, + /// The full overlay IP of the node, derived from its public key. + pub ip: *mut c_char, pub pubkey: *mut c_char, + /// Node start time, as seconds since the Unix epoch. + pub start_time: u64, } /// Information about a known peer connection. @@ -238,8 +242,8 @@ pub unsafe extern "C" fn mycelium_string_array_free(arr: *mut mycelium_string_ar } /// Free a `mycelium_node_info_t` populated by `mycelium_get_node_info`. -/// Releases the inner `subnet` and `pubkey` strings and resets them to -/// NULL. +/// Releases the inner `subnet`, `ip` and `pubkey` strings and resets them +/// to NULL. /// /// # Safety /// @@ -253,8 +257,10 @@ pub unsafe extern "C" fn mycelium_node_info_free(info: *mut mycelium_node_info_t } let info = &mut *info; free_cstring(info.subnet); + free_cstring(info.ip); free_cstring(info.pubkey); info.subnet = std::ptr::null_mut(); + info.ip = std::ptr::null_mut(); info.pubkey = std::ptr::null_mut(); } diff --git a/mycelium/src/lib.rs b/mycelium/src/lib.rs index 1d3c116..661d02f 100644 --- a/mycelium/src/lib.rs +++ b/mycelium/src/lib.rs @@ -133,14 +133,20 @@ pub struct Node { proxy: Proxy, #[cfg(feature = "message")] message_stack: message::MessageStack, + /// The moment this node was created, used to report uptime/start time. + started: std::time::SystemTime, } /// General info about a node. pub struct NodeInfo { /// The overlay subnet in use by the node. pub node_subnet: Subnet, + /// The full overlay IP of the node (derived from the public key). + pub node_ip: std::net::IpAddr, /// The public key of the node pub node_pubkey: crypto::PublicKey, + /// Node start time, as seconds since the Unix epoch. + pub node_start_time: u64, } impl Node @@ -149,6 +155,7 @@ where { /// Setup a new `Node` with the provided [`Config`]. pub async fn new(config: Config) -> Result> { + let started = std::time::SystemTime::now(); // If a private network is configured, validate network name if let Some((net_name, _)) = &config.private_network_config { if net_name.len() < 2 || net_name.len() > 64 { @@ -334,6 +341,7 @@ where proxy, #[cfg(feature = "message")] message_stack: ms, + started, }) } @@ -341,7 +349,13 @@ where pub fn info(&self) -> NodeInfo { NodeInfo { node_subnet: self.router.node_tun_subnet(), + node_ip: self.router.node_public_key().address().into(), node_pubkey: self.router.node_public_key(), + node_start_time: self + .started + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_secs()) + .unwrap_or(0), } }