Compare commits
No commits in common. "be253e8001331f8bc1a7665158baad053a996be2" and "9be6a0e40be62ed0524fc70917e6e8178adfcc8a" have entirely different histories.
be253e8001
...
9be6a0e40b
8 changed files with 11 additions and 303 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
|
@ -2732,7 +2732,7 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itsgoin-cli"
|
name = "itsgoin-cli"
|
||||||
version = "0.5.0"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"hex",
|
"hex",
|
||||||
|
|
@ -2744,7 +2744,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itsgoin-core"
|
name = "itsgoin-core"
|
||||||
version = "0.5.0"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
|
|
@ -2767,7 +2767,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itsgoin-desktop"
|
name = "itsgoin-desktop"
|
||||||
version = "0.5.0"
|
version = "0.4.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
|
|
|
||||||
|
|
@ -3868,15 +3868,12 @@ impl ConnectionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let our_profile = self.our_nat_profile();
|
|
||||||
let payload = RelayIntroducePayload {
|
let payload = RelayIntroducePayload {
|
||||||
intro_id,
|
intro_id,
|
||||||
target: *target,
|
target: *target,
|
||||||
requester: self.our_node_id,
|
requester: self.our_node_id,
|
||||||
requester_addresses: our_addrs,
|
requester_addresses: our_addrs,
|
||||||
ttl,
|
ttl,
|
||||||
nat_mapping: Some(our_profile.mapping.to_string()),
|
|
||||||
nat_filtering: Some(our_profile.filtering.to_string()),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let (mut send, mut recv) = pc.connection.open_bi().await?;
|
let (mut send, mut recv) = pc.connection.open_bi().await?;
|
||||||
|
|
@ -3912,8 +3909,6 @@ impl ConnectionManager {
|
||||||
target_addresses: vec![],
|
target_addresses: vec![],
|
||||||
relay_available: false,
|
relay_available: false,
|
||||||
reject_reason: Some("duplicate intro".to_string()),
|
reject_reason: Some("duplicate intro".to_string()),
|
||||||
nat_mapping: None,
|
|
||||||
nat_filtering: None,
|
|
||||||
};
|
};
|
||||||
write_typed_message(&mut send, MessageType::RelayIntroduceResult, &result).await?;
|
write_typed_message(&mut send, MessageType::RelayIntroduceResult, &result).await?;
|
||||||
send.finish()?;
|
send.finish()?;
|
||||||
|
|
@ -3937,15 +3932,12 @@ impl ConnectionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let our_profile = self.our_nat_profile();
|
|
||||||
let result = RelayIntroduceResultPayload {
|
let result = RelayIntroduceResultPayload {
|
||||||
intro_id: payload.intro_id,
|
intro_id: payload.intro_id,
|
||||||
accepted: true,
|
accepted: true,
|
||||||
target_addresses: our_addrs,
|
target_addresses: our_addrs,
|
||||||
relay_available: false,
|
relay_available: false,
|
||||||
reject_reason: None,
|
reject_reason: None,
|
||||||
nat_mapping: Some(our_profile.mapping.to_string()),
|
|
||||||
nat_filtering: Some(our_profile.filtering.to_string()),
|
|
||||||
};
|
};
|
||||||
write_typed_message(&mut send, MessageType::RelayIntroduceResult, &result).await?;
|
write_typed_message(&mut send, MessageType::RelayIntroduceResult, &result).await?;
|
||||||
send.finish()?;
|
send.finish()?;
|
||||||
|
|
@ -3971,20 +3963,7 @@ impl ConnectionManager {
|
||||||
let our_http_capable = self.http_capable;
|
let our_http_capable = self.http_capable;
|
||||||
let our_http_addr = self.http_addr.clone();
|
let our_http_addr = self.http_addr.clone();
|
||||||
let our_nat_profile = self.our_nat_profile();
|
let our_nat_profile = self.our_nat_profile();
|
||||||
// Prefer fresh NAT profile from relay payload over stale stored profile
|
let peer_nat_profile = {
|
||||||
let peer_nat_profile = if payload.nat_mapping.is_some() || payload.nat_filtering.is_some() {
|
|
||||||
let mapping = payload.nat_mapping.as_deref()
|
|
||||||
.map(crate::types::NatMapping::from_str_label)
|
|
||||||
.unwrap_or(crate::types::NatMapping::Unknown);
|
|
||||||
let filtering = payload.nat_filtering.as_deref()
|
|
||||||
.map(crate::types::NatFiltering::from_str_label)
|
|
||||||
.unwrap_or(crate::types::NatFiltering::Unknown);
|
|
||||||
let fresh = crate::types::NatProfile::new(mapping, filtering);
|
|
||||||
// Update storage with fresh profile
|
|
||||||
let s = self.storage.get().await;
|
|
||||||
let _ = s.set_peer_nat_profile(&requester, &fresh);
|
|
||||||
fresh
|
|
||||||
} else {
|
|
||||||
let s = self.storage.get().await;
|
let s = self.storage.get().await;
|
||||||
s.get_peer_nat_profile(&requester)
|
s.get_peer_nat_profile(&requester)
|
||||||
};
|
};
|
||||||
|
|
@ -4102,7 +4081,6 @@ impl ConnectionManager {
|
||||||
target_addresses: vec![],
|
target_addresses: vec![],
|
||||||
relay_available: false,
|
relay_available: false,
|
||||||
reject_reason: Some(format!("relay forward failed: {}", e)),
|
reject_reason: Some(format!("relay forward failed: {}", e)),
|
||||||
nat_mapping: None, nat_filtering: None,
|
|
||||||
};
|
};
|
||||||
write_typed_message(&mut send, MessageType::RelayIntroduceResult, &result).await?;
|
write_typed_message(&mut send, MessageType::RelayIntroduceResult, &result).await?;
|
||||||
send.finish()?;
|
send.finish()?;
|
||||||
|
|
@ -4177,7 +4155,6 @@ impl ConnectionManager {
|
||||||
target_addresses: vec![],
|
target_addresses: vec![],
|
||||||
relay_available: false,
|
relay_available: false,
|
||||||
reject_reason: Some(format!("relay forward to session failed: {}", e)),
|
reject_reason: Some(format!("relay forward to session failed: {}", e)),
|
||||||
nat_mapping: None, nat_filtering: None,
|
|
||||||
};
|
};
|
||||||
write_typed_message(&mut send, MessageType::RelayIntroduceResult, &result).await?;
|
write_typed_message(&mut send, MessageType::RelayIntroduceResult, &result).await?;
|
||||||
send.finish()?;
|
send.finish()?;
|
||||||
|
|
@ -4217,8 +4194,6 @@ impl ConnectionManager {
|
||||||
requester: payload.requester,
|
requester: payload.requester,
|
||||||
requester_addresses: req_addrs,
|
requester_addresses: req_addrs,
|
||||||
ttl: payload.ttl - 1,
|
ttl: payload.ttl - 1,
|
||||||
nat_mapping: payload.nat_mapping.clone(),
|
|
||||||
nat_filtering: payload.nat_filtering.clone(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let forward_result = async {
|
let forward_result = async {
|
||||||
|
|
@ -4271,7 +4246,6 @@ impl ConnectionManager {
|
||||||
target_addresses: vec![],
|
target_addresses: vec![],
|
||||||
relay_available: false,
|
relay_available: false,
|
||||||
reject_reason: Some("target not reachable through relay".to_string()),
|
reject_reason: Some("target not reachable through relay".to_string()),
|
||||||
nat_mapping: None, nat_filtering: None,
|
|
||||||
};
|
};
|
||||||
write_typed_message(&mut send, MessageType::RelayIntroduceResult, &result).await?;
|
write_typed_message(&mut send, MessageType::RelayIntroduceResult, &result).await?;
|
||||||
send.finish()?;
|
send.finish()?;
|
||||||
|
|
@ -4299,7 +4273,6 @@ impl ConnectionManager {
|
||||||
target_addresses: vec![],
|
target_addresses: vec![],
|
||||||
relay_available: false,
|
relay_available: false,
|
||||||
reject_reason: Some("relay at capacity".to_string()),
|
reject_reason: Some("relay at capacity".to_string()),
|
||||||
nat_mapping: None, nat_filtering: None,
|
|
||||||
};
|
};
|
||||||
write_typed_message(&mut requester_send, MessageType::RelayIntroduceResult, &result).await?;
|
write_typed_message(&mut requester_send, MessageType::RelayIntroduceResult, &result).await?;
|
||||||
requester_send.finish()?;
|
requester_send.finish()?;
|
||||||
|
|
@ -4320,7 +4293,6 @@ impl ConnectionManager {
|
||||||
target_addresses: vec![],
|
target_addresses: vec![],
|
||||||
relay_available: false,
|
relay_available: false,
|
||||||
reject_reason: Some("target not connected to relay".to_string()),
|
reject_reason: Some("target not connected to relay".to_string()),
|
||||||
nat_mapping: None, nat_filtering: None,
|
|
||||||
};
|
};
|
||||||
write_typed_message(&mut requester_send, MessageType::RelayIntroduceResult, &result).await?;
|
write_typed_message(&mut requester_send, MessageType::RelayIntroduceResult, &result).await?;
|
||||||
requester_send.finish()?;
|
requester_send.finish()?;
|
||||||
|
|
@ -5839,7 +5811,6 @@ impl ConnectionManager {
|
||||||
let result = RelayIntroduceResultPayload {
|
let result = RelayIntroduceResultPayload {
|
||||||
intro_id: payload.intro_id, accepted: false, target_addresses: vec![],
|
intro_id: payload.intro_id, accepted: false, target_addresses: vec![],
|
||||||
relay_available: false, reject_reason: Some("duplicate intro".to_string()),
|
relay_available: false, reject_reason: Some("duplicate intro".to_string()),
|
||||||
nat_mapping: None, nat_filtering: None,
|
|
||||||
};
|
};
|
||||||
write_typed_message(&mut send, MessageType::RelayIntroduceResult, &result).await?;
|
write_typed_message(&mut send, MessageType::RelayIntroduceResult, &result).await?;
|
||||||
send.finish()?;
|
send.finish()?;
|
||||||
|
|
@ -5865,22 +5836,7 @@ impl ConnectionManager {
|
||||||
let our_http_capable = cm.http_capable;
|
let our_http_capable = cm.http_capable;
|
||||||
let our_http_addr = cm.http_addr.clone();
|
let our_http_addr = cm.http_addr.clone();
|
||||||
let our_nat_profile = cm.our_nat_profile();
|
let our_nat_profile = cm.our_nat_profile();
|
||||||
// Prefer fresh NAT profile from relay payload over stale stored profile
|
let peer_nat_profile = { let s = cm.storage.get().await; s.get_peer_nat_profile(&payload.requester) };
|
||||||
let peer_nat_profile = if payload.nat_mapping.is_some() || payload.nat_filtering.is_some() {
|
|
||||||
let mapping = payload.nat_mapping.as_deref()
|
|
||||||
.map(crate::types::NatMapping::from_str_label)
|
|
||||||
.unwrap_or(crate::types::NatMapping::Unknown);
|
|
||||||
let filtering = payload.nat_filtering.as_deref()
|
|
||||||
.map(crate::types::NatFiltering::from_str_label)
|
|
||||||
.unwrap_or(crate::types::NatFiltering::Unknown);
|
|
||||||
let fresh = crate::types::NatProfile::new(mapping, filtering);
|
|
||||||
let s = cm.storage.get().await;
|
|
||||||
let _ = s.set_peer_nat_profile(&payload.requester, &fresh);
|
|
||||||
fresh
|
|
||||||
} else {
|
|
||||||
let s = cm.storage.get().await;
|
|
||||||
s.get_peer_nat_profile(&payload.requester)
|
|
||||||
};
|
|
||||||
Some(RelayGathered::WeAreTarget { our_addrs, endpoint, storage, our_node_id, our_nat_type, our_http_capable, our_http_addr, our_nat_profile, peer_nat_profile })
|
Some(RelayGathered::WeAreTarget { our_addrs, endpoint, storage, our_node_id, our_nat_type, our_http_capable, our_http_addr, our_nat_profile, peer_nat_profile })
|
||||||
} else {
|
} else {
|
||||||
// We are relay — gather target connection, requester observed addr, etc.
|
// We are relay — gather target connection, requester observed addr, etc.
|
||||||
|
|
@ -5910,8 +5866,6 @@ impl ConnectionManager {
|
||||||
let result = RelayIntroduceResultPayload {
|
let result = RelayIntroduceResultPayload {
|
||||||
intro_id: payload.intro_id, accepted: true, target_addresses: our_addrs,
|
intro_id: payload.intro_id, accepted: true, target_addresses: our_addrs,
|
||||||
relay_available: false, reject_reason: None,
|
relay_available: false, reject_reason: None,
|
||||||
nat_mapping: Some(our_nat_profile.mapping.to_string()),
|
|
||||||
nat_filtering: Some(our_nat_profile.filtering.to_string()),
|
|
||||||
};
|
};
|
||||||
write_typed_message(&mut send, MessageType::RelayIntroduceResult, &result).await?;
|
write_typed_message(&mut send, MessageType::RelayIntroduceResult, &result).await?;
|
||||||
send.finish()?;
|
send.finish()?;
|
||||||
|
|
@ -6000,7 +5954,6 @@ impl ConnectionManager {
|
||||||
let result = RelayIntroduceResultPayload {
|
let result = RelayIntroduceResultPayload {
|
||||||
intro_id: payload.intro_id, accepted: false, target_addresses: vec![],
|
intro_id: payload.intro_id, accepted: false, target_addresses: vec![],
|
||||||
relay_available: false, reject_reason: Some(format!("relay forward failed: {}", e_msg)),
|
relay_available: false, reject_reason: Some(format!("relay forward failed: {}", e_msg)),
|
||||||
nat_mapping: None, nat_filtering: None,
|
|
||||||
};
|
};
|
||||||
write_typed_message(&mut send, MessageType::RelayIntroduceResult, &result).await?;
|
write_typed_message(&mut send, MessageType::RelayIntroduceResult, &result).await?;
|
||||||
send.finish()?;
|
send.finish()?;
|
||||||
|
|
|
||||||
|
|
@ -349,11 +349,6 @@ impl Network {
|
||||||
self.device_role
|
self.device_role
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the explicit bind address (from --bind flag), if any.
|
|
||||||
pub fn bind_addr(&self) -> Option<std::net::SocketAddr> {
|
|
||||||
self.bind_addr
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether this node can serve HTTP (has TCP reachability).
|
/// Whether this node can serve HTTP (has TCP reachability).
|
||||||
pub fn is_http_capable(&self) -> bool {
|
pub fn is_http_capable(&self) -> bool {
|
||||||
self.has_upnp_tcp || self.has_public_v6 || self.bind_addr.is_some()
|
self.has_upnp_tcp || self.has_public_v6 || self.bind_addr.is_some()
|
||||||
|
|
@ -1957,13 +1952,7 @@ impl Network {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Re-check connection — peer may have connected to us while we were trying direct
|
// 4. Try relay introduction + hole punch (no lock during I/O)
|
||||||
if let Some(conn) = self.conn_handle.get_any_connection(peer_id).await {
|
|
||||||
debug!(peer = hex::encode(peer_id), "Peer connected to us while we were trying — using existing connection");
|
|
||||||
return Ok(conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Try relay introduction + hole punch (no lock during I/O)
|
|
||||||
let relay_candidates = self.conn_handle.find_relays_for(peer_id).await;
|
let relay_candidates = self.conn_handle.find_relays_for(peer_id).await;
|
||||||
|
|
||||||
if relay_candidates.is_empty() {
|
if relay_candidates.is_empty() {
|
||||||
|
|
@ -1986,28 +1975,10 @@ impl Network {
|
||||||
self.send_relay_introduce_standalone(relay_peer, peer_id, *ttl),
|
self.send_relay_introduce_standalone(relay_peer, peer_id, *ttl),
|
||||||
).await;
|
).await;
|
||||||
|
|
||||||
// Re-check: peer may have connected while relay intro was in flight
|
|
||||||
if let Some(conn) = self.conn_handle.get_any_connection(peer_id).await {
|
|
||||||
debug!(peer = hex::encode(peer_id), "Peer connected during relay intro — using existing");
|
|
||||||
return Ok(conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
match intro_result {
|
match intro_result {
|
||||||
Ok(Ok(ref result)) if result.accepted => {
|
Ok(Ok(result)) if result.accepted => {
|
||||||
let our_profile = self.conn_handle.our_nat_profile().await;
|
let our_profile = self.conn_handle.our_nat_profile().await;
|
||||||
// Prefer fresh NAT profile from relay response over stale stored profile
|
let peer_profile = {
|
||||||
let peer_profile = if result.nat_mapping.is_some() || result.nat_filtering.is_some() {
|
|
||||||
let mapping = result.nat_mapping.as_deref()
|
|
||||||
.map(crate::types::NatMapping::from_str_label)
|
|
||||||
.unwrap_or(crate::types::NatMapping::Unknown);
|
|
||||||
let filtering = result.nat_filtering.as_deref()
|
|
||||||
.map(crate::types::NatFiltering::from_str_label)
|
|
||||||
.unwrap_or(crate::types::NatFiltering::Unknown);
|
|
||||||
let fresh = crate::types::NatProfile::new(mapping, filtering);
|
|
||||||
let s = self.storage.get().await;
|
|
||||||
let _ = s.set_peer_nat_profile(peer_id, &fresh);
|
|
||||||
fresh
|
|
||||||
} else {
|
|
||||||
let s = self.storage.get().await;
|
let s = self.storage.get().await;
|
||||||
s.get_peer_nat_profile(peer_id)
|
s.get_peer_nat_profile(peer_id)
|
||||||
};
|
};
|
||||||
|
|
@ -2303,15 +2274,12 @@ impl Network {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let our_profile = self.conn_handle.our_nat_profile().await;
|
|
||||||
let payload = crate::protocol::RelayIntroducePayload {
|
let payload = crate::protocol::RelayIntroducePayload {
|
||||||
intro_id,
|
intro_id,
|
||||||
target: *target,
|
target: *target,
|
||||||
requester: self.our_node_id,
|
requester: self.our_node_id,
|
||||||
requester_addresses: our_addrs,
|
requester_addresses: our_addrs,
|
||||||
ttl,
|
ttl,
|
||||||
nat_mapping: Some(our_profile.mapping.to_string()),
|
|
||||||
nat_filtering: Some(our_profile.filtering.to_string()),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let (mut send, mut recv) = conn.open_bi().await?;
|
let (mut send, mut recv) = conn.open_bi().await?;
|
||||||
|
|
|
||||||
|
|
@ -461,12 +461,6 @@ pub struct RelayIntroducePayload {
|
||||||
pub requester_addresses: Vec<String>,
|
pub requester_addresses: Vec<String>,
|
||||||
/// Max forwarding hops remaining (0 = relay must know target directly)
|
/// Max forwarding hops remaining (0 = relay must know target directly)
|
||||||
pub ttl: u8,
|
pub ttl: u8,
|
||||||
/// Requester's current NAT mapping type (for hole punch strategy)
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub nat_mapping: Option<String>,
|
|
||||||
/// Requester's current NAT filtering type
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub nat_filtering: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Target's response to a relay introduction (bi-stream response)
|
/// Target's response to a relay introduction (bi-stream response)
|
||||||
|
|
@ -478,12 +472,6 @@ pub struct RelayIntroduceResultPayload {
|
||||||
/// Relay is willing to serve as stream relay fallback
|
/// Relay is willing to serve as stream relay fallback
|
||||||
pub relay_available: bool,
|
pub relay_available: bool,
|
||||||
pub reject_reason: Option<String>,
|
pub reject_reason: Option<String>,
|
||||||
/// Target's current NAT mapping type (for hole punch strategy)
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub nat_mapping: Option<String>,
|
|
||||||
/// Target's current NAT filtering type
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub nat_filtering: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open a relay pipe — intermediary splices two bi-streams (bi-stream)
|
/// Open a relay pipe — intermediary splices two bi-streams (bi-stream)
|
||||||
|
|
@ -870,8 +858,6 @@ mod tests {
|
||||||
requester: [2u8; 32],
|
requester: [2u8; 32],
|
||||||
requester_addresses: vec!["10.0.0.2:4433".to_string()],
|
requester_addresses: vec!["10.0.0.2:4433".to_string()],
|
||||||
ttl: 1,
|
ttl: 1,
|
||||||
nat_mapping: Some("eim".to_string()),
|
|
||||||
nat_filtering: Some("open".to_string()),
|
|
||||||
};
|
};
|
||||||
let json = serde_json::to_string(&payload).unwrap();
|
let json = serde_json::to_string(&payload).unwrap();
|
||||||
let decoded: RelayIntroducePayload = serde_json::from_str(&json).unwrap();
|
let decoded: RelayIntroducePayload = serde_json::from_str(&json).unwrap();
|
||||||
|
|
@ -890,8 +876,6 @@ mod tests {
|
||||||
target_addresses: vec!["10.0.0.1:4433".to_string(), "192.168.1.1:4433".to_string()],
|
target_addresses: vec!["10.0.0.1:4433".to_string(), "192.168.1.1:4433".to_string()],
|
||||||
relay_available: true,
|
relay_available: true,
|
||||||
reject_reason: None,
|
reject_reason: None,
|
||||||
nat_mapping: Some("eim".to_string()),
|
|
||||||
nat_filtering: Some("open".to_string()),
|
|
||||||
};
|
};
|
||||||
let json = serde_json::to_string(&payload).unwrap();
|
let json = serde_json::to_string(&payload).unwrap();
|
||||||
let decoded: RelayIntroduceResultPayload = serde_json::from_str(&json).unwrap();
|
let decoded: RelayIntroduceResultPayload = serde_json::from_str(&json).unwrap();
|
||||||
|
|
@ -908,8 +892,6 @@ mod tests {
|
||||||
target_addresses: vec![],
|
target_addresses: vec![],
|
||||||
relay_available: false,
|
relay_available: false,
|
||||||
reject_reason: Some("target not reachable".to_string()),
|
reject_reason: Some("target not reachable".to_string()),
|
||||||
nat_mapping: None,
|
|
||||||
nat_filtering: None,
|
|
||||||
};
|
};
|
||||||
let json2 = serde_json::to_string(&rejected).unwrap();
|
let json2 = serde_json::to_string(&rejected).unwrap();
|
||||||
let decoded2: RelayIntroduceResultPayload = serde_json::from_str(&json2).unwrap();
|
let decoded2: RelayIntroduceResultPayload = serde_json::from_str(&json2).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -103,9 +103,8 @@ fn parse_xor_mapped_address(resp: &[u8], txn_id: &[u8; 12]) -> Option<SocketAddr
|
||||||
/// Query a single STUN server and return the mapped address.
|
/// Query a single STUN server and return the mapped address.
|
||||||
async fn stun_query(sock: &UdpSocket, server: &str) -> Option<SocketAddr> {
|
async fn stun_query(sock: &UdpSocket, server: &str) -> Option<SocketAddr> {
|
||||||
use std::net::ToSocketAddrs;
|
use std::net::ToSocketAddrs;
|
||||||
// Filter for IPv4 since our socket is bound to 0.0.0.0 (IPv4)
|
|
||||||
let server_addr = match server.to_socket_addrs() {
|
let server_addr = match server.to_socket_addrs() {
|
||||||
Ok(addrs) => addrs.filter(|a| a.is_ipv4()).next()?,
|
Ok(mut addrs) => addrs.next()?,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!(server, error = %e, "STUN DNS resolution failed");
|
debug!(server, error = %e, "STUN DNS resolution failed");
|
||||||
return None;
|
return None;
|
||||||
|
|
|
||||||
|
|
@ -1623,120 +1623,6 @@ async fn get_network_summary(state: State<'_, AppNode>) -> Result<NetworkSummary
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct OurInfoDto {
|
|
||||||
node_id: String,
|
|
||||||
addresses: Vec<AddressInfoDto>,
|
|
||||||
nat_type: String,
|
|
||||||
device_role: String,
|
|
||||||
upnp: bool,
|
|
||||||
http_capable: bool,
|
|
||||||
http_addr: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct AddressInfoDto {
|
|
||||||
addr: String,
|
|
||||||
family: String, // "IPv4" or "IPv6"
|
|
||||||
status: String, // "Public", "NAT (easy)", "NAT (hard)", "UPnP", "LAN", "Server"
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn get_our_info(state: State<'_, AppNode>) -> Result<OurInfoDto, String> {
|
|
||||||
let node = get_node(&state).await;
|
|
||||||
let net = &node.network;
|
|
||||||
let nat_type = net.conn_handle().nat_type().await;
|
|
||||||
let has_upnp = net.has_upnp();
|
|
||||||
let _has_public_v6 = net.has_public_v6();
|
|
||||||
let bind_addr = net.bind_addr();
|
|
||||||
|
|
||||||
let mut addresses = Vec::new();
|
|
||||||
|
|
||||||
// Collect bound socket addresses (these are our local interfaces)
|
|
||||||
let bound: std::collections::HashSet<String> = net.bound_sockets()
|
|
||||||
.iter()
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Gather bound (local) addresses with classification
|
|
||||||
for sock in net.bound_sockets() {
|
|
||||||
if sock.ip().is_loopback() || sock.ip().is_unspecified() { continue; }
|
|
||||||
let family = if sock.ip().is_ipv4() { "IPv4" } else { "IPv6" };
|
|
||||||
let status = classify_addr(&sock, &nat_type, has_upnp, bind_addr.is_some(), false);
|
|
||||||
addresses.push(AddressInfoDto {
|
|
||||||
addr: sock.to_string(),
|
|
||||||
family: family.to_string(),
|
|
||||||
status,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add UPnP external address if different from bound
|
|
||||||
if let Some(ref mapping) = net.upnp_mapping() {
|
|
||||||
let ext = mapping.external_addr;
|
|
||||||
if !addresses.iter().any(|a| a.addr == ext.to_string()) {
|
|
||||||
addresses.insert(0, AddressInfoDto {
|
|
||||||
addr: ext.to_string(),
|
|
||||||
family: if ext.ip().is_ipv4() { "IPv4" } else { "IPv6" }.to_string(),
|
|
||||||
status: "UPnP external".to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add iroh-discovered addresses not already listed (STUN-observed externals)
|
|
||||||
for sock in net.endpoint_addr().ip_addrs() {
|
|
||||||
if sock.ip().is_loopback() || sock.ip().is_unspecified() { continue; }
|
|
||||||
let s = sock.to_string();
|
|
||||||
if addresses.iter().any(|a| a.addr == s) { continue; }
|
|
||||||
let family = if sock.ip().is_ipv4() { "IPv4" } else { "IPv6" };
|
|
||||||
let is_observed = !bound.contains(&s);
|
|
||||||
let status = classify_addr(sock, &nat_type, has_upnp, bind_addr.is_some(), is_observed);
|
|
||||||
addresses.push(AddressInfoDto {
|
|
||||||
addr: s,
|
|
||||||
family: family.to_string(),
|
|
||||||
status,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(OurInfoDto {
|
|
||||||
node_id: hex::encode(net.node_id_bytes()),
|
|
||||||
addresses,
|
|
||||||
nat_type: nat_type.to_string(),
|
|
||||||
device_role: format!("{:?}", net.device_role()),
|
|
||||||
upnp: has_upnp,
|
|
||||||
http_capable: net.is_http_capable(),
|
|
||||||
http_addr: net.http_addr(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn classify_addr(sock: &std::net::SocketAddr, nat_type: &itsgoin_core::types::NatType, has_upnp: bool, is_server: bool, is_observed: bool) -> String {
|
|
||||||
use std::net::IpAddr;
|
|
||||||
let ip = sock.ip();
|
|
||||||
let is_v6 = ip.is_ipv6();
|
|
||||||
let is_pub = match ip {
|
|
||||||
IpAddr::V4(v4) => !v4.is_private() && !v4.is_loopback() && !v4.is_link_local(),
|
|
||||||
IpAddr::V6(v6) => !v6.is_loopback() && {
|
|
||||||
let seg = v6.segments();
|
|
||||||
seg[0] & 0xfe00 != 0xfe00 && seg[0] & 0xffc0 != 0xfe80
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if is_server { return "Server".to_string(); }
|
|
||||||
if is_pub {
|
|
||||||
if is_observed && !is_v6 {
|
|
||||||
return match nat_type {
|
|
||||||
itsgoin_core::types::NatType::Public |
|
|
||||||
itsgoin_core::types::NatType::Easy => "External · easy punch".to_string(),
|
|
||||||
itsgoin_core::types::NatType::Hard => "External · hard punch (scan)".to_string(),
|
|
||||||
itsgoin_core::types::NatType::Unknown => "External".to_string(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return "Public".to_string();
|
|
||||||
}
|
|
||||||
if is_v6 { return "Link-local".to_string(); }
|
|
||||||
"LAN".to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct ActivityEventDto {
|
struct ActivityEventDto {
|
||||||
|
|
@ -2501,7 +2387,6 @@ pub fn run() {
|
||||||
sync_all,
|
sync_all,
|
||||||
sync_from_peer,
|
sync_from_peer,
|
||||||
get_network_summary,
|
get_network_summary,
|
||||||
get_our_info,
|
|
||||||
get_activity_log,
|
get_activity_log,
|
||||||
trigger_rebalance,
|
trigger_rebalance,
|
||||||
request_referrals,
|
request_referrals,
|
||||||
|
|
|
||||||
|
|
@ -2986,14 +2986,9 @@ function openDiagnostics() {
|
||||||
<button id="request-referrals-btn" class="btn btn-ghost btn-sm">Request Referrals</button>
|
<button id="request-referrals-btn" class="btn btn-ghost btn-sm">Request Referrals</button>
|
||||||
</div>
|
</div>
|
||||||
<div style="display:flex;gap:0.5rem;margin-top:0.5rem;flex-wrap:wrap;justify-content:center">
|
<div style="display:flex;gap:0.5rem;margin-top:0.5rem;flex-wrap:wrap;justify-content:center">
|
||||||
<button id="show-ourinfo-btn" class="btn btn-ghost btn-sm">Our Info</button>
|
|
||||||
<button id="show-connections-btn" class="btn btn-ghost btn-sm">Show Connections</button>
|
<button id="show-connections-btn" class="btn btn-ghost btn-sm">Show Connections</button>
|
||||||
<button id="show-anchors-btn" class="btn btn-ghost btn-sm">Stored Anchors</button>
|
<button id="show-anchors-btn" class="btn btn-ghost btn-sm">Stored Anchors</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="ourinfo-section" class="hidden">
|
|
||||||
<h4 class="subsection-title">Our Info</h4>
|
|
||||||
<div id="ourinfo-content"></div>
|
|
||||||
</div>
|
|
||||||
<div id="connections-section" class="hidden">
|
<div id="connections-section" class="hidden">
|
||||||
<h4 class="subsection-title">Mesh & Session Connections</h4>
|
<h4 class="subsection-title">Mesh & Session Connections</h4>
|
||||||
<div id="connections-list"></div>
|
<div id="connections-list"></div>
|
||||||
|
|
@ -3011,45 +3006,6 @@ function openDiagnostics() {
|
||||||
networkSummaryEl = $('#network-summary');
|
networkSummaryEl = $('#network-summary');
|
||||||
connectionsList = $('#connections-list');
|
connectionsList = $('#connections-list');
|
||||||
peersList = null; // Known peers removed
|
peersList = null; // Known peers removed
|
||||||
// Wire Our Info toggle
|
|
||||||
$('#show-ourinfo-btn').addEventListener('click', async () => {
|
|
||||||
const section = $('#ourinfo-section');
|
|
||||||
const btn = $('#show-ourinfo-btn');
|
|
||||||
if (section.classList.contains('hidden')) {
|
|
||||||
section.classList.remove('hidden');
|
|
||||||
btn.textContent = 'Hide Our Info';
|
|
||||||
try {
|
|
||||||
const info = await invoke('get_our_info');
|
|
||||||
const httpVal = info.httpAddr || 'No';
|
|
||||||
let html = `<div class="diag-grid" style="margin-bottom:0.5rem">
|
|
||||||
<div class="diag-item"><span class="diag-label">NAT Type</span><span class="diag-value" style="font-size:0.85rem">${info.natType}</span></div>
|
|
||||||
<div class="diag-item"><span class="diag-label">Role</span><span class="diag-value" style="font-size:0.85rem">${info.deviceRole}</span></div>
|
|
||||||
<div class="diag-item"><span class="diag-label">UPnP</span><span class="diag-value" style="font-size:0.85rem">${info.upnp ? 'Yes' : 'No'}</span></div>
|
|
||||||
</div>
|
|
||||||
<div style="text-align:center;margin-bottom:0.75rem">
|
|
||||||
<span style="color:#888;font-size:0.75rem">HTTP</span>
|
|
||||||
<span style="color:#ccc;font-size:0.8rem;margin-left:0.5rem">${httpVal}</span>
|
|
||||||
</div>`;
|
|
||||||
html += '<div style="font-size:0.8rem">';
|
|
||||||
for (const a of info.addresses) {
|
|
||||||
const color = a.status.includes('Public') || a.status.includes('punchable') || a.status.includes('Server') ? '#7fdbca' :
|
|
||||||
a.status.includes('UPnP') || a.status.includes('External') ? '#5b8def' : '#888';
|
|
||||||
html += `<div style="padding:0.3rem 0;border-bottom:1px solid #1a1a2e">
|
|
||||||
<div style="color:#ccc;font-family:monospace;font-size:0.7rem;word-break:break-all">${a.addr}</div>
|
|
||||||
<div style="color:${color};font-size:0.7rem;margin-top:0.1rem">${a.family} · ${a.status}</div>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
html += '</div>';
|
|
||||||
html += `<p style="color:#555;font-size:0.7rem;text-align:center;margin-top:0.5rem;word-break:break-all">Node: ${info.nodeId.substring(0, 16)}…</p>`;
|
|
||||||
$('#ourinfo-content').innerHTML = html;
|
|
||||||
} catch (e) {
|
|
||||||
$('#ourinfo-content').innerHTML = `<p class="empty-hint">Failed to load: ${e}</p>`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
section.classList.add('hidden');
|
|
||||||
btn.textContent = 'Our Info';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Wire connections toggle
|
// Wire connections toggle
|
||||||
$('#show-connections-btn').addEventListener('click', () => {
|
$('#show-connections-btn').addEventListener('click', () => {
|
||||||
const section = $('#connections-section');
|
const section = $('#connections-section');
|
||||||
|
|
@ -3614,29 +3570,10 @@ async function init() {
|
||||||
}
|
}
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
|
||||||
// Ready button — click to go to feed
|
|
||||||
const readyBtn = document.getElementById('welcome-ready-btn');
|
|
||||||
const readyBar = document.getElementById('welcome-ready-bar');
|
|
||||||
if (readyBtn) {
|
|
||||||
readyBtn.addEventListener('click', () => {
|
|
||||||
if (readyBtn.disabled) return;
|
|
||||||
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
||||||
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
|
|
||||||
const feedTab = document.querySelector('.tab[data-tab="feed"]');
|
|
||||||
if (feedTab) feedTab.classList.add('active');
|
|
||||||
document.getElementById('view-feed').classList.add('active');
|
|
||||||
currentTab = 'feed';
|
|
||||||
_lastFeedViewMs = Date.now();
|
|
||||||
updateTabBadge('feed', 0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for backend in the background, then load node info
|
// Wait for backend in the background, then load node info
|
||||||
(async () => {
|
(async () => {
|
||||||
for (let attempt = 0; attempt < 30; attempt++) {
|
for (let attempt = 0; attempt < 30; attempt++) {
|
||||||
try {
|
try {
|
||||||
// Animate progress bar toward 90% during readiness checks
|
|
||||||
if (readyBar) readyBar.style.width = Math.min(90, (attempt + 1) * 3) + '%';
|
|
||||||
await invoke('get_node_info');
|
await invoke('get_node_info');
|
||||||
break;
|
break;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -3649,19 +3586,9 @@ async function init() {
|
||||||
setupOverlay.classList.remove('hidden');
|
setupOverlay.classList.remove('hidden');
|
||||||
setupName.focus();
|
setupName.focus();
|
||||||
}
|
}
|
||||||
// Pre-load feed + messages from local DB (instant — no network needed)
|
// Reload feed now that backend is ready
|
||||||
await loadFeed(true).catch(() => {});
|
loadFeed(true).catch(() => {});
|
||||||
loadMessages(true).catch(() => {});
|
loadMessages(true).catch(() => {});
|
||||||
// Mark ready button as clickable
|
|
||||||
if (readyBar) readyBar.style.width = '100%';
|
|
||||||
if (readyBtn) {
|
|
||||||
readyBtn.disabled = false;
|
|
||||||
readyBtn.textContent = 'Ready — Go to Feed';
|
|
||||||
readyBtn.style.opacity = '1';
|
|
||||||
readyBtn.style.color = '#7fdbca';
|
|
||||||
readyBtn.style.borderColor = '#7fdbca';
|
|
||||||
readyBtn.style.cursor = 'pointer';
|
|
||||||
}
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Mark notif ready after first welcome fetch succeeds (skip first 2 ticks to avoid spam)
|
// Mark notif ready after first welcome fetch succeeds (skip first 2 ticks to avoid spam)
|
||||||
|
|
|
||||||
|
|
@ -49,12 +49,6 @@
|
||||||
<div><span id="welcome-reacts" style="font-size:1.4rem;font-weight:700;color:#5b8def;display:block">-</span>Reacts</div>
|
<div><span id="welcome-reacts" style="font-size:1.4rem;font-weight:700;color:#5b8def;display:block">-</span>Reacts</div>
|
||||||
<div><span id="welcome-comments" style="font-size:1.4rem;font-weight:700;color:#5b8def;display:block">-</span>Comments</div>
|
<div><span id="welcome-comments" style="font-size:1.4rem;font-weight:700;color:#5b8def;display:block">-</span>Comments</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-top:2rem;max-width:280px;margin-left:auto;margin-right:auto">
|
|
||||||
<div style="background:#1a1a2e;border-radius:6px;height:6px;overflow:hidden;margin-bottom:0.75rem">
|
|
||||||
<div id="welcome-ready-bar" style="height:100%;background:#7fdbca;width:0%;transition:width 0.5s ease;border-radius:6px"></div>
|
|
||||||
</div>
|
|
||||||
<button id="welcome-ready-btn" disabled style="width:100%;padding:0.75rem 1.5rem;border:1px solid #333;border-radius:8px;background:#1a1a2e;color:#666;font-size:0.95rem;cursor:not-allowed;opacity:0.5;transition:all 0.3s ease">Loading...</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue