Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
296 commits
Select commit Hold shift + click to select a range
23dd10c
OS UDP: avoid sort in hello-response eviction
JKamsker Feb 27, 2026
33d5adc
Docs: correct UDP ephemeral port selection
JKamsker Feb 27, 2026
779bc26
Tests: reduce timing and port flakiness
JKamsker Feb 27, 2026
1cd6f43
fix(transport): bound hello-response eviction queue
JKamsker Feb 27, 2026
702b533
docs: clarify UDP bind/receive semantics
JKamsker Feb 27, 2026
70cee3a
test: increase UDP port bind retry budget
JKamsker Feb 27, 2026
3bf1093
fix(node): serialize concurrent network leaves
JKamsker Feb 27, 2026
812f67f
fix(store): validate state root before mkdir
JKamsker Feb 27, 2026
ad305ea
fix(transport): refresh peer last-seen even on dispatch faults
JKamsker Feb 27, 2026
4b9c04a
fix(transport): avoid evicting locally-registered networks
JKamsker Feb 27, 2026
9c4ab55
fix(net): validate UDP checksum using header length
JKamsker Feb 27, 2026
6fd46fd
fix(store): re-check root reparse point on each access
JKamsker Feb 27, 2026
552d6b3
fix(io): strip BOMs in bounded text reads
JKamsker Feb 27, 2026
081a03b
fix(sockets): dispose stream if connect canceled mid-init
JKamsker Feb 27, 2026
a359210
fix(tcp): prevent negative PendingAcceptCount race
JKamsker Feb 27, 2026
c906d13
fix(multipath): handle multi-send SocketExceptions consistently
JKamsker Feb 27, 2026
8c6b117
fix(dataplane): enforce root-relayed RX when multipath off
JKamsker Feb 27, 2026
f7e83de
test: relax concurrency deadlines to reduce flakiness
JKamsker Feb 27, 2026
90e0910
fix(udp): drop when incoming queue is full to avoid stalls
JKamsker Feb 27, 2026
abf0ede
fix(socket): dispose created runtime on mid-creation cancellation
JKamsker Feb 27, 2026
a8353ee
fix(sockets): block bind/listen while connect is in progress
JKamsker Feb 27, 2026
c68af1e
fix(http): always release reserved overlay local ports
JKamsker Feb 27, 2026
0c083f4
fix(store): allow symlinked ancestors; validate alias paths
JKamsker Feb 27, 2026
0a86042
fix(net): reject invalid IPv6 AH header lengths
JKamsker Feb 27, 2026
d50e116
test: harden junction helper and relax remaining timeouts
JKamsker Feb 27, 2026
09910e5
fix(dataplane): drop oldest on queue pressure instead of newest
JKamsker Feb 27, 2026
154ab52
fix(dataplane): allow dispatcher drop logic with multi-reader channel
JKamsker Feb 27, 2026
ca58dc5
fix(http): always release reserved port
JKamsker Feb 27, 2026
5b2fabf
fix(tcp): reset segment after full consumption
JKamsker Feb 27, 2026
a899f63
fix(io): fail if secret file perms unsupported
JKamsker Feb 27, 2026
7f6c668
chore(test): remove unused using
JKamsker Feb 27, 2026
6e05adf
fix(store): validate root reparse after create
JKamsker Feb 27, 2026
73e3085
fix(internal): prevent ActiveTaskSet.WaitAsync spin
JKamsker Feb 27, 2026
e007a54
fix(dataplane): skip decode for non-root when direct RX off
JKamsker Feb 27, 2026
5a6d4e9
test: reduce concurrency flakiness in channel writer tests
JKamsker Feb 27, 2026
efbe91d
fix(udp): restore destination IP filtering on receive
JKamsker Feb 27, 2026
d51f3ee
Revert "fix(udp): restore destination IP filtering on receive"
JKamsker Feb 27, 2026
333a356
Revert "fix(dataplane): skip decode for non-root when direct RX off"
JKamsker Feb 27, 2026
2e729c1
fix(dataplane): early-drop non-root when direct RX off
JKamsker Feb 27, 2026
9aa4877
fix(udp): avoid extra drops on backpressure
JKamsker Feb 27, 2026
7222815
fix(dataplane): avoid extra peer-queue drops
JKamsker Feb 27, 2026
55628c5
fix(bootstrap): avoid sync-over-async udp cleanup
JKamsker Feb 27, 2026
b772580
fix(tcp): make listener dispose backlog drain best-effort
JKamsker Feb 27, 2026
0be0ece
test: use CreateUdpTransportAsync in bootstrapper tests
JKamsker Feb 27, 2026
5938b99
fix(io): treat non-seekable reads as Try* failure
JKamsker Feb 27, 2026
3d68414
fix(store): bounded read for managed IPs file
JKamsker Feb 27, 2026
499ac06
fix(store): reject Windows reparse-point ancestors
JKamsker Feb 27, 2026
4da105e
fix(socket): avoid NRE racing runtime/join tasks
JKamsker Feb 27, 2026
155b40c
fix(events): bound NodeEventStream dispatch backlog
JKamsker Feb 27, 2026
f377164
fix(multipath): always dispose udp sockets even on faults
JKamsker Feb 27, 2026
3770bbc
fix: harden ZeroTierUdpMultiTransport disposal
JKamsker Feb 27, 2026
44a7295
fix: harden ZeroTierUdpTransport disposal
JKamsker Feb 27, 2026
5b1c9a0
fix: reject duplicate multipath UDP ports
JKamsker Feb 27, 2026
1953045
fix: ensure IPv6-only UDP socket is v6-only
JKamsker Feb 27, 2026
ad0c60f
fix: avoid unused dispose exceptions in Release
JKamsker Feb 27, 2026
e5a6959
Fix LocalUdpPorts duplicate validation when multipath off
JKamsker Feb 27, 2026
fda07de
Skip IPv6 dual-mode test when bind fails
JKamsker Feb 27, 2026
088a320
Throw ObjectDisposedException from ZeroTierUdpTransport.LocalSockets
JKamsker Feb 27, 2026
02cf186
Avoid disposing SemaphoreSlim locks in DisposeAsync
JKamsker Feb 27, 2026
24117f5
Dispose queued OverlayTcpClient instances on listener shutdown
JKamsker Feb 27, 2026
95e49a7
Suppress CA2213 for idempotent DisposeAsync locks
JKamsker Feb 27, 2026
be11310
fix(overlay): avoid multi-reader accept queue and writes-after-dispose
JKamsker Feb 27, 2026
13fff12
fix(transport): make OsUdpNodeTransport DisposeAsync idempotent
JKamsker Feb 27, 2026
9a998a6
fix(tcp): make UserSpaceTcpSender disposal concurrency-safe
JKamsker Feb 27, 2026
006ac9f
fix(tcp): make UserSpaceTcp client/server DisposeAsync idempotent
JKamsker Feb 27, 2026
832b22c
fix(node): avoid disposing lifecycle state lock
JKamsker Feb 27, 2026
67d5ae2
fix(node): prevent leave-gate map growth
JKamsker Feb 27, 2026
a9a317a
fix(tcp): unblock pending sender waits on dispose
JKamsker Feb 27, 2026
ee7912a
fix(transport): make disposed flag volatile
JKamsker Feb 27, 2026
39663e3
chore(analyzers): suppress CA1001 for NodeNetworkService gate
JKamsker Feb 27, 2026
aa678c8
fix(node): serialize disposal with lifecycle operations
JKamsker Feb 27, 2026
9392a40
fix(node): make runtime Disposed flag thread-safe
JKamsker Feb 27, 2026
0160eb3
fix(node): avoid wedging dispose and block ops early
JKamsker Feb 27, 2026
246b477
fix(node): make JoinNetworkAsync idempotent
JKamsker Feb 27, 2026
69af991
fix(transport): only teardown OS-UDP network state when last subscrib…
JKamsker Feb 27, 2026
967cead
fix(node): time-bound transport disposal
JKamsker Feb 27, 2026
aacf56c
fix(transport): time-bound OS-UDP receiver shutdown
JKamsker Feb 27, 2026
2a1b31a
fix(node): make peer recovery best-effort and avoid join inconsistent…
JKamsker Feb 27, 2026
5b4c3cf
fix(sockets): make ZtTcpListener DisposeAsync idempotent
JKamsker Feb 27, 2026
f541654
test: avoid networkId collision across parallel in-memory tests
JKamsker Feb 27, 2026
855d926
test: use xUnit v2 skip exception
JKamsker Feb 27, 2026
10ca4e2
fix: run root reparse checks cross-platform
JKamsker Feb 27, 2026
4af2da0
fix: make node event dispatch queue lossless
JKamsker Feb 27, 2026
22b8956
fix: always complete events on dispose
JKamsker Feb 27, 2026
9a06ef2
fix: gate join rollback leave operations
JKamsker Feb 27, 2026
d242cba
fix: keep RX loop running after queue drop
JKamsker Feb 27, 2026
bb12cfa
fix: bound hole-punch cache size
JKamsker Feb 27, 2026
9c90c9d
fix: compare-and-remove stale negotiation state
JKamsker Feb 27, 2026
7265c82
fix: avoid orphaned ACK waiter on dispose race
JKamsker Feb 27, 2026
3e1526b
fix: synchronize runtime dispose with creation
JKamsker Feb 27, 2026
889a0ce
fix: filter UDP receives by bound local address
JKamsker Feb 27, 2026
4e4c44f
test: use SkippableFact for runtime skips
JKamsker Feb 27, 2026
0261fac
fix: allow wildcard UDP sockets to receive any dst
JKamsker Feb 27, 2026
95a73cb
fix: prevent runtime resurrection without deadlocking dispose
JKamsker Feb 27, 2026
98e6c63
test: treat UDP wildcard bind as IPAddress.Any
JKamsker Feb 27, 2026
ec7dea0
fix: enhance multipath configuration validation in CLI commands
JKamsker Mar 1, 2026
c11476a
feat: add initial documentation for ZTSharp library and usage rules
JKamsker Mar 15, 2026
45161d6
feat: implement multipath options and direct path selection enhancements
JKamsker Mar 15, 2026
628ccd2
Preserve direct endpoint socket affinity
JKamsker Mar 15, 2026
3ce1fac
Try hinted direct probes on mapped sockets
JKamsker Mar 15, 2026
0496e5b
Retry root surface probing across sockets
JKamsker Mar 15, 2026
a069804
Bootstrap peer trust before direct path probing
JKamsker Mar 15, 2026
06632c2
Improve direct path confirmation and rendezvous punch
JKamsker Mar 15, 2026
e233345
Reduce direct hint fanout during bootstrap
JKamsker Mar 15, 2026
e5987e8
Filter incompatible private direct path hints
JKamsker Mar 15, 2026
3bb95b8
Avoid duplicate direct hint rebootstrap
JKamsker Mar 15, 2026
3b80ce2
Tune direct bootstrap probe cadence
JKamsker Mar 15, 2026
d93578d
Refine push-direct bootstrap policy
JKamsker Mar 15, 2026
40cb39d
Support upstream empty echo direct probes
JKamsker Mar 15, 2026
2730140
Keep hinted direct paths alive during maintenance
JKamsker Mar 15, 2026
4f18e90
Refactor internal classes for improved organization and maintainability
JKamsker Mar 15, 2026
9b9410c
Maintain relayed peers in direct-path maintenance
JKamsker Mar 15, 2026
5ed7ebc
Prioritize reachable hinted path scopes
JKamsker Mar 15, 2026
01a823e
Probe hinted direct paths across fallback sockets
JKamsker Mar 15, 2026
61d107c
Modularize direct hint path planning
JKamsker Mar 15, 2026
462b5d1
Keep direct candidates per scope and family
JKamsker Mar 15, 2026
baccb79
Align push-direct probing with upstream
JKamsker Mar 15, 2026
48c34b1
Gate private direct hints by local networks
JKamsker Mar 16, 2026
3e3ef89
Allow shared direct hints during bootstrap
JKamsker Mar 16, 2026
c021254
Trace pre-decode direct inbound traffic
JKamsker Mar 16, 2026
0e9709c
Advertise local interface direct paths
JKamsker Mar 16, 2026
55bccec
Fix local direct path source initialization
JKamsker Mar 16, 2026
93519b6
Throttle hinted direct maintenance probes
JKamsker Mar 16, 2026
dea236b
Filter local direct path advertisements
JKamsker Mar 16, 2026
850d703
Loosen remote path hints and trim local ads
JKamsker Mar 16, 2026
a6d28b4
Improve direct bootstrap compatibility
JKamsker Mar 16, 2026
00de4cf
Preserve push-path socket affinity
JKamsker Mar 16, 2026
a91dfca
Bind multipath sockets to local interfaces
JKamsker Mar 16, 2026
9b836ba
Filter local bind addresses by gateway
JKamsker Mar 16, 2026
21cf26a
Fan out direct hint bootstrap across sockets
JKamsker Mar 16, 2026
49f94f0
Stabilize direct hint probing tests
JKamsker Mar 16, 2026
276e0f8
Trim unusable direct path probes
JKamsker Mar 16, 2026
ce4d16a
Probe direct hints with HELLO first
JKamsker Mar 16, 2026
1896717
Filter pushed direct paths by peer reachability
JKamsker Mar 16, 2026
6313130
Track self-awareness per reporter path
JKamsker Mar 16, 2026
5ee9db4
Fix inbound direct HELLO confirmation build
JKamsker Mar 16, 2026
e3b91d7
Retry repeated push-direct candidates
JKamsker Mar 16, 2026
6b60c6c
Fan out root path advertisements across sockets
JKamsker Mar 16, 2026
8949588
Fan out relayed credentials across root sockets
JKamsker Mar 16, 2026
8da299c
Use peer root socket affinity for relayed control
JKamsker Mar 16, 2026
0ae43dd
Prefer peer root socket for root hello
JKamsker Mar 16, 2026
4a5a136
Align ok-hello wire format with upstream
JKamsker Mar 16, 2026
3978c61
Relax direct reply correlation for NAT remaps
JKamsker Mar 16, 2026
0d230c1
Relay public rendezvous hints to peers
JKamsker Mar 16, 2026
7589a68
Prefer affine public rendezvous hint
JKamsker Mar 16, 2026
7bf882a
Prefer single socket for direct hints
JKamsker Mar 16, 2026
db4f9ec
Align direct bootstrap with upstream hints
JKamsker Mar 16, 2026
61c563d
Fan out push-direct probes across sockets
JKamsker Mar 16, 2026
70dc25b
Tighten direct hint socket selection
JKamsker Mar 16, 2026
a3847ca
Fan out direct-only HELLO probes
JKamsker Mar 16, 2026
29965f4
Reuse direct HELLO packets across sockets
JKamsker Mar 16, 2026
d78e466
Force direct-only hinted HELLO bootstrap
JKamsker Mar 16, 2026
26c7711
Probe hinted direct paths on all admissible sockets
JKamsker Mar 16, 2026
584f083
Probe hinted direct paths on all admissible sockets
JKamsker Mar 16, 2026
5a5a214
Prefer root family for multipath UDP binds
JKamsker Mar 16, 2026
2ce7c4b
Prefer route-selected bind address for multipath
JKamsker Mar 16, 2026
268303d
Fan out rendezvous bootstrap across eligible sockets
JKamsker Mar 16, 2026
21bd4ac
Extend direct-only HTTP connect timeout
JKamsker Mar 16, 2026
788c224
Keep relayed bootstrap alive on all root sockets
JKamsker Mar 16, 2026
de1e446
Allow direct-only payloads on hinted paths
JKamsker Mar 16, 2026
118bbe7
Rotate hinted direct payload paths
JKamsker Mar 16, 2026
d51d6f1
Fan out direct-only SYN payloads
JKamsker Mar 16, 2026
4d5857c
Extend direct-only TCP SYN budget
JKamsker Mar 16, 2026
244d6bd
Fan out direct-only SYNs across sockets
JKamsker Mar 16, 2026
06b1460
Use echo-first direct-only hinted maintenance
JKamsker Mar 16, 2026
1d66786
Use same-socket rendezvous direct bootstrap
JKamsker Mar 16, 2026
1933d01
Honor same-socket direct hint bootstrap
JKamsker Mar 16, 2026
66504d1
Narrow first-hop direct hint bootstrap
JKamsker Mar 16, 2026
dce8ef5
Keep maintenance hinted probes socket-affine
JKamsker Mar 16, 2026
deeb93d
Keep direct-only hinted payloads flow-sticky
JKamsker Mar 16, 2026
6eab6e8
Prefer hinted socket affinity for direct-only maintenance
JKamsker Mar 16, 2026
f24b275
Keep direct bootstrap probes on hinted socket
JKamsker Mar 16, 2026
fa24e21
Prefer fresh push-direct endpoints over stale ones
JKamsker Mar 16, 2026
0465e64
Narrow direct-only probing to one hinted endpoint
JKamsker Mar 16, 2026
2c8d4d5
Prefer rendezvous endpoints over pushed hints
JKamsker Mar 16, 2026
f732e56
Advertise local surfaces in direct HELLO
JKamsker Mar 16, 2026
23664ad
Relay public rendezvous surfaces via root
JKamsker Mar 16, 2026
2d92dc1
Force HELLO on root rendezvous bootstrap
JKamsker Mar 16, 2026
d6c850c
Restore upstream direct HELLO endpoint semantics
JKamsker Mar 16, 2026
2a75b5e
Gate direct path promotion on hop-0 OK
JKamsker Mar 16, 2026
27eed34
Remove peer relayed rendezvous bootstrap
JKamsker Mar 16, 2026
f8fab8e
Stop pinning ordinary pushed direct paths to one socket
JKamsker Mar 16, 2026
546c1af
Retry sticky direct-only hints with periodic HELLO
JKamsker Mar 16, 2026
c96fc7c
Fan out periodic rendezvous HELLO retries
JKamsker Mar 16, 2026
15f83a7
Gate direct-only payload on confirmed paths
JKamsker Mar 16, 2026
aae36c9
Wait for hinted paths before SYN fanout
JKamsker Mar 16, 2026
bb45a14
Prime direct-only hinted paths with HELLO
JKamsker Mar 16, 2026
9f6014d
Advertise local surfaces in direct-only HELLO
JKamsker Mar 16, 2026
32fadbf
Use advertised surfaces in hinted HELLO bootstrap
JKamsker Mar 16, 2026
dac5a36
Tighten direct bootstrap relay control
JKamsker Mar 16, 2026
3f54b8a
Prefer pinned rendezvous paths in direct-only mode
JKamsker Mar 16, 2026
53259d6
Use echo-first hinted direct bootstrap
JKamsker Mar 16, 2026
cd6238f
Refresh pinned rendezvous hole punches
JKamsker Mar 16, 2026
659b012
Clamp direct-only traffic to pinned rendezvous
JKamsker Mar 16, 2026
713a298
Keep pinned rendezvous maintenance on one socket
JKamsker Mar 16, 2026
fc8f0fd
Report local surface in direct HELLO
JKamsker Mar 16, 2026
61ef904
Clamp direct ads to pinned rendezvous socket
JKamsker Mar 16, 2026
97a5268
Pin relayed control to rendezvous socket
JKamsker Mar 16, 2026
3ba2f50
Restore upstream HELLO endpoint semantics
JKamsker Mar 16, 2026
06485dd
Match upstream rendezvous hole-punch size
JKamsker Mar 16, 2026
c395f93
Reduce ordinary push-direct socket fanout
JKamsker Mar 16, 2026
99d6804
Honor single-socket ordinary direct hints
JKamsker Mar 16, 2026
4a0bab8
Narrow unresolved direct-only payload fanout
JKamsker Mar 16, 2026
e3b9902
Keep unresolved direct-only hinted HELLOs on one socket
JKamsker Mar 16, 2026
3231734
Keep ordinary direct hints on root-affine sockets
JKamsker Mar 16, 2026
6f588eb
Match upstream rendezvous hole-punch size
JKamsker Mar 16, 2026
e325c28
Probe alternate public hints while payload stays pinned
JKamsker Mar 16, 2026
beeed04
Restore upstream direct bootstrap advertisements
JKamsker Mar 16, 2026
6e0ab6c
Restore upstream ordinary direct hint fanout
JKamsker Mar 16, 2026
2f67b44
Pin relayed root control to observed socket
JKamsker Mar 16, 2026
8cad9d7
Fan out direct-only payload beyond rendezvous
JKamsker Mar 16, 2026
c3a9a26
Force direct-only HELLO on alternate hints
JKamsker Mar 16, 2026
a2498a3
Accept CGNAT direct hints on shared space
JKamsker Mar 16, 2026
d5332d7
Accept routed private hints on allowed sockets
JKamsker Mar 16, 2026
b4e6d87
Advertise local private paths for private peers
JKamsker Mar 16, 2026
95ece29
Pin private direct hints to root socket
JKamsker Mar 16, 2026
abceb72
Pin ordinary direct hints to observed socket
JKamsker Mar 16, 2026
3f6d3a2
Clamp direct-only fanout to pinned rendezvous
JKamsker Mar 16, 2026
93c9c07
Handle relayed peer rendezvous control
JKamsker Mar 16, 2026
9052307
Relay peer rendezvous across public surfaces
JKamsker Mar 16, 2026
1f12e26
Restore upstream direct rendezvous semantics
JKamsker Mar 16, 2026
91ab54b
Add direct p2p investigation logbook
JKamsker Mar 16, 2026
74e440d
Fan out ordinary pushed direct hints
JKamsker Mar 16, 2026
bf8a0bd
Rotate ordinary direct hint bootstrap
JKamsker Mar 16, 2026
40691e2
Skip alternate hints after rendezvous
JKamsker Mar 16, 2026
b39da32
Delay direct-only payload until rendezvous
JKamsker Mar 16, 2026
b7e1a83
Delay payload after pinned rendezvous
JKamsker Mar 16, 2026
e0f9c1b
Advertise full local paths after rendezvous
JKamsker Mar 16, 2026
30bf131
Send full local ads before rendezvous bootstrap
JKamsker Mar 16, 2026
76b8100
Require rendezvous for direct-only payload
JKamsker Mar 16, 2026
4790586
Honor receiving socket for direct hint bootstrap
JKamsker Mar 16, 2026
6c2b90b
Refresh root hello on rendezvous socket
JKamsker Mar 16, 2026
5d1cf8f
Pin root control after rendezvous
JKamsker Mar 16, 2026
1627cb5
Delay rendezvous probes until peer version known
JKamsker Mar 16, 2026
29985a0
Delay direct rendezvous callback probes
JKamsker Mar 16, 2026
87377b5
Skip relay refresh on direct rendezvous
JKamsker Mar 16, 2026
7ea7203
Suppress relay maintenance on pinned rendezvous
JKamsker Mar 16, 2026
7d07a10
Probe sibling hints alongside pinned rendezvous
JKamsker Mar 16, 2026
b07e51f
Keep pinned rendezvous maintenance on echo
JKamsker Mar 16, 2026
4ddaa93
Relax pinned rendezvous maintenance test
JKamsker Mar 16, 2026
e0105ee
Keep root hello alive until echo is legal
JKamsker Mar 16, 2026
cf3493c
Revisit alternate hints during pinned maintenance
JKamsker Mar 16, 2026
2f0c0d4
Rotate alternate hinted maintenance sockets
JKamsker Mar 16, 2026
e7d8de5
Require rendezvous before direct-only hinted control
JKamsker Mar 16, 2026
1c12772
Predict direct-path ports around rendezvous
JKamsker Mar 16, 2026
19ef004
Pin sibling direct probes to rendezvous socket
JKamsker Mar 16, 2026
2ac30d1
Keep pinned sibling direct probes on echo
JKamsker Mar 16, 2026
4bcd1a8
Clamp initial pinned sibling bootstrap
JKamsker Mar 16, 2026
83ab9bb
Prioritize nonpublic alternates after rendezvous
JKamsker Mar 16, 2026
e4854fd
Log stock ZeroTier path analysis
JKamsker Mar 16, 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
8 changes: 8 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

# ZTSharp
ZTSharp is a fully managed c# library that implements the Zerotier protocol without relying on unmanaged code or external dependencies.

# Rules
- Do not use or reuse official Zerotier services, connections or identities from the current host. The library **must** be self-contained and not rely on any existing Zerotier configuration or services on the host machine.
- The source code must be written in clean, modular, easy to understand and maintainable style. It should follow best practices in c#.
- The source code must be high-performant and efficient, minimizing resource usage and maximizing speed (Eg use ArrayPool, Span, etc... instead of creating new arrays or lists all of the time (especially hot paths)).
23 changes: 15 additions & 8 deletions ZTSharp.Tests/AtomicFileTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,23 @@ public async Task WriteAllBytesAsync_Throws_WhenAtomicMoveNeverSucceeds()
var root = TestTempPaths.CreateGuidSuffixed("zt-atomic-file-");
Directory.CreateDirectory(root);

var destination = Path.Combine(root, "dest");
Directory.CreateDirectory(destination);

var ex = await Assert.ThrowsAsync<IOException>(async () =>
try
{
await AtomicFile.WriteAllBytesAsync(destination, new byte[] { 1, 2, 3 }, CancellationToken.None);
});
var destination = Path.Combine(root, "dest");
Directory.CreateDirectory(destination);

