ItsGoin v0.3.2 — Decentralized social media network
No central server, user-owned data, reverse-chronological feed. Rust core + Tauri desktop + Android app + plain HTML/CSS/JS frontend. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
800388cda4
146 changed files with 53227 additions and 0 deletions
243
crates/core/src/upnp.rs
Normal file
243
crates/core/src/upnp.rs
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
//! Best-effort UPnP port mapping for NAT traversal.
|
||||
//! Skipped entirely on mobile platforms where UPnP is unsupported.
|
||||
|
||||
use std::net::SocketAddr;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use tracing::{info, debug};
|
||||
|
||||
/// Result of a successful UPnP port mapping.
|
||||
pub struct UpnpMapping {
|
||||
pub external_addr: SocketAddr,
|
||||
pub lease_secs: u32,
|
||||
pub local_port: u16,
|
||||
}
|
||||
|
||||
/// Best-effort UPnP port mapping.
|
||||
/// 3s gateway discovery timeout, 1800s (30 min) lease, UDP protocol.
|
||||
/// Returns None on any failure (no router, unsupported, timeout, port conflict).
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub async fn try_upnp_mapping(local_port: u16) -> Option<UpnpMapping> {
|
||||
use igd_next::SearchOptions;
|
||||
|
||||
let search_opts = SearchOptions {
|
||||
timeout: Some(std::time::Duration::from_secs(3)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let gateway = match igd_next::aio::tokio::search_gateway(search_opts).await {
|
||||
Ok(gw) => gw,
|
||||
Err(e) => {
|
||||
debug!("UPnP gateway discovery failed (expected behind non-UPnP router): {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let external_ip = match gateway.get_external_ip().await {
|
||||
Ok(ip) => ip,
|
||||
Err(e) => {
|
||||
debug!("UPnP: could not get external IP: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Local address for the mapping — bind to all interfaces
|
||||
let local_addr = SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED), local_port);
|
||||
let lease_secs: u32 = 1800; // 30 minutes
|
||||
|
||||
// Try mapping the same external port first
|
||||
let result = gateway.add_port(
|
||||
igd_next::PortMappingProtocol::UDP,
|
||||
local_port,
|
||||
local_addr,
|
||||
lease_secs,
|
||||
"itsgoin",
|
||||
).await;
|
||||
|
||||
let external_port = match result {
|
||||
Ok(()) => local_port,
|
||||
Err(_) => {
|
||||
// Port taken — try any available port
|
||||
match gateway.add_any_port(
|
||||
igd_next::PortMappingProtocol::UDP,
|
||||
local_addr,
|
||||
lease_secs,
|
||||
"itsgoin",
|
||||
).await {
|
||||
Ok(port) => port,
|
||||
Err(e) => {
|
||||
debug!("UPnP: port mapping failed: {}", e);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let external_addr = SocketAddr::new(external_ip, external_port);
|
||||
info!("UPnP: mapped {}:{} → :{}", external_ip, external_port, local_port);
|
||||
|
||||
Some(UpnpMapping {
|
||||
external_addr,
|
||||
lease_secs,
|
||||
local_port,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
pub async fn try_upnp_mapping(_local_port: u16) -> Option<UpnpMapping> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Renew an existing UPnP lease. Returns true on success.
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub async fn renew_upnp_mapping(local_port: u16, external_port: u16) -> bool {
|
||||
use igd_next::SearchOptions;
|
||||
|
||||
let search_opts = SearchOptions {
|
||||
timeout: Some(std::time::Duration::from_secs(3)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let gateway = match igd_next::aio::tokio::search_gateway(search_opts).await {
|
||||
Ok(gw) => gw,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
let local_addr = SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED), local_port);
|
||||
gateway.add_port(
|
||||
igd_next::PortMappingProtocol::UDP,
|
||||
external_port,
|
||||
local_addr,
|
||||
1800,
|
||||
"itsgoin",
|
||||
).await.is_ok()
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
pub async fn renew_upnp_mapping(_local_port: u16, _external_port: u16) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Remove UPnP mapping on shutdown. Best-effort, errors are silently ignored.
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub async fn remove_upnp_mapping(external_port: u16) {
|
||||
use igd_next::SearchOptions;
|
||||
|
||||
let search_opts = SearchOptions {
|
||||
timeout: Some(std::time::Duration::from_secs(3)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if let Ok(gateway) = igd_next::aio::tokio::search_gateway(search_opts).await {
|
||||
let _ = gateway.remove_port(igd_next::PortMappingProtocol::UDP, external_port).await;
|
||||
info!("UPnP: removed port mapping for external port {}", external_port);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
pub async fn remove_upnp_mapping(_external_port: u16) {}
|
||||
|
||||
// --- TCP port mapping (for HTTP post delivery) ---
|
||||
|
||||
/// Best-effort UPnP TCP port mapping on the same port as QUIC UDP.
|
||||
/// Returns true on success. Reuses the already-discovered gateway.
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub async fn try_upnp_tcp_mapping(local_port: u16, external_port: u16) -> bool {
|
||||
use igd_next::SearchOptions;
|
||||
|
||||
let search_opts = SearchOptions {
|
||||
timeout: Some(std::time::Duration::from_secs(3)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let gateway = match igd_next::aio::tokio::search_gateway(search_opts).await {
|
||||
Ok(gw) => gw,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
let local_addr = SocketAddr::new(
|
||||
std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED),
|
||||
local_port,
|
||||
);
|
||||
|
||||
match gateway
|
||||
.add_port(
|
||||
igd_next::PortMappingProtocol::TCP,
|
||||
external_port,
|
||||
local_addr,
|
||||
1800,
|
||||
"itsgoin-http",
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => {
|
||||
info!("UPnP: TCP port {} mapped for HTTP serving", external_port);
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("UPnP: TCP port mapping failed (non-fatal): {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
pub async fn try_upnp_tcp_mapping(_local_port: u16, _external_port: u16) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Renew an existing UPnP TCP lease. Returns true on success.
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub async fn renew_upnp_tcp_mapping(local_port: u16, external_port: u16) -> bool {
|
||||
use igd_next::SearchOptions;
|
||||
|
||||
let search_opts = SearchOptions {
|
||||
timeout: Some(std::time::Duration::from_secs(3)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let gateway = match igd_next::aio::tokio::search_gateway(search_opts).await {
|
||||
Ok(gw) => gw,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
let local_addr = SocketAddr::new(
|
||||
std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED),
|
||||
local_port,
|
||||
);
|
||||
gateway
|
||||
.add_port(
|
||||
igd_next::PortMappingProtocol::TCP,
|
||||
external_port,
|
||||
local_addr,
|
||||
1800,
|
||||
"itsgoin-http",
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
pub async fn renew_upnp_tcp_mapping(_local_port: u16, _external_port: u16) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Remove UPnP TCP mapping on shutdown.
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub async fn remove_upnp_tcp_mapping(external_port: u16) {
|
||||
use igd_next::SearchOptions;
|
||||
|
||||
let search_opts = SearchOptions {
|
||||
timeout: Some(std::time::Duration::from_secs(3)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if let Ok(gateway) = igd_next::aio::tokio::search_gateway(search_opts).await {
|
||||
let _ = gateway
|
||||
.remove_port(igd_next::PortMappingProtocol::TCP, external_port)
|
||||
.await;
|
||||
info!("UPnP: removed TCP port mapping for port {}", external_port);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
pub async fn remove_upnp_tcp_mapping(_external_port: u16) {}
|
||||
Loading…
Add table
Add a link
Reference in a new issue