Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,513 changes: 1,141 additions & 372 deletions Cargo.lock

Large diffs are not rendered by default.

64 changes: 24 additions & 40 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,34 @@ version = "0.0.1"
edition = "2024"

[dependencies]
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"
bytes = "1.11.0"
anyhow = "1.0.100"
tokio = { version = "1.48.0", features = ["full"] }
tracing = "*"
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
time = "0.3.44"
tun= { version = "0.8.5", features = ["futures-core", "async"] }
async-trait = "0.1.89"
ed25519-dalek = { version = "2.1", features = ["rand_core"] }
rand = "0.9.2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
bytes = "1"
anyhow = "1"
tokio = { version = "1", features = ["full"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
time = "0.3"
tun= { version = "0.8", features = ["futures-core", "async"] }
async-trait = "0.1"
ed25519-dalek = { version = "2", features = ["rand_core"] }
rand = "0.10"
base64 = "0.22"
aes-gcm = "0.10"
chacha20poly1305 = "0.10"
toml = "0.9.8"
ipnet = "2.10"
clap = { version = "4.5", features = ["derive"] }
ureq = "2.10"
toml = "0.9"
ipnet = "2"
clap = { version = "4", features = ["derive"] }
stunclient = "0.4"
socket2 = "0.6.1"
notify = "7.0"
tokio-util = "0.7.17"
ctrlc2 = "3.7.3"
axum = "0.7"
tower = "0.4"
tower-http = { version = "0.5", features = ["cors"] }
once_cell = "1.20"
socket2 = "0.6"
notify = "8"
tokio-util = "0.7"
ctrlc2 = "3"
axum = "0.8"
tower = "0.5"
tower-http = { version = "0.6", features = ["cors"] }
once_cell = "1"
reqwest = "0.13"

[dev-dependencies]
tokio-test = "0.4"

[[bin]]
name = "server"
path = "src/cmd/server.rs"

[[bin]]
name = "client"
path = "src/cmd/client.rs"

[[example]]
name = "stun_discover"
path = "examples/stun_discover.rs"

[[example]]
name = "tun_reader"
path = "examples/read_async.rs"
12 changes: 3 additions & 9 deletions examples/read_async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.

use std::thread::{sleep, Builder};
use std::time::Duration;
use tokio::io::AsyncReadExt;
use tokio_util::sync::CancellationToken;
use tun::{AbstractDevice, BoxError};

#[tokio::main]
Expand Down Expand Up @@ -45,11 +43,7 @@ async fn main_entry() -> Result<(), BoxError> {
let size = dev.mtu()? as usize + tun::PACKET_INFORMATION_LENGTH;
let mut buf = vec![0; size];
loop {
tokio::select! {
len = dev.read(&mut buf) => {
println!("pkt: {:?}", &buf[..len?]);
}
};
let len = dev.read(&mut buf).await?;
println!("pkt: {:?}", &buf[..len]);
}
Ok(())
}
}
46 changes: 25 additions & 21 deletions examples/stun_discover.rs → examples/tun_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//! cargo run --example stun_discover -- --port 51258
//! ```

use rustun::client::p2p::stun::{StunClient, NatType};
use rustun::client::p2p::stun::{NatType, StunClient};
use std::time::Duration;

#[derive(clap::Parser, Debug)]
Expand All @@ -19,15 +19,15 @@ struct Args {
/// Local UDP port to bind (0 for automatic)
#[arg(short, long, default_value = "0")]
port: u16,

/// Custom STUN server (can be specified multiple times)
#[arg(short, long)]
stun_server: Vec<String>,

/// Request timeout in seconds
#[arg(short, long, default_value = "5")]
timeout: u64,

/// Enable verbose logging
#[arg(short, long)]
verbose: bool,
Expand All @@ -37,16 +37,14 @@ struct Args {
async fn main() {
use clap::Parser;
let args = Args::parse();

// Setup logging
let log_level = if args.verbose { "debug" } else { "info" };
tracing_subscriber::fmt()
.with_env_filter(log_level)
.init();

tracing_subscriber::fmt().with_env_filter(log_level).init();

println!("🔍 STUN Discovery Tool");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");

// Create STUN client
let stun_client = if args.stun_server.is_empty() {
println!("📡 Using default Google STUN servers");
Expand All @@ -55,35 +53,42 @@ async fn main() {
println!("📡 Using custom STUN servers: {:?}", args.stun_server);
StunClient::with_servers(args.stun_server)
};

let stun_client = stun_client.with_timeout(Duration::from_secs(args.timeout));

println!("🔌 Local port: {}", if args.port == 0 { "auto".to_string() } else { args.port.to_string() });

println!(
"🔌 Local port: {}",
if args.port == 0 {
"auto".to_string()
} else {
args.port.to_string()
}
);
println!();

// Perform discovery
println!("⏳ Discovering public address...");
match stun_client.discover(args.port).await {
Ok(result) => {
println!("✅ STUN Discovery Successful!\n");

println!("📍 Results:");
println!(" Local Address: {}", result.local_addr);
println!(" Public Address: {}", result.public_addr());
println!(" Public IP: {}", result.public_ip);
println!(" Public Port: {}", result.public_port);
println!();

println!("🌐 NAT Information:");
println!(" Type: {:?}", result.nat_type);
println!(" Description: {}", result.nat_type.description());
println!();

// Show P2P compatibility
println!("🔗 P2P Compatibility:");
show_p2p_compatibility(&result.nat_type);
println!();

// Recommendations
println!("💡 Recommendations:");
match result.nat_type {
Expand Down Expand Up @@ -117,7 +122,7 @@ async fn main() {
}
}
Err(e) => {
eprintln!("❌ STUN Discovery Failed: {}", e);
eprintln!("❌ STUN Discovery Failed: {e}");
eprintln!();
eprintln!("Possible reasons:");
eprintln!(" - No internet connection");
Expand All @@ -137,7 +142,7 @@ fn show_p2p_compatibility(nat_type: &NatType) {
("Port-Restricted", NatType::PortRestricted),
("Symmetric NAT", NatType::Symmetric),
];

println!(" Success rates with different peer NAT types:");
for (name, peer_nat) in &scenarios {
let rate = nat_type.hole_punch_success_rate(peer_nat);
Expand All @@ -146,4 +151,3 @@ fn show_p2p_compatibility(nat_type: &NatType) {
println!(" {:18} {:3}% {}", name, percentage, bar);
}
}

6 changes: 6 additions & 0 deletions src/bin/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use rustun::client::main;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
main::run_client().await
}
6 changes: 6 additions & 0 deletions src/bin/server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use rustun::server::main;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
main::run_server().await
}
13 changes: 6 additions & 7 deletions src/client/http/cache.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! Status cache management

use std::sync::Arc;
use tokio::sync::RwLock;
use super::models::StatusResponse;
use std::sync::Arc;
use std::sync::RwLock;

/// Global status cache (shared between HTTP server and event loop)
static STATUS_CACHE: once_cell::sync::Lazy<Arc<RwLock<Option<StatusResponse>>>> =
Expand All @@ -14,14 +14,13 @@ pub fn get_cache() -> Arc<RwLock<Option<StatusResponse>>> {
}

/// Update the status cache (called from event loop)
pub async fn update(status: StatusResponse) {
let mut cache = STATUS_CACHE.write().await;
pub fn update(status: StatusResponse) {
let mut cache = STATUS_CACHE.write().unwrap();
*cache = Some(status);
}

/// Get the current cached status
pub async fn get() -> Option<StatusResponse> {
let cache = STATUS_CACHE.read().await;
pub fn get() -> Option<StatusResponse> {
let cache = STATUS_CACHE.read().unwrap();
cache.clone()
}

19 changes: 6 additions & 13 deletions src/client/http/handlers.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
//! HTTP request handlers
use axum::{
extract::State,
http::StatusCode,
response::Json,
};
use serde_json;
use super::models::StatusResponse;
use super::cache::get_cache;
use super::models::StatusResponse;
use axum::{extract::State, http::StatusCode, response::Json};
use serde_json;

/// Shared state for the HTTP server
#[derive(Clone)]
pub struct AppState {
status_cache: std::sync::Arc<tokio::sync::RwLock<Option<StatusResponse>>>,
status_cache: std::sync::Arc<std::sync::RwLock<Option<StatusResponse>>>,
}

impl AppState {
Expand All @@ -32,13 +28,10 @@ pub async fn health() -> Json<serde_json::Value> {
}

/// Status endpoint handler
pub async fn status(
State(state): State<AppState>,
) -> Result<Json<StatusResponse>, StatusCode> {
let cache = state.status_cache.read().await;
pub async fn status(State(state): State<AppState>) -> Result<Json<StatusResponse>, StatusCode> {
let cache = state.status_cache.read().unwrap();
match cache.as_ref() {
Some(status) => Ok(Json(status.clone())),
None => Err(StatusCode::SERVICE_UNAVAILABLE),
}
}

4 changes: 2 additions & 2 deletions src/client/http/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pub mod models;
pub mod cache;
mod handlers;
pub mod models;
pub mod server;

// Re-export commonly used types
pub use models::*;
pub use models::*;
1 change: 0 additions & 1 deletion src/client/http/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,3 @@ pub struct ClusterPeerInfo {
pub last_active: u64,
pub status: String, // "online", "warning", "inactive", "offline"
}

12 changes: 4 additions & 8 deletions src/client/http/server.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
//! HTTP server setup and management

use axum::{
routing::get,
Router,
};
use super::handlers::{AppState, health, status};
use axum::{Router, routing::get};

/// Start the HTTP server
pub async fn start(port: u16) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Expand All @@ -15,10 +12,9 @@ pub async fn start(port: u16) -> Result<(), Box<dyn std::error::Error + Send + S
.route("/health", get(health))
.with_state(app_state);

let listener = tokio::net::TcpListener::bind(format!("127.0.0.1:{}", port)).await?;
tracing::info!("HTTP status server listening on http://127.0.0.1:{}/status", port);
let listener = tokio::net::TcpListener::bind(format!("127.0.0.1:{port}")).await?;
tracing::info!("HTTP status server listening on http://127.0.0.1:{port}/status");

axum::serve(listener, app).await?;
Ok(())
}

Loading
Loading