Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
56e89b6
perf: tighten network timeouts for faster connection establishment
mickvandijke Apr 4, 2026
eb1eb64
fix: differentiate FAST_SEND_ACK_TIMEOUT from default (500ms → 250ms)
mickvandijke Apr 4, 2026
24bec69
Merge pull request #38 from saorsa-labs/perf/tighten-network-timeouts
jacderida Apr 4, 2026
e29f324
perf: increase timeouts based on 50-node multi-region testnet data
jacderida Apr 4, 2026
da74da9
fix: race condition in hole-punch peer ID matching
jacderida Apr 4, 2026
f65af2e
debug: add peer ID tracing to hole-punch coordination path
jacderida Apr 5, 2026
d99e5ab
feat: add preferred coordinator for hole-punch relay and improve rela…
jacderida Apr 5, 2026
9009e64
fix: scale send ACK timeout with payload size
jacderida Apr 5, 2026
e828dbc
debug: check connection health before queuing PUNCH_ME_NOW relay
jacderida Apr 5, 2026
6c4088f
debug: log when PUNCH_ME_NOW relay frame is encoded into QUIC packet
jacderida Apr 5, 2026
c670c54
fix: always overwrite NAT API connection DashMap with newer connection
jacderida Apr 5, 2026
5516b06
fix: detect zombie connections before queuing PUNCH_ME_NOW relay
jacderida Apr 5, 2026
0216038
fix: detect stale coordinator connections by comparing handle indices
jacderida Apr 5, 2026
e861d3a
fix: keep preferred coordinator on hole-punch retry
jacderida Apr 5, 2026
5280123
refactor!: remove dead NatTraversalEndpoint hole-punch chain
mickvandijke Apr 6, 2026
7314d0b
feat: best-effort UPnP IGD port mapping for NAT traversal
mickvandijke Apr 6, 2026
090a45c
refactor!: remove orphaned CandidatePair public types
mickvandijke Apr 6, 2026
b906e65
style: apply rustfmt to two unrelated info! call sites
mickvandijke Apr 6, 2026
21bda71
fix: reject IPv6 documentation prefix in UPnP external IP classifier
mickvandijke Apr 6, 2026
9ba3ecc
Merge pull request #41 from saorsa-labs/refactor/remove-dead-hole-pun…
jacderida Apr 6, 2026
faaa797
chore: resolve merge conflict with rc-2026.4.1 after PR #41
mickvandijke Apr 6, 2026
3d73241
Merge pull request #40 from saorsa-labs/feat/upnp-port-mapping
jacderida Apr 6, 2026
3c847aa
perf!: hold read lock on router during send via atomic selection stats
mickvandijke Apr 6, 2026
45bf738
fix: address PR #42 review — atomic fallback decrement, read-lock con…
mickvandijke Apr 6, 2026
386c6e8
refactor!: make ConnectionRouter fully lock-free and interior-mutable
mickvandijke Apr 6, 2026
6bfd936
Merge pull request #42 from saorsa-labs/perf/router-stats-atomic-read…
jacderida Apr 6, 2026
496721b
fix: raise coordinator PUNCH_ME_NOW rate limit from 5 to 50 per minute
jacderida Apr 6, 2026
70ab29b
fix: raise coordinator PUNCH_ME_NOW rate limit from 5 to 50 per minute
jacderida Apr 6, 2026
b58e671
feat(nat): rotate hole-punch coordinators with capped per-attempt tim…
mickvandijke Apr 6, 2026
7f2887e
refactor!(nat): node-wide coordinator back-pressure via shared RelayS…
mickvandijke Apr 6, 2026
2441896
Merge pull request #43 from saorsa-labs/feat/hole-punch-coordinator-r…
mickvandijke Apr 6, 2026
6e85dd9
fix: raise coordinator PUNCH_ME_NOW rate limit from 50 to 300 per minute
jacderida Apr 7, 2026
a6c82d0
fix: raise send_ack_timeout from 500ms to 5s for cross-region connect…
grumbach Apr 8, 2026
55ffca7
fix(nat): accept loop keeps newer connection on dedup instead of clos…
grumbach Apr 8, 2026
1478a1b
fix(reachability): replace has_public_ip with scope-aware peer-verifi…
grumbach Apr 7, 2026
d12d86c
fix: address review feedback — double-count bug, relay capability, ru…
grumbach Apr 7, 2026
6f139c7
fix(relay): rotate through all relay candidates instead of failing on…
grumbach Apr 7, 2026
e44b25e
fix(relay): return socket on session reuse instead of None
grumbach Apr 7, 2026
f475bd0
fix(relay): schedule periodic cleanup of expired relay sessions
grumbach Apr 7, 2026
44b4662
fix(nat): quality-aware coordinator selection with RTT weighting and …
grumbach Apr 7, 2026
664ce20
fix(nat): defer old connection close on dedup to avoid disrupting in-…
jacderida Apr 8, 2026
2cabf2b
fix(nat): normalize IPv4-mapped IPv6 addresses in hole-punch DashMap …
jacderida Apr 8, 2026
b2e6aaa
fix: NAT traversal fixes for large-scale testnets (990+ nodes)
jacderida Apr 9, 2026
9a8ffa2
feat: PUNCH_ME_NOW_NACK — coordinator signals target not found
jacderida Apr 9, 2026
4587a33
Merge pull request #56 from jacderida/round4-combined
jacderida Apr 11, 2026
35a43c7
feat: increase ACK timeout size budget from 1ms/KB to 4ms/KB
jacderida Apr 12, 2026
f2b30ad
Merge pull request #58 from saorsa-labs/feat-upload-timeouts
jacderida Apr 13, 2026
8f1dd14
fix: increase handshake channel capacity from 32 to 1024
jacderida Apr 14, 2026
7986b01
Merge pull request #59 from saorsa-labs/fix/handshake-channel-capacity
jacderida Apr 14, 2026
66a1c6f
Revert "Merge pull request #59 from saorsa-labs/fix/handshake-channel…
jacderida Apr 14, 2026
891efbc
Merge pull request #60 from saorsa-labs/revert/handshake-channel-capa…
jacderida Apr 14, 2026
49a13c4
chore: bump version to 0.32.0
jacderida Apr 14, 2026
a6cc9fb
fix: apply rustfmt formatting and fix test_default_config assertion
jacderida Apr 14, 2026
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
314 changes: 198 additions & 116 deletions Cargo.lock