var ex = await Assert.ThrowsAsync<IOException>(async () =>
{
await AtomicFile.WriteAllBytesAsync(destination, new byte[] { 1, 2, 3 }, CancellationToken.None);
});

Assert.Contains("Atomic replace failed", ex.Message, StringComparison.OrdinalIgnoreCase);
Assert.True(Directory.Exists(destination));
Assert.Contains("Atomic replace failed", ex.Message, StringComparison.OrdinalIgnoreCase);
Assert.True(Directory.Exists(destination));
}
finally
{
Directory.Delete(root, recursive: true);
}
}
}

29 changes: 29 additions & 0 deletions ZTSharp.Tests/BoundedFileIOBomTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Text;
using ZTSharp.Internal;

namespace ZTSharp.Tests;

public sealed class BoundedFileIOBomTests
{
[Fact]
public void TryReadAllText_StripsUtf8Bom()
{
var path = TestTempPaths.CreateGuidSuffixed("bounded-io-bom-");
try
{
var payload = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes("hello")).ToArray();
File.WriteAllBytes(path, payload);

Assert.True(BoundedFileIO.TryReadAllText(path, maxBytes: 1024, Encoding.UTF8, out var text));
Assert.Equal("hello", text);
}
finally
{
if (File.Exists(path))
{
File.Delete(path);
}
}
}
}

