Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
64 changes: 35 additions & 29 deletions abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,41 @@ package net
// These public compatibility constants intentionally remain literal values so
// the protocol-neutral root package does not import protocol ABI packages.
const (
AddressV1Size uint32 = 32
HandleV1Size uint32 = 8
UDPReceiveResultV1Size uint32 = 48
TCPStreamV1Size uint32 = 72
TCPIOResultV1Size uint32 = 8
TLSStreamV1Size uint32 = 72
TLSIOResultV1Size uint32 = 8
TLSConnectionInfoV1Size uint32 = 144
TLSMaxALPNV1Bytes uint32 = 32
DNSNameV1Size uint32 = 260
DNSQueryV1Size uint32 = 268
DNSRecordV1Size uint32 = 560
ICMPv4EchoRequestV1Size uint32 = 48
ICMPv4EchoResultV1Size uint32 = 48
ICMPv6EchoRequestV1Size uint32 = 48
ICMPv6EchoResultV1Size uint32 = 48
ICMPv6NeighborKeyV1Size uint32 = 32
ICMPv6NeighborV1Size uint32 = 40
ICMPv6OperationsV1Size uint32 = 4
DHCPv6OperationsV1Size uint32 = 4
DHCPv6ConfigurationV1Size uint32 = 3368
NTPSampleV1Size uint32 = 72
MDNSNameV1Size uint32 = 260
MDNSQueryV1Size uint32 = 268
MDNSRecordV1Size uint32 = 832
MDNSAnnouncementV1Size uint32 = 8
PollBudgetV1Size uint32 = 24
PollEventV1Size uint32 = 16
PollResultV1Size uint32 = 24
AddressV1Size uint32 = 32
HandleV1Size uint32 = 8
UDPReceiveResultV1Size uint32 = 48
TCPStreamV1Size uint32 = 72
TCPIOResultV1Size uint32 = 8
TLSStreamV1Size uint32 = 72
TLSIOResultV1Size uint32 = 8
TLSConnectionInfoV1Size uint32 = 144
TLSConnectionInfoV2Size uint32 = 144
TLSChannelBindingV1Size uint32 = 32
TLSMaxALPNV1Bytes uint32 = 32

TLSConnectionInfoV2FlagResumed uint32 = 1 << 0
TLSConnectionInfoV2FlagServerRole uint32 = 1 << 1
TLSConnectionInfoV2FlagPeerAuthenticated uint32 = 1 << 2
DNSNameV1Size uint32 = 260
DNSQueryV1Size uint32 = 268
DNSRecordV1Size uint32 = 560
ICMPv4EchoRequestV1Size uint32 = 48
ICMPv4EchoResultV1Size uint32 = 48
ICMPv6EchoRequestV1Size uint32 = 48
ICMPv6EchoResultV1Size uint32 = 48
ICMPv6NeighborKeyV1Size uint32 = 32
ICMPv6NeighborV1Size uint32 = 40
ICMPv6OperationsV1Size uint32 = 4
DHCPv6OperationsV1Size uint32 = 4
DHCPv6ConfigurationV1Size uint32 = 3368
NTPSampleV1Size uint32 = 72
MDNSNameV1Size uint32 = 260
MDNSQueryV1Size uint32 = 268
MDNSRecordV1Size uint32 = 832
MDNSAnnouncementV1Size uint32 = 8
PollBudgetV1Size uint32 = 24
PollEventV1Size uint32 = 16
PollResultV1Size uint32 = 24

UDPReceiveFlagTruncated uint32 = 1

Expand Down
126 changes: 126 additions & 0 deletions agent-todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -1615,3 +1615,129 @@ No repository-owned workstream or completion criterion from this hardening reque
topology, so the lifecycle/preview-1 integration must be re-reviewed and
re-ported. No provenance or review-bundle hashes were produced. Production
readiness also still requires executed arm64 TLS evidence.

## TLS client/server stream completion — July 20, 2026

- Preserved `connection_info_v1` byte-for-byte: offset 68 is again only the
little-endian resumed boolean 0 or 1. Added the separate additive
`connection_info_v2` import for resumed, server-role, and peer-authenticated
flags; unknown flags remain reserved and invalid.
- Changed server-profile composition so storing host credentials grants no
endpoint authority. `tls.AllowListeners()` is now the explicit ordinary
inbound TLS grant, remains separate from raw TCP, and continues to honor
applicable raw-TCP inbound deny rules.
- Server profile construction now eagerly parses every certificate, verifies
chain linkage and leaf/signer public-key equality, clones certificate
metadata, rejects dynamic SNI/configuration callbacks, and documents that the
host retains lifetime/concurrency ownership of each `crypto.Signer`.
- Added deterministic live two-namespace lneto TLS tests for ordinary server
authentication and mTLS, ALPN, role metadata, multi-queue backpressure,
bidirectional plaintext, clean two-way `close_notify`, abrupt raw-transport
truncation, listener reuse, exact quota/port release, close/accept races, and
concurrent namespace/resource teardown.
- The hosted ordinary-Go failure in Actions run `29757140541`, job
`88402308965`, was a real final-flight test deadlock: the client reported
verified completion before its final TLS 1.3 flight had been pumped to the
standard-library server. The test and client benchmark now explicitly service
that bounded flight before waiting for peer completion.
- Current local evidence: `go test ./...`, shuffled tests, full race/shuffle,
vet, source boundaries, checkptr, accepted-diagnostic linux/386, all 123
TinyGo-supported packages, all 12 custom CLI bundles, and all 17 TLS signoff
profiles passed. TLS signoff now resolves 164 named tests after bounded
resumption, channel-binding, client-certificate, frozen-clock,
software-signer, static-SNI, and certificate-rotation coverage. Fuzz smoke
passes 47
targets in 33 packages, including seven TLS-owned targets. Benchmark smoke
passes 173 top-level targets; the five-by-200 ms capture expands to 196 result
names and includes separate client/server TLS 1.3 handshakes. Four arm64 test
binaries cross-compile, while execution remains `skipped-no-runner`.
- Standard-Go TLS client/server streams and listeners are now implemented and
exercised. HTTP, HTTPS, portable TinyGo TLS, strict release adoption, and
executed arm64 evidence remain explicitly incomplete. PR #3 must remain a
draft and TLS must remain outside aggregate `register`.

## PR #3 TinyGo CI watchdog — July 25, 2026

- Actions run `29883882777`, job `88810323462`, reached GitHub's six-hour job
limit while the first supported package (`github.com/wago-org/net`) was still
running under TinyGo. The previous hosted TinyGo matrix had completed in about
45 minutes, and the same root package completed locally under TinyGo 0.41.1,
so the observed run is treated as a wedged package attempt rather than evidence
that the supported-package boundary changed.
- `scripts/tinygo-supported-test.sh` now runs every package verbosely behind a
ten-minute watchdog, prints a timed-out attempt, retries a timeout once, and
preserves only the final attempt in the canonical per-package log inventory.
Non-timeout failures are not retried. The timeout and retry bounds are
configurable for focused validation but remain finite and fail closed.
- Added a regression with a fake TinyGo process that wedges the root package on
its first attempt, proving the watchdog retries exactly once and still covers
all 124 supported packages.
- The remaining feature backlog at that point was HTTP/HTTPS APIs, portable
TinyGo TLS, executed arm64 evidence, strict release adoption, and the
separately documented protocol-expansion exclusions.

## Bounded standard-Go TLS resumption — July 25, 2026

- TinyGo TLS remains explicitly unsupported; no compatibility shim, plaintext
wrapper, or alternative cryptographic engine was added.
- Added opt-in `EnableClientSessionResumption(maxEntries, maxBytes)`. Every
backend instance constructs its own cache, so tickets never cross Wago
instance ownership. The cache stores serialized standard-library state under
exact entry/byte bounds, uses bounded LRU eviction, forces early-data state
off, clears retained ticket/state bytes on eviction and teardown, and rejects
caller-supplied arbitrary cache implementations.
- Added opt-in `EnableServerSessionTickets(keys...)` with one to four explicit,
unique, nonzero 32-byte keys. The first key issues tickets and the bounded
ordered set accepts retained rotation keys; ambient generation and mutable
guest key authority remain absent.
- Resumption cache capacity is conservatively reserved from the exact
per-instance queued-byte quota before cache allocation and released exactly
once on teardown. Aggregate configured cache capacity remains under the 64 MiB
TLS retention ceiling.
- Added standard-Go TLS 1.3 tests proving a full first handshake, resumed second
handshake through `[new, old]` key rotation, resumed third handshake with only
the new key, per-instance cache isolation, LRU/byte rejection, and exact quota
rollback/release. Existing `connection_info` and `connection_info_v2` already
expose the resumed bit without an ABI change.
- Graceful stream shutdown was already complete: `shutdown_write` drains accepted
plaintext and emits `close_notify`, while peer `close_notify` becomes stable
EOF. Resource `close` deliberately remains the deterministic abort path and
never waits for peer packets.
- Added the fixed `channel_binding` import for the 32-byte RFC 9266
`tls-exporter` binding. The label, context, and length are not guest-selectable;
it becomes available only after authenticated completion and preserves the
complete output on `AGAIN` or failure. Standard-library peers prove both sides
derive identical bytes.
- STARTTLS/existing-handle transfer, DTLS, QUIC TLS, 0-RTT, arbitrary dynamic
callbacks, and live mutation of immutable profiles remain separate authority
or transport designs rather than incomplete behavior in the bounded TLS
stream module.
- Current validation passes `go test ./...`, focused TLS race tests, `go vet
./...`, source-boundary checks, shell syntax, diff checks, and all 17 TLS
signoff package runs resolving 164 named tests.

## Bounded TLS host-call hardening — July 26, 2026

- Client and server profile construction now rejects caller-supplied
`tls.Config.Time` callbacks. Hosts may use `tls.ValidationTime` to install one
immutable UTC-normalized validation instant through package-owned code, or
omit it to use Go's system clock.
- Client certificate chains now receive the same eager DER parsing, chain-link
signature checks, and leaf/private-key correspondence validation as server
certificates before a profile can be registered.
- Client and server private keys are restricted to standard in-memory RSA,
NIST ECDSA, and Ed25519 implementations. Arbitrary `crypto.Signer` wrappers,
including HSM/delegated signers without a cancellable `Sign` contract, fail
closed before guest traffic can start a TLS worker.
- This closes the in-process teardown gap where an arbitrary signer or clock
callback could block inside `crypto/tls` while stream close waited for its
bounded worker set to exit. External signer support remains intentionally
unsupported until it can use a killable, finite host operation boundary.
- Static SNI selection is proven against multiple immutable certificates.
Certificate rotation is proven through a drain-close-relisten sequence: old
streams complete first, then a listener using the new profile presents a
different peer SPKI on the same endpoint. Closing the pinned lneto listener
before accepted streams drain remains an explicit abort boundary rather than
a zero-downtime handoff claim.
- Standard Go passes across the complete repository, and all 17 TLS signoff
package runs now resolve and pass 164 named test targets.
13 changes: 8 additions & 5 deletions benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@ in `docs/architecture.md`:
client/server round trip;
- DNS query construction, wire-name decode, response parse/selection, query
creation, record iteration, and readiness;
- TLS 1.3 handshake, fixed-ring steady-state transfer, and profile-name
authorization with allocation reporting;
- TLS 1.3 client and server handshakes, fixed-ring steady-state transfer, and
profile-name authorization with allocation reporting;
- instance manager attach/lookup/locking/poll and protocol operation wrappers;
- guest status mapping and complete UDP/TCP guest poll host calls.

The checked-in capture is in `baseline.txt`; `baseline-summary.md` records the
TLS-aware medians and environment. Discovery now finds 172 top-level benchmark
targets in 50 packages, with 195 distinct result names after subbenchmark
expansion. The baseline and independent repeatability candidate both use the
TLS-aware medians and environment. The expanded client/server branch discovers
173 top-level benchmark targets in 50 packages, with 196 distinct result names
after subbenchmark expansion. A full-grid five-by-200 ms local capture includes
both handshake roles; the checked-in release-readiness baseline remains the
prior outbound-only evidence until this draft branch is adopted. The baseline
and independent repeatability candidate both use the
repository's full five-by-200 ms, single-CPU, `-benchmem` profile on the same Go
1.24.4 Linux/amd64 Ryzen 7 8845HS environment. `candidate-summary.md` and
`benchstat.txt` document the comparison without treating timing noise as a
Expand Down
45 changes: 45 additions & 0 deletions docs/abi-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -683,14 +683,19 @@ provide `net.tcp`. All functions return one `i32` status:

```text
namespace_default(out_namespace_ptr: i32) -> i32
listen(namespace: i64, local_ptr: i32, profile_id: i32, out_listener_ptr: i32) -> i32
accept(listener: i64, out_stream_ptr: i32) -> i32
connect(namespace: i64, remote_ptr: i32, profile_id: i32,
server_name_ptr: i32, server_name_len: i32, out_stream_ptr: i32) -> i32
finish_connect(stream: i64) -> i32
read(stream: i64, dst_ptr: i32, dst_len: i32, out_result_ptr: i32) -> i32
write(stream: i64, src_ptr: i32, src_len: i32, out_result_ptr: i32) -> i32
shutdown_write(stream: i64) -> i32
connection_info(stream: i64, out_info_ptr: i32) -> i32
connection_info_v2(stream: i64, out_info_ptr: i32) -> i32
channel_binding(stream: i64, out_binding_ptr: i32) -> i32
close(stream: i64) -> i32
close_listener(listener: i64) -> i32
poll(events_ptr: i32, events_capacity: i32, budget_ptr: i32, result_ptr: i32) -> i32
```

Expand All @@ -712,6 +717,46 @@ struct wago_net_tls_connection_info_v1 {
};
```

At offset 68, v1 `resumed` remains a little-endian boolean whose only emitted
values are exactly 0 and 1. Server role and peer-authentication state are not
encoded into that field. This byte-for-byte contract predates server support and
is retained unchanged.

The additive `connection_info_v2` import writes a distinct 144-byte
`wago_net_tls_connection_info_v2` layout. Its physical offsets match v1 except
that offset 68 is a flags word:

```c
struct wago_net_tls_connection_info_v2 {
struct wago_net_addr_v1 local; // offset 0
struct wago_net_addr_v1 remote; // offset 32
uint16_t tls_version; // offset 64
uint16_t cipher_suite; // offset 66
uint32_t flags; // offset 68
uint32_t identity_type; // offset 72
uint32_t alpn_length; // offset 76, maximum 32
uint8_t alpn[32]; // offset 80
uint8_t peer_leaf_spki_sha256[32]; // offset 112
};
```

Defined v2 flags are bit 0 `RESUMED`, bit 1 `SERVER_ROLE`, and bit 2
`PEER_AUTHENTICATED`; all other bits are reserved and must be zero. For a server
stream without required client authentication, `PEER_AUTHENTICATED` is clear,
`identity_type` is 0, and the peer SPKI digest is all zero. For a mutually
authenticated server stream, the flag and digest are present while
`identity_type` remains 0 because no DNS/IP client-name assertion is made.

The shared `wago_net.abi_version` remains 1.0: the existing v1 import and bytes
are unchanged, while role-aware metadata is feature-detected through the
separately named additive `connection_info_v2` import.

The additive `channel_binding` import writes exactly 32 bytes from the RFC 9266
`tls-exporter` channel binding (`EXPORTER-Channel-Binding`, no context). The
label, context, and output length are fixed by the ABI rather than guest input.
It is available only after authenticated handshake completion; `AGAIN`, errors,
and invalid handles leave the complete 32-byte output unchanged.

Connection metadata is available only after verified completion. Certificate
DER, chains, private keys, and error strings are never guest output. Clean
`close_notify` returns `EOF`; raw transport EOF and corrupted records return
Expand Down
18 changes: 12 additions & 6 deletions docs/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ and build caches enabled and has seven bounded jobs:
- **quality** runs the ordinary suite, one shuffled suite, `go vet`, and the
backend/source-boundary guard;
- **tls-standard-go** runs `scripts/tls-signoff.sh`, retaining the exact public
composition, security, ABI, dependency, mixed-transport, EOF, quota, and worker
lifecycle ordinary/race evidence;
composition, client/server security, v1/v2 ABI, live mixed-transport,
close-notify/truncation, quota, listener-race, and worker-lifecycle
ordinary/race evidence;
- **tinygo-supported** installs pinned TinyGo 0.41.1 and runs
`scripts/tinygo-supported-test.sh` across the exact 124-package supported
surface while retaining the reviewed five-package TLS exclusion;
surface while retaining the reviewed five-package TLS exclusion; each package
has a ten-minute watchdog and one timeout-only retry so a wedged TinyGo test
cannot consume the six-hour hosted-job limit;
- **race** runs the complete suite with the race detector and shuffle, with five
repetitions only for scheduled or manually requested deep checks;
- **fuzz-smoke** runs all targets discovered by `scripts/fuzz-smoke.sh` on weekly
Expand All @@ -24,7 +27,9 @@ and build caches enabled and has seven bounded jobs:

TLS is intentionally absent from TinyGo rather than represented by a stub. The
TinyGo job uploads its supported and excluded manifests, canonical detail, and
per-package logs on failure and for scheduled/manual runs. The standard-Go TLS
per-package verbose logs on failure and for scheduled/manual runs. A timed-out
attempt is printed before the bounded retry, leaving the final attempt in the
canonical package log. The standard-Go TLS
job similarly retains its package/test manifests and logs. Static repository
tests require both script invocations and the pinned TinyGo version to remain in
the workflow.
Expand Down Expand Up @@ -65,12 +70,13 @@ scripts/ci-checkptr.sh

The script first compiles and initializes every package and test binary with
`-gcflags=all=-d=checkptr=2`. It then runs every test under the same
instrumentation except these two allocation-only assertions:
instrumentation except these three allocation-only assertions:

- `TestInstallNamespaceServicesAvoidsPerProtocolScratchForCommonSelections` in
the root package;
- `TestNamespaceCompositionAvoidsPerServiceHeapGrowthForPlannedSuite` in
`internal/namespace/core`.
`internal/namespace/core`;
- `TestHostFacadeExactAttachedLookupDoesNotAllocate` in `internal/plugin`.

Checkptr instrumentation intentionally adds allocations, so those tests cannot
truthfully enforce their ordinary-build exact `testing.AllocsPerRun` budgets in
Expand Down
Loading
Loading