Large diffs are not rendered by default.

17 changes: 15 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ members = [

[package]
name = "saorsa-transport"
version = "0.31.0"
version = "0.32.0"
edition = "2024"
rust-version = "1.88.0"
license = "MIT OR Apache-2.0"
Expand Down Expand Up @@ -39,11 +39,18 @@ exclude = [
[features]
# Default features include essential functionality with 100% PQC support
# v0.15.0: Simplified feature flags - crypto is always enabled
default = ["platform-verifier", "network-discovery"]
default = ["platform-verifier", "network-discovery", "upnp"]

# Platform-specific certificate verification
platform-verifier = ["dep:rustls-platform-verifier"]

# UPnP IGD port mapping for best-effort NAT traversal assistance.
# When enabled, the endpoint will opportunistically request a UDP port
# mapping from a local Internet Gateway Device. Failure is silent and
# non-fatal — the endpoint behaves identically to a non-UPnP build when
# no gateway is available.
upnp = ["dep:igd-next"]

# Configure `tracing` to log events via `log` if no `tracing` subscriber exists
log = ["tracing/log"]

Expand Down Expand Up @@ -113,6 +120,12 @@ rustls-post-quantum = { version = "0.2", features = ["aws-lc-rs-unstable"] }
socket2 = { version = "0.5", optional = true }
nix = { version = "0.29", features = ["resource", "net"], optional = true }

# UPnP IGD port mapping (optional)
# Used by the `upnp` feature for best-effort UDP port mapping. The
# implementation never blocks startup and silently degrades when the
# router does not support or has disabled UPnP IGD.
igd-next = { version = "0.17", default-features = false, features = ["aio_tokio"], optional = true }

# BLE transport dependencies (cross-platform, optional)
# btleplug supports Linux (BlueZ), macOS (Core Bluetooth), and Windows (WinRT)
btleplug = { version = "0.11", optional = true }
Expand Down
22 changes: 11 additions & 11 deletions benches/connection_router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ fn bench_engine_selection(c: &mut Criterion) {

// Benchmark UDP address selection
group.bench_function("udp_address", |b| {
let mut router = ConnectionRouter::new(RouterConfig::default());
let router = ConnectionRouter::new(RouterConfig::default());
b.iter(|| {
let engine = router.select_engine_for_addr(black_box(&udp_transport));
black_box(engine)
Expand All @@ -47,7 +47,7 @@ fn bench_engine_selection(c: &mut Criterion) {

// Benchmark BLE address selection
group.bench_function("ble_address", |b| {
let mut router = ConnectionRouter::new(RouterConfig::default());
let router = ConnectionRouter::new(RouterConfig::default());
b.iter(|| {
let engine = router.select_engine_for_addr(black_box(&ble_transport));
black_box(engine)
Expand All @@ -56,7 +56,7 @@ fn bench_engine_selection(c: &mut Criterion) {

// Benchmark LoRa address selection
group.bench_function("lora_address", |b| {
let mut router = ConnectionRouter::new(RouterConfig::default());
let router = ConnectionRouter::new(RouterConfig::default());
b.iter(|| {
let engine = router.select_engine_for_addr(black_box(&lora_transport));
black_box(engine)
Expand All @@ -76,7 +76,7 @@ fn bench_engine_selection_detailed(c: &mut Criterion) {

// Benchmark broadband selection
group.bench_function("broadband_detailed", |b| {
let mut router = ConnectionRouter::new(RouterConfig::default());
let router = ConnectionRouter::new(RouterConfig::default());
b.iter(|| {
let result = router.select_engine_detailed(black_box(&broadband_caps));
black_box(result)
Expand All @@ -85,7 +85,7 @@ fn bench_engine_selection_detailed(c: &mut Criterion) {

// Benchmark BLE selection
group.bench_function("ble_detailed", |b| {
let mut router = ConnectionRouter::new(RouterConfig::default());
let router = ConnectionRouter::new(RouterConfig::default());
b.iter(|| {
let result = router.select_engine_detailed(black_box(&ble_caps));
black_box(result)
Expand All @@ -94,7 +94,7 @@ fn bench_engine_selection_detailed(c: &mut Criterion) {

// Benchmark LoRa selection
group.bench_function("lora_detailed", |b| {
let mut router = ConnectionRouter::new(RouterConfig::default());
let router = ConnectionRouter::new(RouterConfig::default());
b.iter(|| {
let result = router.select_engine_detailed(black_box(&lora_caps));
black_box(result)
Expand All @@ -112,7 +112,7 @@ fn bench_fallback_selection(c: &mut Criterion) {

// Benchmark with QUIC available
group.bench_function("quic_available", |b| {
let mut router = ConnectionRouter::new(RouterConfig::default());
let router = ConnectionRouter::new(RouterConfig::default());
b.iter(|| {
let result = router.select_engine_with_fallback(
black_box(&broadband_caps),
Expand All @@ -125,7 +125,7 @@ fn bench_fallback_selection(c: &mut Criterion) {

// Benchmark with fallback needed
group.bench_function("quic_fallback", |b| {
let mut router = ConnectionRouter::new(RouterConfig::default());
let router = ConnectionRouter::new(RouterConfig::default());
b.iter(|| {
let result = router.select_engine_with_fallback(
black_box(&broadband_caps),
Expand Down Expand Up @@ -184,7 +184,7 @@ fn bench_constrained_connect(c: &mut Criterion) {

// Benchmark constrained connection creation
group.bench_function("ble_connect", |b| {
let mut router = ConnectionRouter::new(RouterConfig::default());
let router = ConnectionRouter::new(RouterConfig::default());
b.iter(|| {
let result = router.connect(black_box(&ble_addr));
black_box(result)
Expand Down Expand Up @@ -215,11 +215,11 @@ fn bench_stats_tracking(c: &mut Criterion) {

// Benchmark selection with stats update
group.bench_function("selection_with_stats", |b| {
let mut router = ConnectionRouter::new(RouterConfig::default());
let router = ConnectionRouter::new(RouterConfig::default());
b.iter(|| {
let _ = router.select_engine_for_addr(black_box(&udp_addr));
let _ = router.select_engine_for_addr(black_box(&ble_addr));
let stats = router.stats().clone();
let stats = router.stats().snapshot();
black_box(stats)
});
});
Expand Down
2 changes: 2 additions & 0 deletions benches/nat_traversal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,7 @@ fn bench_pair_generation(c: &mut Criterion) {
CandidateSource::Observed { .. } => 1,
CandidateSource::Peer => 2,
CandidateSource::Predicted => 3,
CandidateSource::PortMapped => 4,
};

for remote in &remote_candidates {
Expand All @@ -585,6 +586,7 @@ fn bench_pair_generation(c: &mut Criterion) {
CandidateSource::Observed { .. } => 1,
CandidateSource::Peer => 2,
CandidateSource::Predicted => 3,
CandidateSource::PortMapped => 4,
};

// Calculate priority
Expand Down
1 change: 1 addition & 0 deletions examples/simple_p2p.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ async fn main() -> anyhow::Result<()> {
addr,
public_key,
side,
..
} => {
println!(
"Connected to peer at {} (side: {:?}, has key: {})",
Expand Down
1 change: 1 addition & 0 deletions src/bin/e2e-test-node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,7 @@ async fn handle_event(
addr,
public_key: _,
side,
traversal_method: _,
} => {
let direction = if side.is_client() {
"outbound"
Expand Down
17 changes: 17 additions & 0 deletions src/bin/saorsa-transport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,14 @@ struct Args {
/// Chunk size for data generation/verification (bytes)
#[arg(long, default_value = "65536")]
chunk_size: usize,

/// Disable best-effort UPnP IGD port mapping. By default the endpoint
/// asks the local router to forward its UDP port — pass this flag to
/// skip the UPnP probe entirely (useful when the router is known to
/// be hostile or when running on infrastructure that does not need
/// it). NAT traversal still works without UPnP via hole punching.
#[arg(long)]
no_upnp: bool,
}

/// CLI subcommands
Expand Down Expand Up @@ -371,6 +379,15 @@ async fn main() -> anyhow::Result<()> {
}
// v0.13.0: No mode-based NAT config - all nodes are symmetric

if args.no_upnp {
let nat = saorsa_transport::unified_config::NatConfig {
upnp: saorsa_transport::upnp::UpnpConfig::disabled(),
..saorsa_transport::unified_config::NatConfig::default()
};
builder = builder.nat(nat);
info!("UPnP IGD port mapping disabled (--no-upnp)");
}

let config = builder.build()?;

// Create endpoint
Expand Down
Loading
Loading