41 changes: 19 additions & 22 deletions ZTSharp.Tests/ChannelWriterConcurrencyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ public async Task InMemoryZtUdpClient_ReceivesDatagrams_UnderConcurrentSends()
sendTasks[i] = sender.SendToAsync(payload, receiverId, remotePort: 30000);
}

await Task.WhenAll(sendTasks).WaitAsync(TimeSpan.FromSeconds(2));
await Task.WhenAll(sendTasks).WaitAsync(TimeSpan.FromSeconds(10));

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
var seen = new bool[count];
for (var i = 0; i < count; i++)
{
Expand Down Expand Up @@ -68,7 +68,7 @@ public async Task InMemoryOverlayTcpListener_AcceptsMultipleConcurrentClients()

await using var listener = new OverlayTcpListener(serverNode, networkId, serverPort);

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
var acceptTask = Task.Run(async () =>
{
var accepted = new List<OverlayTcpClient>(capacity: count);
Expand All @@ -94,8 +94,8 @@ public async Task InMemoryOverlayTcpListener_AcceptsMultipleConcurrentClients()
}, cts.Token);
}

await Task.WhenAll(connectTasks).WaitAsync(TimeSpan.FromSeconds(5), cts.Token);
var acceptedClients = await acceptTask.WaitAsync(TimeSpan.FromSeconds(5), cts.Token);
await Task.WhenAll(connectTasks).WaitAsync(TimeSpan.FromSeconds(10), cts.Token);
var acceptedClients = await acceptTask.WaitAsync(TimeSpan.FromSeconds(10), cts.Token);
Assert.Equal(count, acceptedClients.Count);

foreach (var accepted in acceptedClients)
Expand Down Expand Up @@ -158,36 +158,33 @@ void CaptureSyn(in RawFrame frame)
await using var client = new OverlayTcpClient(clientNode, networkId, clientPort);
await client.ConnectAsync(serverNode.NodeId.Value, serverPort);

await using var _ = await acceptTask.WaitAsync(TimeSpan.FromSeconds(2));
await using var _ = await acceptTask.WaitAsync(TimeSpan.FromSeconds(10));

var (connectionId, sourcePort) = await synInfoTcs.Task.WaitAsync(TimeSpan.FromSeconds(2));
var (connectionId, sourcePort) = await synInfoTcs.Task.WaitAsync(TimeSpan.FromSeconds(10));

const int frames = 500;
var payload = Encoding.ASCII.GetBytes("abcdefgh");

var sendTasks = new Task[frames];
for (var i = 0; i < frames; i++)
{
sendTasks[i] = Task.Run(async () =>
{
var dataFrame = new byte[OverlayTcpFrameCodec.HeaderLength + payload.Length];
OverlayTcpFrameCodec.BuildHeader(
OverlayTcpFrameCodec.FrameType.Data,
sourcePort: serverPort,
destinationPort: clientPort,
destinationNodeId: clientNode.NodeId.Value,
connectionId,
dataFrame);
payload.CopyTo(dataFrame.AsSpan(OverlayTcpFrameCodec.HeaderLength));
await serverNode.SendFrameAsync(networkId, dataFrame);
});
var dataFrame = new byte[OverlayTcpFrameCodec.HeaderLength + payload.Length];
OverlayTcpFrameCodec.BuildHeader(
OverlayTcpFrameCodec.FrameType.Data,
sourcePort: serverPort,
destinationPort: clientPort,
destinationNodeId: clientNode.NodeId.Value,
connectionId,
dataFrame);
payload.CopyTo(dataFrame.AsSpan(OverlayTcpFrameCodec.HeaderLength));
sendTasks[i] = serverNode.SendFrameAsync(networkId, dataFrame);
}

