Audit fixes: key permissions, lock contention, Docker IP filter, doc updates
Security: identity.key written with 0600 permissions (Unix). Docker bridge IPs (172.17-31.x) filtered from is_shareable_addr to prevent topology disclosure in relay introductions. Lock contention: ManifestPush relay and DeleteRecord CDN notify now gather connections under lock, then send outside lock. UI: syncBtn null guard prevents crash on hidden element. Documentation: design.html version badge updated to v0.4.4. Self Last Encounter threshold corrected from 3h to 4h. Multi-Device Identity section rewritten for multi-identity-per-device (complete) + multi-device (planned) + post merge (planned). MEMORY.md updated to v0.4.4+ status. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
18a40756d8
commit
fb1e92985c
5 changed files with 85 additions and 58 deletions
|
|
@ -4935,42 +4935,32 @@ impl ConnectionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send CDN delete notices (async, best-effort)
|
// Gather connections for CDN delete notices under lock, then send outside
|
||||||
|
let mut delete_notices: Vec<(iroh::endpoint::Connection, crate::protocol::BlobDeleteNoticePayload)> = Vec::new();
|
||||||
for (cid, downstream, upstream) in &blob_cleanup {
|
for (cid, downstream, upstream) in &blob_cleanup {
|
||||||
// Notify downstream (with our upstream info for tree healing)
|
let upstream_info = upstream.as_ref().map(|(nid, addrs)| PeerWithAddress { n: hex::encode(nid), a: addrs.clone() });
|
||||||
let upstream_info = upstream.as_ref().map(|(nid, addrs)| {
|
let ds_payload = crate::protocol::BlobDeleteNoticePayload { cid: *cid, upstream_node: upstream_info };
|
||||||
PeerWithAddress {
|
|
||||||
n: hex::encode(nid),
|
|
||||||
a: addrs.clone(),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let ds_payload = crate::protocol::BlobDeleteNoticePayload {
|
|
||||||
cid: *cid,
|
|
||||||
upstream_node: upstream_info,
|
|
||||||
};
|
|
||||||
for (ds_nid, _) in downstream {
|
for (ds_nid, _) in downstream {
|
||||||
if let Some(pc) = cm.connections_ref().get(ds_nid) {
|
if let Some(pc) = cm.connections_ref().get(ds_nid) {
|
||||||
if let Ok(mut send) = pc.connection.open_uni().await {
|
delete_notices.push((pc.connection.clone(), ds_payload.clone()));
|
||||||
let _ = write_typed_message(&mut send, MessageType::BlobDeleteNotice, &ds_payload).await;
|
|
||||||
let _ = send.finish();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// Notify upstream (no upstream info — just "remove me")
|
|
||||||
if let Some((up_nid, _)) = upstream {
|
if let Some((up_nid, _)) = upstream {
|
||||||
let up_payload = crate::protocol::BlobDeleteNoticePayload {
|
let up_payload = crate::protocol::BlobDeleteNoticePayload { cid: *cid, upstream_node: None };
|
||||||
cid: *cid,
|
|
||||||
upstream_node: None,
|
|
||||||
};
|
|
||||||
if let Some(pc) = cm.connections_ref().get(up_nid) {
|
if let Some(pc) = cm.connections_ref().get(up_nid) {
|
||||||
if let Ok(mut send) = pc.connection.open_uni().await {
|
delete_notices.push((pc.connection.clone(), up_payload));
|
||||||
let _ = write_typed_message(&mut send, MessageType::BlobDeleteNotice, &up_payload).await;
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drop(cm);
|
||||||
|
// Send outside lock
|
||||||
|
for (conn, payload) in &delete_notices {
|
||||||
|
if let Ok(mut send) = conn.open_uni().await {
|
||||||
|
let _ = write_typed_message(&mut send, MessageType::BlobDeleteNotice, payload).await;
|
||||||
let _ = send.finish();
|
let _ = send.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
MessageType::VisibilityUpdate => {
|
MessageType::VisibilityUpdate => {
|
||||||
let payload: crate::protocol::VisibilityUpdatePayload =
|
let payload: crate::protocol::VisibilityUpdatePayload =
|
||||||
read_payload(recv, MAX_PAYLOAD).await?;
|
read_payload(recv, MAX_PAYLOAD).await?;
|
||||||
|
|
@ -5167,15 +5157,20 @@ impl ConnectionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(storage);
|
drop(storage);
|
||||||
// Relay to downstream (best-effort via mesh connections)
|
// Gather relay connections under lock, then relay outside
|
||||||
for (ds_nid, relay_payload) in &relay_targets {
|
let relay_conns: Vec<(iroh::endpoint::Connection, crate::protocol::ManifestPushPayload)> = relay_targets.iter()
|
||||||
if let Some(pc) = cm.connections_ref().get(ds_nid) {
|
.filter_map(|(ds_nid, payload)| {
|
||||||
if let Ok(mut send) = pc.connection.open_uni().await {
|
cm.connections_ref().get(ds_nid).map(|pc| (pc.connection.clone(), payload.clone()))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
drop(cm);
|
||||||
|
// Relay outside lock
|
||||||
|
for (conn, relay_payload) in &relay_conns {
|
||||||
|
if let Ok(mut send) = conn.open_uni().await {
|
||||||
let _ = write_typed_message(&mut send, MessageType::ManifestPush, relay_payload).await;
|
let _ = write_typed_message(&mut send, MessageType::ManifestPush, relay_payload).await;
|
||||||
let _ = send.finish();
|
let _ = send.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 5: Spawn post discovery task (non-blocking)
|
// Phase 5: Spawn post discovery task (non-blocking)
|
||||||
if !discovery_posts.is_empty() {
|
if !discovery_posts.is_empty() {
|
||||||
|
|
@ -5291,8 +5286,7 @@ impl ConnectionManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(cm);
|
debug!(peer = hex::encode(remote_node_id), stored, relayed = relay_conns.len(), "Received manifest push");
|
||||||
debug!(peer = hex::encode(remote_node_id), stored, relayed = relay_targets.len(), "Received manifest push");
|
|
||||||
}
|
}
|
||||||
MessageType::SocialDisconnectNotice => {
|
MessageType::SocialDisconnectNotice => {
|
||||||
let payload: SocialDisconnectNoticePayload = read_payload(recv, MAX_PAYLOAD).await?;
|
let payload: SocialDisconnectNoticePayload = read_payload(recv, MAX_PAYLOAD).await?;
|
||||||
|
|
|
||||||
|
|
@ -188,8 +188,10 @@ impl IdentityManager {
|
||||||
let id_dir = self.base_dir.join("identities").join(&node_id_hex);
|
let id_dir = self.base_dir.join("identities").join(&node_id_hex);
|
||||||
std::fs::create_dir_all(&id_dir)?;
|
std::fs::create_dir_all(&id_dir)?;
|
||||||
|
|
||||||
// Write identity key
|
// Write identity key with restricted permissions
|
||||||
std::fs::write(id_dir.join("identity.key"), seed)?;
|
let key_path = id_dir.join("identity.key");
|
||||||
|
std::fs::write(&key_path, seed)?;
|
||||||
|
set_key_permissions(&key_path);
|
||||||
|
|
||||||
// Write metadata
|
// Write metadata
|
||||||
let now = now_ms();
|
let now = now_ms();
|
||||||
|
|
@ -223,7 +225,9 @@ impl IdentityManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::fs::create_dir_all(&id_dir)?;
|
std::fs::create_dir_all(&id_dir)?;
|
||||||
std::fs::write(id_dir.join("identity.key"), seed)?;
|
let key_path = id_dir.join("identity.key");
|
||||||
|
std::fs::write(&key_path, seed)?;
|
||||||
|
set_key_permissions(&key_path);
|
||||||
|
|
||||||
let now = now_ms();
|
let now = now_ms();
|
||||||
let meta = IdentityMeta {
|
let meta = IdentityMeta {
|
||||||
|
|
@ -390,3 +394,16 @@ fn now_ms() -> u64 {
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.as_millis() as u64
|
.as_millis() as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set restrictive permissions on identity key files (user-only read/write).
|
||||||
|
fn set_key_permissions(path: &std::path::Path) {
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
let _ = std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600));
|
||||||
|
}
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
{
|
||||||
|
let _ = path; // no-op on non-Unix (Windows uses ACLs, Android sandboxes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,11 +61,22 @@ fn is_public_ip(ip: IpAddr) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Filter out addresses that are never useful to share (loopback, link-local, unspecified).
|
/// Filter out addresses that are never useful to share (loopback, link-local, unspecified,
|
||||||
/// Keeps LAN addresses (192.168.x, 10.x, 172.16-31.x) since peers might be on the same LAN.
|
/// Docker bridge). Keeps common LAN addresses (192.168.x, 10.x) for same-WiFi discovery.
|
||||||
|
/// Excludes 172.17-31.x (Docker/container bridges) to avoid topology disclosure.
|
||||||
pub(crate) fn is_shareable_addr(addr: &SocketAddr) -> bool {
|
pub(crate) fn is_shareable_addr(addr: &SocketAddr) -> bool {
|
||||||
match addr.ip() {
|
match addr.ip() {
|
||||||
IpAddr::V4(v4) => !v4.is_loopback() && !v4.is_link_local() && !v4.is_unspecified(),
|
IpAddr::V4(v4) => {
|
||||||
|
if v4.is_loopback() || v4.is_link_local() || v4.is_unspecified() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Exclude Docker bridge range (172.17.0.0 - 172.31.255.255)
|
||||||
|
let octets = v4.octets();
|
||||||
|
if octets[0] == 172 && octets[1] >= 17 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
IpAddr::V6(v6) => !v6.is_loopback() && !v6.is_unspecified(),
|
IpAddr::V6(v6) => !v6.is_loopback() && !v6.is_unspecified(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3141,7 +3141,7 @@ $('#share-details-btn').addEventListener('click', () => {
|
||||||
overlay.querySelector('#share-close-btn').addEventListener('click', () => overlay.remove());
|
overlay.querySelector('#share-close-btn').addEventListener('click', () => overlay.remove());
|
||||||
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });
|
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });
|
||||||
});
|
});
|
||||||
syncBtn.addEventListener('click', doSyncAll);
|
if (syncBtn) syncBtn.addEventListener('click', doSyncAll);
|
||||||
if (copyBtn) copyBtn.addEventListener('click', async () => {
|
if (copyBtn) copyBtn.addEventListener('click', async () => {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(connectString);
|
await navigator.clipboard.writeText(connectString);
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
|
|
||||||
<div class="container wide">
|
<div class="container wide">
|
||||||
<section>
|
<section>
|
||||||
<span class="version-badge">v0.3.1 — 2026-03-13</span>
|
<span class="version-badge">v0.4.4 — 2026-03-31</span>
|
||||||
<h1 style="font-size: 2rem; font-weight: 800; letter-spacing: -0.03em; margin-bottom: 0.5rem;">Design Document</h1>
|
<h1 style="font-size: 2rem; font-weight: 800; letter-spacing: -0.03em; margin-bottom: 0.5rem;">Design Document</h1>
|
||||||
<p>This is the canonical technical reference for ItsGoin. It describes the vision, the architecture, and the current state of every subsystem — with full implementation detail. This document is versioned; each update records what changed.</p>
|
<p>This is the canonical technical reference for ItsGoin. It describes the vision, the architecture, and the current state of every subsystem — with full implementation detail. This document is versioned; each update records what changed.</p>
|
||||||
<div class="card" style="margin-top: 1rem;">
|
<div class="card" style="margin-top: 1rem;">
|
||||||
|
|
@ -274,7 +274,7 @@
|
||||||
<ol style="padding-left: 1.25rem; margin: 0.5rem 0; color: var(--text-muted);">
|
<ol style="padding-left: 1.25rem; margin: 0.5rem 0; color: var(--text-muted);">
|
||||||
<li><strong>Dead connection removal</strong>: Remove connections with <code>close_reason()</code> set, or idle > 600s (zombie)</li>
|
<li><strong>Dead connection removal</strong>: Remove connections with <code>close_reason()</code> set, or idle > 600s (zombie)</li>
|
||||||
<li><strong>Stale entry pruning</strong>: N2/N3 entries tagged to a peer that is no longer connected are pruned immediately (on disconnect and on startup sweep). Age-based fallback: entries older than 7 days. Social route watchers older than 30 days.</li>
|
<li><strong>Stale entry pruning</strong>: N2/N3 entries tagged to a peer that is no longer connected are pruned immediately (on disconnect and on startup sweep). Age-based fallback: entries older than 7 days. Social route watchers older than 30 days.</li>
|
||||||
<li><strong>Priority 0 — Preferred peer reconnection</strong>: Iterate <code>preferred_peers</code> table, reconnect any that are disconnected. If at capacity, evict the lowest-diversity non-preferred peer to make room. Prune preferred peers unreachable for 7+ days (slot released, does NOT auto-return on reconnect — must re-negotiate via MeshPrefer). After 7 days, social checkin frequency drops from 1–3 hours to daily until the 30-day reconnect watcher expires.</li>
|
<li><strong>Priority 0 — Preferred peer reconnection</strong>: Iterate <code>preferred_peers</code> table, reconnect any that are disconnected. If at capacity, evict the lowest-diversity non-preferred peer to make room. Prune preferred peers unreachable for 7+ days (slot released, does NOT auto-return on reconnect — must re-negotiate via MeshPrefer). After 7 days, social checkin frequency drops from 1–4 hours to daily until the 30-day reconnect watcher expires.</li>
|
||||||
<li><strong>Priority 1 — Reconnect recently dead</strong>: Re-establish dropped non-preferred connections. <strong>Skip blacklisted nodes</strong> — do not attempt reconnection to peers in <code>mesh_blacklist</code>.</li>
|
<li><strong>Priority 1 — Reconnect recently dead</strong>: Re-establish dropped non-preferred connections. <strong>Skip blacklisted nodes</strong> — do not attempt reconnection to peers in <code>mesh_blacklist</code>.</li>
|
||||||
<li><strong>Priority 2 — Signal growth loop</strong>: Fill remaining empty slots via growth loop</li>
|
<li><strong>Priority 2 — Signal growth loop</strong>: Fill remaining empty slots via growth loop</li>
|
||||||
<li><strong>Idle session cleanup</strong>: Reap interactive sessions idle > 300s (5 min). Keep-alive sessions are NOT reaped by idle timeout.</li>
|
<li><strong>Idle session cleanup</strong>: Reap interactive sessions idle > 300s (5 min). Keep-alive sessions are NOT reaped by idle timeout.</li>
|
||||||
|
|
@ -355,7 +355,7 @@
|
||||||
<table>
|
<table>
|
||||||
<tr><th>Layer</th><th>Purpose</th><th>Connections</th><th>Sync trigger</th></tr>
|
<tr><th>Layer</th><th>Purpose</th><th>Connections</th><th>Sync trigger</th></tr>
|
||||||
<tr><td><strong>Mesh</strong></td><td>Structural backbone: N1/N2/N3 routing, diversity, discovery</td><td>101 mesh slots (preferred + non-preferred)</td><td>N/A — mesh is infrastructure, not content</td></tr>
|
<tr><td><strong>Mesh</strong></td><td>Structural backbone: N1/N2/N3 routing, diversity, discovery</td><td>101 mesh slots (preferred + non-preferred)</td><td>N/A — mesh is infrastructure, not content</td></tr>
|
||||||
<tr><td><strong>Social</strong></td><td>Follows, audience, DMs — the human relationships</td><td>Social routes + keep-alive sessions as needed</td><td>Pull posts when Self Last Encounter > 3 hours</td></tr>
|
<tr><td><strong>Social</strong></td><td>Follows, audience, DMs — the human relationships</td><td>Social routes + keep-alive sessions as needed</td><td>Pull posts when Self Last Encounter > 4 hours</td></tr>
|
||||||
<tr><td><strong>File</strong></td><td>Content storage and distribution — blobs, CDN trees</td><td>Upstream/downstream file peers + keep-alive sessions as needed</td><td>Pull on blob request, push on post creation</td></tr>
|
<tr><td><strong>File</strong></td><td>Content storage and distribution — blobs, CDN trees</td><td>Upstream/downstream file peers + keep-alive sessions as needed</td><td>Pull on blob request, push on post creation</td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
@ -1017,7 +1017,7 @@ FAILURE: C → B → A: AnchorProbeResult { reachable: false }</code></pre
|
||||||
<div class="note">
|
<div class="note">
|
||||||
<strong>v0.2.0 change</strong>: Pull sync pulls posts from <strong>social layer peers</strong> (follows, audience) and <strong>upstream file peers</strong>, NOT from mesh peers. Mesh connections exist for routing diversity, not content. This separates infrastructure from content flow.
|
<strong>v0.2.0 change</strong>: Pull sync pulls posts from <strong>social layer peers</strong> (follows, audience) and <strong>upstream file peers</strong>, NOT from mesh peers. Mesh connections exist for routing diversity, not content. This separates infrastructure from content flow.
|
||||||
</div>
|
</div>
|
||||||
<p><strong>Self Last Encounter</strong>: For each peer we sync with, we track the timestamp of our last successful sync. When Self Last Encounter ages beyond <strong>3 hours</strong>, a pull sync is triggered. Self Last Encounter is updated to the newer of: (a) what's currently stored, or (b) the "file last update" timestamp from file headers received during blob transfers. Since file headers include the author's recent post list, downloading a blob from any peer hosting that author's content can update Self Last Encounter for the author.</p>
|
<p><strong>Self Last Encounter</strong>: For each peer we sync with, we track the timestamp of our last successful sync. When Self Last Encounter ages beyond <strong>4 hours</strong>, a pull sync is triggered. Self Last Encounter is updated to the newer of: (a) what's currently stored, or (b) the "file last update" timestamp from file headers received during blob transfers. Since file headers include the author's recent post list, downloading a blob from any peer hosting that author's content can update Self Last Encounter for the author.</p>
|
||||||
|
|
||||||
<h3>Pull sync filtering</h3>
|
<h3>Pull sync filtering</h3>
|
||||||
<ul style="padding-left: 1.25rem; margin: 0.5rem 0; color: var(--text-muted);">
|
<ul style="padding-left: 1.25rem; margin: 0.5rem 0; color: var(--text-muted);">
|
||||||
|
|
@ -1417,22 +1417,27 @@ END</code></pre>
|
||||||
|
|
||||||
<!-- 23. Multi-Device Identity -->
|
<!-- 23. Multi-Device Identity -->
|
||||||
<section id="multidevice">
|
<section id="multidevice">
|
||||||
<h2>23. Multi-Device Identity</h2>
|
<h2>23. Identity Management</h2>
|
||||||
<h3>Status: <span class="badge badge-planned">Planned</span></h3>
|
|
||||||
|
|
||||||
<h3>Concept</h3>
|
<h3>Multi-identity per device <span class="badge badge-complete">Complete</span></h3>
|
||||||
<p>Multiple devices share the <strong>same identity key</strong> (ed25519 keypair, same NodeId). All devices ARE the same node from the network's perspective. Posts from any device appear as the same author.</p>
|
<p>A single device can hold <strong>multiple identities</strong>, each with its own ed25519 keypair, database, blob store, follows, and posts. One identity is active at a time — switching performs a hot-swap (Node teardown + rebuild, ~3-5 seconds).</p>
|
||||||
|
|
||||||
<h3>Device identity</h3>
|
|
||||||
<p>Each device also generates a unique <strong>device identity</strong> (separate ed25519 keypair). This device-specific key is used to:</p>
|
|
||||||
<ul style="padding-left: 1.25rem; margin: 0.5rem 0; color: var(--text-muted);">
|
<ul style="padding-left: 1.25rem; margin: 0.5rem 0; color: var(--text-muted);">
|
||||||
<li><strong>Find each other</strong>: Devices with the same shared identity can search for each other using their device identities to facilitate syncs and self-routing</li>
|
<li><strong>Directory structure</strong>: <code>itsgoin-data/identities/{node_id_hex}/</code> — each identity gets its own subdirectory with <code>identity.key</code>, <code>itsgoin.db</code>, <code>blobs/</code>, and <code>meta.json</code></li>
|
||||||
<li><strong>Own-device relay</strong>: Route traffic through your own devices (e.g., home computer relaying for your phone) using the device identity for authentication</li>
|
<li><strong>Legacy migration</strong>: Flat <code>itsgoin-data/</code> layout auto-migrates to per-identity subdirectories on first launch</li>
|
||||||
<li><strong>Conflict resolution</strong>: When devices post simultaneously, device identity helps order and deduplicate</li>
|
<li><strong>Create, import, switch, delete</strong> via Settings UI</li>
|
||||||
|
<li><strong>Key permissions</strong>: <code>identity.key</code> files written with 0600 permissions (Unix)</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>Setup</h3>
|
<h3>Multi-device identity <span class="badge badge-planned">Planned</span></h3>
|
||||||
<p>Export <code>identity.key</code> from one device, import on another. The device identity is generated automatically on each device. Once two devices share an identity key, they can discover each other through normal network routing (same NodeId appears at multiple addresses).</p>
|
<p>Multiple devices share the <strong>same identity key</strong> (ed25519 keypair, same NodeId). All devices ARE the same node from the network’s perspective. Posts from any device appear as the same author.</p>
|
||||||
|
<ul style="padding-left: 1.25rem; margin: 0.5rem 0; color: var(--text-muted);">
|
||||||
|
<li><strong>Setup</strong>: Export <code>identity.key</code> from one device, import on another using the identity management UI</li>
|
||||||
|
<li><strong>Device identity</strong>: Each device generates a unique device keypair for self-routing and conflict resolution (planned)</li>
|
||||||
|
<li><strong>Own-device relay</strong>: Route traffic through your own devices (planned)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Post import & merge <span class="badge badge-planned">Planned</span></h3>
|
||||||
|
<p>Import posts from another identity into the current one. Public posts imported directly. Encrypted posts require the original identity’s key for decryption, then re-encrypted under the current identity. Merge creates <strong>new posts</strong> (new PostId, new author) with original timestamps preserved and prior author noted in BlobHeader.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- 24. Phase 2 -->
|
<!-- 24. Phase 2 -->
|
||||||
|
|
@ -1599,7 +1604,7 @@ END</code></pre>
|
||||||
<tr><td>MESH_KEEPALIVE_INTERVAL</td><td>30s</td><td>Ping to prevent zombie detection</td></tr>
|
<tr><td>MESH_KEEPALIVE_INTERVAL</td><td>30s</td><td>Ping to prevent zombie detection</td></tr>
|
||||||
<tr><td>ZOMBIE_TIMEOUT</td><td>600s (10 min)</td><td>No activity → dead connection</td></tr>
|
<tr><td>ZOMBIE_TIMEOUT</td><td>600s (10 min)</td><td>No activity → dead connection</td></tr>
|
||||||
<tr><td>SESSION_IDLE_TIMEOUT</td><td>300s (5 min)</td><td>Reap idle interactive sessions (NOT keep-alive)</td></tr>
|
<tr><td>SESSION_IDLE_TIMEOUT</td><td>300s (5 min)</td><td>Reap idle interactive sessions (NOT keep-alive)</td></tr>
|
||||||
<tr><td>SELF_LAST_ENCOUNTER_THRESHOLD</td><td>10800s (3 hours)</td><td>Trigger pull sync when last encounter exceeds this</td></tr>
|
<tr><td>SELF_LAST_ENCOUNTER_THRESHOLD</td><td>14400s (4 hours)</td><td>Trigger pull sync when last encounter exceeds this</td></tr>
|
||||||
<tr><td>QUIC_CONNECT_TIMEOUT</td><td>15s</td><td>Direct connection establishment</td></tr>
|
<tr><td>QUIC_CONNECT_TIMEOUT</td><td>15s</td><td>Direct connection establishment</td></tr>
|
||||||
<tr><td>HOLE_PUNCH_TIMEOUT</td><td>30s</td><td>Overall hole punch window</td></tr>
|
<tr><td>HOLE_PUNCH_TIMEOUT</td><td>30s</td><td>Overall hole punch window</td></tr>
|
||||||
<tr><td>HOLE_PUNCH_ATTEMPT</td><td>2s</td><td>Per-address attempt within window</td></tr>
|
<tr><td>HOLE_PUNCH_ATTEMPT</td><td>2s</td><td>Per-address attempt within window</td></tr>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue