Skip to content

Commit 4535796

Browse files
committed
checkpoint
1 parent aee9477 commit 4535796

104 files changed

Lines changed: 21688 additions & 2824 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AUTORESEARCH1.md

Lines changed: 610 additions & 2 deletions
Large diffs are not rendered by default.

OPTIMIZENETWORKPEER1.md

Lines changed: 851 additions & 91 deletions
Large diffs are not rendered by default.

PACKETRESEARCH1.md

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1402,3 +1402,210 @@ device-to-device drops from the ~118 ms WAN relay to the ~8 ms direct LAN path
14021402
to the Android socket-protect blocker, which is now precisely isolated with
14031403
a concrete fix design.
14041404
- Synthetic RFC-2544 speed server for repeatable in-tunnel measurement.
1405+
1406+
## 18. Final transport-stall and SCTP release pass (2026-07-28)
1407+
1408+
### 18.1 A canceled platform transport could retain a blocked write
1409+
1410+
The retained iOS NetworkExtension trace showed a logical client disappearing
1411+
at 11:42:07 while the old connection's TCP batch write did not return until
1412+
11:42:16. Packet ownership was already removed from the route manager, but H1
1413+
teardown performed this dependency order:
1414+
1415+
```text
1416+
remove route → cancel context → join writer → deferred socket close
1417+
```
1418+
1419+
Context cancellation cannot interrupt a `net.Conn.Write` already blocked in
1420+
the kernel. Socket close was the writer's release operation, yet it was
1421+
deferred until after the join waiting for that writer. The old generation and
1422+
its bounded queues could consequently overlap a reconnect for the complete
1423+
write deadline. H3 had the adjacent inversion when a peer stopped reading and
1424+
QUIC stream credit parked `Stream.Write`.
1425+
1426+
Both paths now close the connection before joining the writer:
1427+
1428+
```text
1429+
remove route → cancel context → close WebSocket/QUIC → join → final drain
1430+
```
1431+
1432+
`TestPlatformTransportCloseInterruptsBlockedH1Write` drives a real WebSocket
1433+
over a wrapper whose `Write` can be released only by `Close`.
1434+
`TestPlatformTransportCloseInterruptsBlockedH3Write` drives a real local QUIC
1435+
connection with fixed 32-KiB stream credit, stops the server reader, fills the
1436+
client's bounded route, and then closes the transport. Both passed ten
1437+
race-enabled repetitions. The asserted upper bound is 500 ms, and H3 normally
1438+
closed in about 45 ms locally. Comparing the physical trace with the test
1439+
bound gives at least an 18× reduction in this teardown tail; it is a lifecycle
1440+
bound, not an end-to-end throughput multiplier.
1441+
1442+
### 18.2 Callback backpressure no longer owns global sequence maps
1443+
1444+
The send, receive, and forward callbacks remain intentionally synchronous
1445+
backpressure. The defect was the lock domain surrounding that contract:
1446+
1447+
- `SendBuffer` held its global sequence-map mutex while closing a sequence.
1448+
Close drains queued `SendPack`s and invokes their completion callbacks. A
1449+
parked callback for one sequence therefore blocked unrelated destination
1450+
lookup/creation.
1451+
- `ReceiveBuffer.Pack` held its global sequence-map mutex while canceling and
1452+
waiting for an older source generation. That worker may be parked in the
1453+
receive callback, so a same-source ordering wait blocked every other
1454+
concurrent source's map access.
1455+
1456+
Send cleanup now removes exact, wire, and destination indexes atomically,
1457+
unlocks, and closes/drains the sequence afterward. Receive replacement cancels
1458+
under the lock, waits outside it, then conditionally removes only the exact old
1459+
generation and retries. The head is deleted only when it still references
1460+
that generation; a missing map entry heals a stale head rather than panicking.
1461+
Forward cleanup also calls sequence close outside its map lock.
1462+
1463+
The intended pressure remains:
1464+
1465+
- send close does not finish until its completion callback returns;
1466+
- a newer receive generation for the same source cannot pass the old callback;
1467+
- the serial packet receive loop does not dispatch around a blocked data
1468+
callback; and
1469+
- no callback timeout, drop, or asynchronous lifetime escape was introduced.
1470+
1471+
The benefit is isolation of unrelated concurrent sequence bookkeeping, not
1472+
removal of data backpressure. The two top-level regressions park the callback,
1473+
prove the same operation remains blocked, prove an unrelated map operation
1474+
finishes within 250 ms, release the callback, and verify ordered completion.
1475+
They passed 20 normal and 20 race-enabled repetitions alongside the
1476+
wire-indistinguishable send-sequence and receive-rejection tests.
1477+
1478+
### 18.3 SCTP 1.11.1 closes adjacent idle/resume failure modes
1479+
1480+
The release graph now uses Pion WebRTC 4.2.18, ICE 4.4.0, SCTP 1.11.1,
1481+
interceptor 0.1.47, and RTP 1.10.5. The SCTP patch is relevant to the observed
1482+
failure class:
1483+
1484+
- a write-loop error closes the underlying transport, releasing a read loop
1485+
that could otherwise wait forever after a one-sided failure;
1486+
- cumulative and gap SACK ranges are validated before the transmit
1487+
acknowledgement point or queues are mutated; and
1488+
- pending DATA, partial acknowledgements, late/crossed shutdown, and graceful
1489+
shutdown advance through a consistent drain boundary.
1490+
1491+
The main SDK was initially aligned while its independent `build`, `cgo`, and
1492+
`js` modules still selected the old patches. Those artifact graphs and the
1493+
local Connect/SDK/server/proxy/validator consumers are now aligned. A new SDK
1494+
top-level regression compares all Pion versions in every artifact `go.mod`
1495+
against the SDK root; it passed 20 normal and 20 race-enabled repetitions.
1496+
1497+
### 18.4 Congestion sweep confirms the retained knee
1498+
1499+
The opt-in CA-step harness was rerun for 203.7 s after the dependency update.
1500+
Single-run measurements were:
1501+
1502+
| CA setting | 1% loss MiB/s (p50/p95) | 50 Mbps shallow queue | 8 Mbps shallow queue |
1503+
|---|---:|---:|---:|
1504+
| 4 MTU | 0.54 (75/845 ms) | 3.03 | 0.82 |
1505+
| 6 MTU | 0.58 (75/1100 ms) | 4.10 | 0.75, ~1 s tail |
1506+
| **8 MTU** | **0.60 (52.8/97.6 ms)** | 2.47, one-MTU collapse | **0.80**, ~110 ms tail |
1507+
| 10 MTU | 0.62 (25.6/99.8 ms) | 3.45 | 0.70, ~1 s tail |
1508+
| 12 MTU | 0.66 (75.3/76.5 ms) | 4.00 | 0.71, ~1 s tail |
1509+
| 16 MTU | 0.79 (75.7/1070 ms) | 4.45 | 0.73, ~1 s tail |
1510+
| 24 MTU | 0.82 (54.1/76.1 ms) | 4.57 | 0.72, 390 ms p50/~1 s tail |
1511+
| 8 MTU + 32 KiB floor | 0.79 (25.5/77.5 ms) | 3.26 | 0.82, ~110 ms tail |
1512+
| 16 MTU + 32 KiB floor | 0.83 (53.8/76.7 ms) | 3.94 | 0.76, 380 ms p50/~1 s tail |
1513+
1514+
This one-shot token/queue phase remains noisy—the 8-MTU 50-Mbps collapse is
1515+
why production selection uses the repeated paired matrices in
1516+
`OPTIMIZENETWORKPEER1.md §F.1`, not the best cell from one run. The new sweep
1517+
confirms the first-principles tradeoff:
1518+
1519+
- larger steps recover faster from exogenous independent loss;
1520+
- the same aggression overshoots real shallow queues and increases RTO pauses;
1521+
- a minimum congestion window improves the loss row by sending despite a real
1522+
congestion signal; and
1523+
- 8 MTU retains the best measured cross-regime knee with stock one-half
1524+
decrease and no forced floor.
1525+
1526+
No SCTP production constant changed. Physical end-to-end steady throughput
1527+
therefore remains 4.8–5.9 MiB/s (about 1.0× the old physical range), while the
1528+
controlled selected-peer receive-window result remains 2.26→28–29 MiB/s
1529+
(about 12.5×). The new wins are bounded teardown, callback-stall locality, and
1530+
upstream association reliability rather than another composable speed
1531+
multiplier.
1532+
1533+
### 18.5 Validation and remaining physical gate
1534+
1535+
The complete post-change Connect tree passed in 479.471 s, with `blocker`,
1536+
`connectctl`, and `extender` also green. The SDK passed in 382.483 s; its
1537+
build/C-binding modules, proxy, and local validator suites pass. Static
1538+
analysis passes across Connect, SDK and native artifact modules, server,
1539+
proxy, and the validator. All added regressions are top-level tests and no
1540+
ordinary `t.Run` was added.
1541+
1542+
The remaining measurement is the latest-build bidirectional
1543+
iPhone↔Pixel cold multi-origin, DNS/TTFB/load, idle/resume, CPU, and footprint
1544+
matrix. The iPhone is currently unplugged. The last installed iOS build did
1545+
not establish the extension after reinstall/permission state changed, so no
1546+
latest-build physical throughput result is inferred from older successful
1547+
captures.
1548+
1549+
### 18.6 Network-contract rollover is now relationship-scoped and bounded
1550+
1551+
A long real-page sequence found that the remaining apparent successor pause
1552+
was not SCTP. Contract creation prefetches the next contract, but the global
1553+
janitor expired any queued result after 120 s. A low-rate sequence could keep
1554+
its current contract open longer than that, then block on a new control round
1555+
trip at the eventual boundary.
1556+
1557+
The initial sizing change also exposed an identity-domain mistake. Marking the
1558+
selected top-level peer ID did not mark provider return traffic, whose
1559+
destination is an ephemeral per-window client ID. The provider continued to
1560+
receive only 13,107 usable bytes from a nominal 16 KiB contract.
1561+
1562+
The retained implementation carries an authenticated, local-only
1563+
`NetworkPeer` policy bit:
1564+
1565+
```text
1566+
selected multi-client default ─┐
1567+
├→ SendSequence → ContractKey
1568+
ProvideMode_Network return ────┘
1569+
├→ 1 MiB first contract
1570+
encrypted-control carrier ────────┘ + live-successor retention
1571+
```
1572+
1573+
The bit is independent of `ForceStream`: public direct streams can force a
1574+
stream without receiving no-escrow Network sizing. It is also absent from
1575+
send-sequence/wire identity, preventing a local sizing policy from forking two
1576+
receiver-indistinguishable sequences. Encryption-control first flights mirror
1577+
the policy. Non-Network companion returns clear both Network policy and
1578+
`ForceStream`.
1579+
1580+
An open Network contract is now the ownership lease for its prefetch. The
1581+
janitor keeps the newest stale successor while that exact key is open; if a
1582+
fresh result exists it keeps no stale entry. Close/sequence teardown removes
1583+
the lease and flushes the queue. `ResetLocalStats` retains the open maps
1584+
because they are operational ownership state. Thus a live rollover is
1585+
immediate without turning delayed create results into unbounded memory or
1586+
unused-contract retention.
1587+
1588+
Physical signed-build evidence (`versionCode=1004819970`):
1589+
1590+
- Samsung→Pixel opened Wikipedia, idled 130 s, then loaded Mozilla, GitHub,
1591+
and Guardian. The first provider-return contract reached
1592+
837,513/838,860 usable bytes after about 290 s and installed its successor
1593+
about 1.1 ms later. No ≥1 s `contract wait` was logged.
1594+
- The four complete loads were 2.490, 12.185, 27.292, and 11.007 s.
1595+
Guardian had timed out beyond 90 s in the preceding build; GitHub was
1596+
44.06 s.
1597+
- Pixel→Samsung crossed 838,860 usable bytes in both request and return
1598+
directions. Successors appeared about 1.4/1.6 ms later, again without a
1599+
slow wait. Wikipedia and Mozilla loaded in 2.152/7.545 s.
1600+
1601+
GitHub retained a 12.46 s p95 request TTFB under 108-way request parallelism
1602+
after rollover on the large second contract. Guardian then loaded in 11.01 s.
1603+
That remaining tail is parallel-flow/transport scheduling, not contract
1604+
acquisition.
1605+
1606+
Top-level sizing, generated-identity, carrier, ownership, public-expiry, reset,
1607+
and bounded-retention tests passed 100 ordinary and 20 race repetitions.
1608+
Deterministic Connect excluding its two randomized PT stress cases passed in
1609+
362.173 s; those cases passed separately in 63.46/65.33 s. Subpackages, vet,
1610+
Android release unit tests, lint, SDK bind, assembly, and the physical install
1611+
pass.

connectctl/main.go

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,28 @@ const DefaultConnectUrl = "wss://connect.bringyour.com"
4343
var Out *log.Logger
4444
var Err *log.Logger
4545

46+
// sinkReceive owns the display values retained after a receive callback
47+
// returns; it must not retain callback-scoped Frames or message bytes.
48+
type sinkReceive struct {
49+
source connect.TransferPath
50+
frameSummary string
51+
provideMode protocol.ProvideMode
52+
}
53+
54+
// snapshotSinkReceive formats borrowed receive frames before the callback
55+
// returns. The decoder may immediately clear and reuse the Frame objects.
56+
func snapshotSinkReceive(
57+
source connect.TransferPath,
58+
frames []*protocol.Frame,
59+
peer connect.Peer,
60+
) *sinkReceive {
61+
return &sinkReceive{
62+
source: source,
63+
frameSummary: fmt.Sprint(frames),
64+
provideMode: peer.ProvideMode,
65+
}
66+
}
67+
4668
func init() {
4769
Out = log.New(os.Stdout, "", 0)
4870
Err = log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Lshortfile)
@@ -686,27 +708,17 @@ func sink(opts docopt.Opts) {
686708
// go platformTransport.Run(routeManager)
687709
}
688710

689-
type Receive struct {
690-
source connect.TransferPath
691-
frames []*protocol.Frame
692-
provideMode protocol.ProvideMode
693-
}
694-
695-
receives := make(chan *Receive)
711+
receives := make(chan *sinkReceive)
696712

697713
client.AddReceiveCallback(func(source connect.TransferPath, frames []*protocol.Frame, peer connect.Peer) {
698-
receives <- &Receive{
699-
source: source,
700-
frames: frames,
701-
provideMode: peer.ProvideMode,
702-
}
714+
receives <- snapshotSinkReceive(source, frames, peer)
703715
})
704716

705717
// FIXME reassemble the chunks. Only a complete message counts as 1 against the message count
706718
for i := 0; messageCount < 0 || i < messageCount; i += 1 {
707719
select {
708720
case receive := <-receives:
709-
fmt.Printf("[%s %s] %s\n", receive.source, receive.provideMode, receive.frames)
721+
fmt.Printf("[%s %s] %s\n", receive.source, receive.provideMode, receive.frameSummary)
710722
}
711723
}
712724
}

connectctl/main_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/urnetwork/connect"
8+
"github.com/urnetwork/connect/protocol"
9+
)
10+
11+
func TestSnapshotSinkReceiveDoesNotRetainBorrowedFrames(t *testing.T) {
12+
frame := &protocol.Frame{
13+
MessageType: protocol.MessageType_TestSimpleMessage,
14+
MessageBytes: []byte("original"),
15+
}
16+
frames := []*protocol.Frame{frame}
17+
wantSummary := fmt.Sprint(frames)
18+
19+
snapshot := snapshotSinkReceive(
20+
connect.SourceId(connect.NewId()),
21+
frames,
22+
connect.Peer{ProvideMode: protocol.ProvideMode_Network},
23+
)
24+
25+
frame.MessageType = protocol.MessageType_IpIpPacketFromProvider
26+
frame.MessageBytes = []byte("reused")
27+
frames[0] = nil
28+
29+
if snapshot.frameSummary != wantSummary {
30+
t.Fatalf("frame summary changed after borrowed frame reuse: got %q want %q", snapshot.frameSummary, wantSummary)
31+
}
32+
}

dns_flow_kernel_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import (
4949
// URNET_DNS_SEED_FIRST score used to stale-seed server 0 (0 = none)
5050
// URNET_DNS_PATH_WARM 1 = apply STAGGER_MS as the warm override over a
5151
// 750ms cold stagger (and enable hedge reservation)
52+
// URNET_DNS_HEDGE_RESERVE HTTP slots reserved for timed hedges (default 4)
5253
func TestDnsFlowKernel(t *testing.T) {
5354
if os.Getenv("URNET_DNSFLOW") == "" {
5455
t.Skip("dns flow kernel: set URNET_DNSFLOW=1")
@@ -82,6 +83,7 @@ func TestDnsFlowKernel(t *testing.T) {
8283
pipeMs := dnsFlowEnvInt("URNET_DNS_PIPE_MS", 0)
8384
seedFirst := dnsFlowEnvInt("URNET_DNS_SEED_FIRST", 0)
8485
pathWarm := dnsFlowEnvInt("URNET_DNS_PATH_WARM", 0)
86+
hedgeReserve := dnsFlowEnvInt("URNET_DNS_HEDGE_RESERVE", 4)
8587

8688
ctx, cancel := context.WithCancel(context.Background())
8789
defer cancel()
@@ -194,6 +196,7 @@ func TestDnsFlowKernel(t *testing.T) {
194196
settings.MaxConcurrentHttpRequests = dnsFlowEnvInt("URNET_DNS_HTTPCAP", 0)
195197
settings.MaxConcurrentResolutions = dnsFlowEnvInt("URNET_DNS_RESCAP", 0)
196198
settings.DohServerRaceMaxInFlight = dnsFlowEnvInt("URNET_DNS_RACEMAX", settings.DohServerRaceMaxInFlight)
199+
settings.DohServerHedgeReserve = hedgeReserve
197200
if 0 < seedFirst && 0 < len(dohUrls) {
198201
settings.ServerStatsSeed = map[string]float64{dohUrls[0]: float64(seedFirst)}
199202
}
@@ -294,11 +297,11 @@ func TestDnsFlowKernel(t *testing.T) {
294297
if effectiveResolutionCap <= 0 {
295298
effectiveResolutionCap = 4 * dnsTargetHttpConcurrency(settings.MemoryTarget.Capacity(), 16)
296299
}
297-
fmt.Printf("[dnsflow] servers=%s dead=%s stagger_ms=%d maxsrv=%d target_kb=%d timeout_ms=%d warm=%d path_warm=%d queries=%d http_cap=%d resolution_cap=%d race_max=%d pipe_slots=%d pipe_ms=%d seed_first=%d | ok=%d p50_ms=%d p95_ms=%d p99_ms=%d max_ms=%d wall_ms=%d peak_conc=%d requests=%d pipe_peak=%d\n",
300+
fmt.Printf("[dnsflow] servers=%s dead=%s stagger_ms=%d maxsrv=%d target_kb=%d timeout_ms=%d warm=%d path_warm=%d queries=%d http_cap=%d resolution_cap=%d race_max=%d hedge_reserve=%d pipe_slots=%d pipe_ms=%d seed_first=%d | ok=%d p50_ms=%d p95_ms=%d p99_ms=%d max_ms=%d wall_ms=%d peak_conc=%d requests=%d pipe_peak=%d\n",
298301
dnsFlowEnv("URNET_DNS_SERVERS", "5,50"),
299302
dnsFlowEnv("URNET_DNS_DEAD", ""),
300303
staggerMs, maxServers, int(targetByteCount/1024), timeoutMs, warm, pathWarm, queryCount,
301-
effectiveHttpCap, effectiveResolutionCap, settings.DohServerRaceMaxInFlight,
304+
effectiveHttpCap, effectiveResolutionCap, settings.DohServerRaceMaxInFlight, hedgeReserve,
302305
pipeSlots, pipeMs, seedFirst,
303306
okCount,
304307
pct(0.50).Milliseconds(),

extender/extender.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -884,7 +884,11 @@ func selfSign(hosts []string, organization string, validFrom time.Duration, vali
884884
}
885885

886886
notBefore := time.Now().Add(-validFrom)
887-
notAfter := notBefore.Add(validFor)
887+
// ValidFrom is the tolerated clock-skew/history window before creation;
888+
// ValidFor is the future lifetime after creation. Adding both durations
889+
// to notBefore made the default 180d/180d certificate expire at the
890+
// instant it was generated.
891+
notAfter := time.Now().Add(validFor)
888892

889893
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
890894
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)

0 commit comments

Comments
 (0)