await Task.WhenAll(sendTasks).WaitAsync(TimeSpan.FromSeconds(2));
await Task.WhenAll(sendTasks).WaitAsync(TimeSpan.FromSeconds(20));

var clientStream = client.GetStream();
var buffer = new byte[frames * payload.Length];
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));
var read = await StreamTestHelpers.ReadExactAsync(clientStream, buffer, buffer.Length, cts.Token);
Assert.Equal(buffer.Length, read);
}
Expand Down
37 changes: 37 additions & 0 deletions ZTSharp.Tests/DirectBootstrapProbeCursorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Net;
using ZTSharp.ZeroTier.Internal;

namespace ZTSharp.Tests;

public sealed class DirectBootstrapProbeCursorTests
{
[Fact]
public void TakeNextEndpoints_RotatesAcrossCandidates()
{
var cursor = new DirectBootstrapProbeCursor();
var endpoints = new[]
{
new IPEndPoint(IPAddress.Parse("176.66.90.119"), 8212),
new IPEndPoint(IPAddress.Parse("176.66.90.119"), 19665),
new IPEndPoint(IPAddress.Parse("176.66.90.119"), 59827)
};

var first = cursor.TakeNextEndpoints(endpoints, budget: 2);
var second = cursor.TakeNextEndpoints(endpoints, budget: 2);

Assert.Equal(new[] { endpoints[0], endpoints[1] }, first);
Assert.Equal(new[] { endpoints[2], endpoints[0] }, second);
}

[Fact]
public void TakeNextSocketIndex_RotatesPerEndpoint()
{
var cursor = new DirectBootstrapProbeCursor();
var endpoint = new IPEndPoint(IPAddress.Parse("176.66.90.119"), 8212);

Assert.Equal(0, cursor.TakeNextSocketIndex(endpoint, socketCount: 3));
Assert.Equal(1, cursor.TakeNextSocketIndex(endpoint, socketCount: 3));
Assert.Equal(2, cursor.TakeNextSocketIndex(endpoint, socketCount: 3));
Assert.Equal(0, cursor.TakeNextSocketIndex(endpoint, socketCount: 3));
}
}
27 changes: 16 additions & 11 deletions ZTSharp.Tests/FileStateStoreSecurityTests.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
using System.Diagnostics;
using System.Net;

