Reveals RingSink operation costs:
poll_ready: 13ns/call (0.57% total)
start_send: <26ns/call (below top-15 threshold)
poll_flush: below top-15 threshold
Confirms ring tunnel sink operations are ~33ns/pkt total,
vs 2.04us for MpscTunnelSender::send (which routes through
tokio mpsc channel). The channel intermediary adds ~2us/pkt
of pure overhead.
channel(32) was frequently full under high pps, causing try_send to
fail and fall back to send().await (semaphore wait). Increasing to 1024
reduces fallback frequency.
Benchmark (4 threads, 1400B, 15s):
MpscTunnelSender::send avg: 2.23us → 2.01us (-220ns)
MpscTunnelSender::send P95: 6.39us → 5.74us (-650ns)
send_msg_internal avg: 2.67us → 2.45us (-220ns)
pps: ~250K (unchanged, receiver-bound)
pps unchanged because bottleneck moved to receiver (forward_one_round →
sink.feed/flush). The 220ns/pkt saving is pure CPU efficiency gain.
FuturesUnordered-based pipeline to overlap encrypt with mpsc_send.
Tested depths 1/4/8/16: max +1.6% at depth=4, within noise. Pipeline
has limited value because try_send fast path eliminates await gaps
that would allow overlap. Default remains depth=1 (serial).
Splits the 1.04us gap between send_msg_by_ip and send_msg_internal:
try_compress_and_encrypt: 386ns (37%) ← AES-GCM encrypt + zstd compress
get_msg_dst_peer_ipv4: 161ns (15%) ← IP→peer_id route lookup
run_nic_packet_process_pipeline: 121ns (12%) ← ACL check
other (fill_hdr + counters): ~440ns (36%)
The 386ns encrypt is the largest optimization opportunity in this gap:
ring tunnel is in-process, so application-layer encryption may be skippable.
Adds hotpath::measure to two critical blind spots in the send chain:
1. MpscTunnelSender::send — the tokio mpsc channel send point, which
accounts for 84% of PeerConn::send_msg wall time (2.33us/pkt).
2. PeerManager::send_msg_by_ip — the top-level packet send entry point,
revealing a 1.04us gap between send_msg_by_ip and send_msg_internal
(encryption + routing + ACL + fan-out).
Full send chain timing now visible:
send_msg_by_ip: 3.83us
└─ send_msg_internal: 2.79us (gap: 1.04us = encrypt + route + ACL)
└─ MpscTunnelSender::send: 2.33us (84% of internal)
Two optimizations to reduce per-packet TrafficMetricRecorder overhead:
1. Batch CounterHandle updates (TRAFFIC_BATCH_SIZE=128): accumulate
bytes/packets in AtomicU64, flush to CounterHandle (and its
touch()/Instant::now()) only every 128 packets. Reduces touch
calls from 4/pkt to 0.03/pkt.
2. Sync fast path: record_tx_fast/record_rx_fast handle the common
case (peer already resolved) without entering async fn or cloning
TrafficCounters. Falls back to async record_tx/record_rx only for
first packet to a new/unresolved peer.
Tests use BATCH_SIZE=1 via cfg(test) for exact counter validation.
Benchmark (4 threads, 1400B, 15s, 3 runs):
Before: 246K pps, send_msg_internal avg 3.13us
After: 250K pps, send_msg_internal avg 3.05us
Delta: +1.6% pps, -80ns/pkt
All 11 traffic_metrics + send_msg_internal tests pass.
Add #[global_allocator] behind feature flags so the bench can test
different allocators. Previously the example used glibc malloc by
default (easytier-core.rs sets jemalloc/mimalloc only for the bin
target, not examples).
Benchmark (4 threads, 1400B, 15s, clone mode):
glibc: 246K pps, 3.13us/pkt
jemalloc: 246K pps, 3.21us/pkt
mimalloc: 242K pps, 3.25us/pkt
All within noise. Single-threaded clone has low malloc contention;
~1500B small allocs are served efficiently by all tcaches.
Replace all UnsafeCell-based counters with safe alternatives:
- New ShardedCounter: per-thread TLS accumulation (thread_local crate)
with periodic publish to static AtomicU64. Zero atomic RMW on hot path.
- ACL RuleStatsTracker: remove unsafe Arc<RuleStats> raw pointer
mutation, use two ShardedCounter fields.
- ZCPacket: replace set_len on uninitialized BytesMut with
write_bytes + copy_nonoverlapping before set_len.
- StatsManager UnsafeCounter/MetricData: remove UnsafeCell and
unsafe impl Send/Sync, wrap ShardedCounter. last_updated uses
AtomicU64 epoch millis.
- Throughput: replace UnsafeCell with AtomicU64.
- secure_datagram: fix grace-window test timestamp.
MpscTunnelSender::send now tries try_send first, falling back to
send().await only when the channel is full. try_send bypasses the
tokio batch_semaphore Acquire::poll + add_permits_locked machinery
(~9.4% of CPU in samply profiling), which is pure overhead when the
channel has capacity.
In the ring-tunnel bench (4 threads, 1400B, 15s) the channel(32) fast
path hits >99%, so the fallback rarely triggers.
Benchmark improvement:
pps: 230K -> 246K (+7.0%)
send_msg_internal avg: 3.25us -> 3.13us (-120ns/pkt)
forward_one_round calls: 2.46M -> 706K (-71%, bigger batches)
All mpsc tests pass.
Replace has_peer(dst_peer_id) + send_msg_directly() with a single
get_peer_by_id() call, eliminating one redundant dashmap contains_key
query (~50-100ns) per packet on the direct-peer happy path.
send_msg_directly is no longer called from send_msg_internal but remains
available for other callers. All 7 send_msg_internal tests pass.
Benchmark (4 threads, 1400B pkts, 15s):
Before: 234K pps, send_msg_internal avg 3.26us
After: 230K pps, send_msg_internal avg 3.25us
Delta within noise; ~10-50ns/pkt saved as expected for one fewer hash.
Replace std::time::Instant with quanta::Instant on per-packet, per-RPC,
and per-session paths. TSC-based, ~5ns vs ~25ns per now() call.
Reuses the existing `extern crate self as hotpath` alias so
`use hotpath::instant::Instant;` resolves to the same quanta type with
or without the hotpath feature. Leaves tokio::time::Instant and
smoltcp::time::Instant untouched.
Handle TUN receive errors by marking the fake TCP stack closed and
clearing registered sockets instead of panicking.
Refuse new sockets on closed stacks and let listeners recreate stacks
when the reader task exits.
Discard malformed fake TCP frames instead of panicking so OpenWrt
nodes can survive unexpected or truncated packets.
Also emit the correct IPv6 ethertype and cover the parser with
round-trip and truncation regression tests.
Addresses issue #2016 where nodes behind unstable networks
(e.g. campus firewalls) cause excessive traffic that can freeze
the remote node.
Two changes in peer_ospf_route.rs:
- Make do_sync_route_info only trigger reverse sync_now when
incoming data actually changed the route table or foreign
network state. The previous unconditional sync_now created
an A->B->A->B ping-pong cycle on every RPC exchange.
- Add exponential backoff (50ms..5s) to session_task retry loop.
The previous fixed 50ms retry produced ~20 RPCs/s during
sustained network instability.
Normalize composite tunnel display values before rendering peer and
debug output so IPv6 tunnel types no longer append `6` to the port.
- Preserve prefixes like `txt-` while converting tunnel schemes to
their IPv6 form.
- Recover malformed values such as `txt-tcp://...:110106` into
`txt-tcp6://...:11010`.
- Reuse the normalized remote address display in CLI debug output.
Implement the previously stubbed connector add/remove CLI commands
using PatchConfig RPC with InstanceConfigPatch.connectors, and
remove the peer add/remove stubs that had incorrect semantics.
PeerCenterRpc was only registered in the per-instance peer-to-peer RPC
manager (domain = network_name), but not in the management API server
(domain = ""). The CLI connects to the management API with an empty
domain, causing "Invalid service name: PeerCenterRpc" errors.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
When two EasyTier instances run on the same machine and share the same
network, the direct connector would expand a remote peer's 0.0.0.0
listener into local interface IPs and then attempt to connect to
itself, causing an infinite loop of failed connection attempts.
The existing `peer_id != my_peer_id` guard does not cover this case
because the two instances have different peer IDs despite sharing the
same physical network interfaces.
Fix by adding a self-connection check in `spawn_direct_connect_task`:
before spawning a connect task, compare the candidate (scheme, IP,
port) against the local running listeners. If a local listener matches
on all three dimensions — accounting for 0.0.0.0/:: wildcards by
checking membership in the local interface IP sets — the candidate is
silently dropped with a DEBUG log message.
The fix covers all four code paths:
- IPv4 unspecified (0.0.0.0) expansion loop
- IPv4 specific-address branch
- IPv6 unspecified (::) expansion loop
- IPv6 specific-address branch
The TESTING flag logic is untouched so existing unit tests are
unaffected.
* refactor(connector): replace is_self_connect closure with GlobalCtx::should_deny_proxy (#1954)
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Addresses security vulnerabilities in axios <1.13.5. Updates the
declared specifier to ^1.13.5 in all three frontend package.json
files and regenerates both npm and pnpm lock files (resolved: 1.13.6).
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Magic DNS updates are full snapshots, so appending routes keeps old IPs and returns duplicate A records. Replace each client's previous routes on update and add a regression test to ensure hostname resolution keeps only the latest IP.
Prevent crashes when users input quic://...:0 by rejecting port 0 explicitly and propagating connect setup errors. Add a regression test to ensure invalid QUIC targets fail gracefully.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
- drop low-priority columns when tables exceed terminal width
- truncate optional columns to fit remaining width
- add --no-trunc flag to disable truncation
- compute column widths using unicode display width
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
- Add short column names for latency, loss rate, rx/tx bytes, tunnel protocol and NAT type
- Format loss rate as percentage with one decimal place
- Change table style from modern to markdown for better readability
* refactor: update global context STUN server initialization
Modified global context initialization to use a single StunInfoCollector
instance with properly configured IPv4 and IPv6 servers instead of
creating separate instances.
feat: add IPv6 STUN server configuration support
Added interface methods and config struct fields to support both IPv4
and IPv6 STUN server configuration. Modified getter and setter methods
to handle Option<Vec<String>> type for both server types.
feat: enhance StunInfoCollector with IPv6 support
Updated StunInfoCollector to support both IPv4 and IPv6 STUN servers.
Added new constructor that accepts both server types and methods to set
them independently.
feat: add CLI argument for IPv6 STUN servers
Added command line argument support for configuring IPv6 STUN servers.
Updated configuration setup to handle both IPv4 and IPv6 STUN server
settings.
docs: add localization for STUN server configuration
Added English and Chinese localization strings for the new STUN server
configuration options, including both IPv4 and IPv6 variants.