namespace ZTSharp.Tests;

public sealed class FileStateStoreSecurityTests
{
[Fact]
[SkippableFact]
public async Task ReadAsync_Throws_WhenPathTraversesJunction()
{
if (!OperatingSystem.IsWindows())
{
return;
}
Skip.IfNot(OperatingSystem.IsWindows(), "Junction traversal tests require Windows.");

var root = TestTempPaths.CreateGuidSuffixed("zt-state-root-");
Directory.CreateDirectory(root);
Expand All @@ -23,10 +19,7 @@ public async Task ReadAsync_Throws_WhenPathTraversesJunction()
await File.WriteAllBytesAsync(secretPath, new byte[] { 1, 2, 3 });

var junction = Path.Combine(root, "escape");
if (!TryCreateJunction(junction, outside))
{
return;
}
Skip.IfNot(TryCreateJunction(junction, outside), "Failed to create junction (insufficient privileges or mklink unavailable).");

var store = new FileStateStore(root);
_ = await Assert.ThrowsAsync<InvalidOperationException>(async () => await store.ReadAsync("escape/secret.bin"));
Expand Down Expand Up @@ -76,7 +69,19 @@ private static bool TryCreateJunction(string junctionPath, string targetPath)
return false;
}

process.WaitForExit(milliseconds: 5000);
if (!process.WaitForExit(milliseconds: 5000))
{
try
{
process.Kill(entireProcessTree: true);
}
catch
{
}

return false;
}

return process.ExitCode == 0 && Directory.Exists(junctionPath);
}
catch
Expand Down
32 changes: 32 additions & 0 deletions ZTSharp.Tests/Ipv6CodecAhHeaderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Net;
using ZTSharp.ZeroTier.Net;

namespace ZTSharp.Tests;

public sealed class Ipv6CodecAhHeaderTests
{
[Fact]
public void TryParseTransportPayload_RejectsInvalidAhHeaderLength()
{
var src = IPAddress.Parse("2001:db8::1");
var dst = IPAddress.Parse("2001:db8::2");

var payload = new byte[8 + 8];
payload[0] = UdpCodec.ProtocolNumber; // next header
payload[1] = 0; // AH payload len (0 => 8 bytes total, invalid; must be >= 12 bytes)

var packet = new byte[40 + payload.Length];
packet[0] = 0x60; // version
packet[4] = (byte)(payload.Length >> 8);
packet[5] = (byte)(payload.Length & 0xFF);
packet[6] = 51; // AH
packet[7] = 64; // hop limit

src.GetAddressBytes().CopyTo(packet, 8);
dst.GetAddressBytes().CopyTo(packet, 24);
payload.CopyTo(packet, 40);

Assert.False(Ipv6Codec.TryParseTransportPayload(packet, out _, out _, out _, out _, out _));
}
}

Loading