Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
a0505e8
silkd: end a watch with an error when the kernel event queue overflows
CMGS Nov 6, 2026
b601d2c
silkd: answer bad_request for a session cwd the guest cannot enter, a…
CMGS Nov 6, 2026
b998aa8
silkd: run each exec in its own process group and signal the group
CMGS Nov 6, 2026
762bc91
sdk/go: send nothing for a call whose ctx is already done
CMGS Nov 6, 2026
239a78d
pool, config: one list of config-owned pool fields, so PUT /v1/pools …
CMGS Nov 6, 2026
79bcb37
pool: record a claim's usage event right after its commit
CMGS Nov 6, 2026
9e36721
pool: bill each transition at the moment it is published
CMGS Nov 6, 2026
76aeb14
pool: bill a cold-booted guest from its start, and end a failed claim…
CMGS Nov 6, 2026
0487fcb
pool: stamp adopt and move_in where they are published
CMGS Nov 6, 2026
a23f8ff
pool: keep the bill and address of a dropped claim another node runs …
CMGS Nov 6, 2026
daf147a
pool: leave a replicated claim's exited VMM alone while the node is f…
CMGS Nov 6, 2026
e88a0c6
pool: hold an adopted claim's Transition until its commit settles
CMGS Nov 6, 2026
27f70ff
pool: check a claim's injects against the policy layer it will run under
CMGS Nov 6, 2026
e5932fe
outbound: re-admit only internal addresses through egress_internal_allow
CMGS Nov 6, 2026
fa54d29
ci: run the tun address store's PostgreSQL tests
CMGS Nov 6, 2026
c634413
pool: skip a wake whose caller left while it queued on the claim
CMGS Nov 6, 2026
d0de3de
pool: relock egress taps at startup when the lock listing fails
CMGS Nov 6, 2026
79f369e
pool: keep a move's reservation as long as a capture may take
CMGS Nov 6, 2026
357de78
tunnet: look a frame's exit up before charging or opening its flow
CMGS Nov 6, 2026
9b01bb1
tunnet: stamp a packet's activity with the time its flow check alread…
CMGS Nov 6, 2026
3592600
test: hand the GRO tests their segments as one receive batch
CMGS Nov 6, 2026
1ec82da
pool: free a dropped claim's overlay address right after its drop com…
CMGS Nov 6, 2026
62e596c
pool: retry an overlay address release the address store refused
CMGS Nov 6, 2026
fb4edb7
scripts: say that each e2e script's cleanup removes every sbx-* VM on…
CMGS Nov 6, 2026
412dc41
review: silkd lsp keeps one stdio pair, pty states its real drain WHY…
CMGS Nov 6, 2026
98beeb5
review: SDK godocs to one line, frame.go layout, Python forwarder and…
CMGS Nov 6, 2026
4282424
sdk/python: drop the trailing ': ' from ExitError when stderr is empt…
CMGS Nov 6, 2026
080eafd
review: pool comments, a tunPlan for commitTun, parseCheckpoint reuse…
CMGS Nov 6, 2026
8622a82
test: pool setup through mustManager and restartReconciled, wg.Go wai…
CMGS Nov 6, 2026
18a6304
pool: leave a received move to its resolve at restart, and sweep a dr…
CMGS Nov 6, 2026
6f2aed7
store/dir: fsync a template's digest file before its meta.json commits
CMGS Nov 6, 2026
826acc6
outbound: read a tun door's guest socket under the owner's lock on re…
CMGS Nov 6, 2026
0c20db2
cut: drop Host.ResolvedName and the DNS resolver's name map
CMGS Nov 6, 2026
44b4814
review: sandboxd comments, layout and one constant
CMGS Nov 6, 2026
2a38d5f
test: sandboxd table and helper reuse, synctest for the address log, …
CMGS Nov 6, 2026
1f876d1
docs: match the code after the audit fixes, cut release-scoped upgrad…
CMGS Nov 6, 2026
ffbdf36
docs: a tun disable during a capture sends the guest no tun_down
CMGS Nov 6, 2026
4985c85
review: the golangci-lint findings of this round's changes (err shado…
CMGS Nov 6, 2026
cfffc4f
pool: end a rolled-back batch's claim events before its teardown, and…
CMGS Nov 6, 2026
443a44c
pool: a losing concurrent tun enable keeps the address the claim hold…
CMGS Nov 6, 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
3 changes: 1 addition & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@ jobs:
- name: Build silkd (musl static, arm64)
# Emulated cargo build via the carrier Dockerfile — byte-consistent
# with the image artifact; slow but release-rare.
# Intermediates live outside the worktree: goreleaser refuses a dirty
# git state, and only dist-extra/ is gitignored.
# Intermediates live outside the worktree: goreleaser refuses a dirty git state.
run: |
docker buildx build --platform linux/arm64 -o type=local,dest=/tmp/silkd-arm64 silkd
mkdir -p /tmp/silkd-dist && cp /tmp/silkd-arm64/silkd-static /tmp/silkd-dist/silkd
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sandboxd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ jobs:
env:
SANDBOXD_TEST_PG_URL: postgres://postgres:sbxci@localhost:5432/postgres?sslmode=disable
run: |
GOWORK=off go test -race -count=1 -v ./tenants/... ./poolset/... ./archives/... ./usage/... ./moves/... | tee "$RUNNER_TEMP/pg-test.log"
GOWORK=off go test -race -count=1 -v ./tenants/... ./poolset/... ./archives/... ./usage/... ./moves/... ./tunnet/pg/... | tee "$RUNNER_TEMP/pg-test.log"
GOWORK=off go test -race -count=1 -v -run 'TestTwoNodesOnOneMetaStore|TestNodesOfACell|TestAMetaStoreNodeWithoutACell' ./pool/ | tee -a "$RUNNER_TEMP/pg-test.log"
! grep -- '--- SKIP' "$RUNNER_TEMP/pg-test.log"

Expand Down
23 changes: 13 additions & 10 deletions docs/advanced-network.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ This page is the contract between silkd and the host. The host side design is
Both modes run at once. silkd keeps the proxy variables in every exec, so a
client that honours them still reaches `127.0.0.1:3128` over loopback; all
other traffic takes the default route through `tun0`. Loopback never enters
`tun0`, so the guest DNS stub of #472 is unaffected; the tun path does not
intercept DNS.
`tun0`, so the [guest DNS relay](egress.md#name-resolution-dns) on
`127.0.0.153` is unaffected; the tun path does not intercept DNS.

### Turning it on and off

Expand Down Expand Up @@ -81,7 +81,8 @@ Offload bits are the kernel's `TUN_F_*` values: `0x01` CSUM, `0x02` TSO4,
`0x04` TSO6, `0x08` TSO_ECN, `0x20` USO4, `0x40` USO6. Each side probes its
kernel (USO needs Linux 6.2) and then enables, with `TUNSETOFFLOAD`, only the
offloads both hellos carry; segmentation offloads are dropped when CSUM is not
common. A side never builds a frame whose GSO type the peer did not offer;
common, and USO is enabled only as the USO4 and USO6 pair. The host offers
USO4 alone, so a guest never enables USO. A side never builds a frame whose GSO type the peer did not offer;
packets its kernel queued before a reconnect keep their old type, and a
receiver may drop one it cannot take.

Expand Down Expand Up @@ -117,7 +118,6 @@ lost; TCP retransmits.
sandboxd needs no TUN, no kernel routes and no NAT rules on the node. It
carries each claim's packets over VXLAN, on an ordinary UDP socket, to an
**exit**: plain Linux configuration on the same host or on a gateway machine.
The design is [#471](https://github.com/cocoonstack/sandbox/issues/471).

```
guest -> tun0 -> silkd --vsock 2051--> sandboxd --VXLAN--> exit (vxlan0, forward, NAT) -> uplink
Expand All @@ -129,7 +129,7 @@ guest -> tun0 -> silkd --vsock 2051--> sandboxd --VXLAN--> exit (vxlan0, forward
disk write on the claim path; each release is appended to
`<data_dir>/tun-freed.log` so its reuse window outlives a restart. A node
with a `mesh` must have `meta_store.cell` and allocates it in the cell's
table, one round trip per claim, and stays off (proxy mode only) while the
[table](deploy.md#shared-tun-address-table), one round trip per claim, and stays off (proxy mode only) while the
meta store is unreachable. A freed address is reused after
`tun.reuse_after_seconds`, so the subnet's size over that window bounds the
sustained claim rate: a `/8` with the 5-day default carries about 38 new tun
Expand Down Expand Up @@ -180,21 +180,24 @@ guest -> tun0 -> silkd --vsock 2051--> sandboxd --VXLAN--> exit (vxlan0, forward
usage event with its IP bytes both ways, the destination `ip:port` as its
reference, and `upstream` `tun/<label>`. A clean stop ends every live flow,
so its records cover the bytes up to the stop, and persists the quota
counter, which is also persisted when the claim is disarmed or moved; a
crash loses both since the last of these.
- **Cost**: a claim of a pool without `tun` pays one map lookup. A tun claim
counter. A disarm or a move copies the counter into the claim record, which
the next `claims.json` write persists (a move's on the target); a crash
loses both since the last of these.
- **Cost**: on a node with a `tun` block, a claim of a pool without `tun` pays
one map lookup and one short hold of the pool lock. A tun claim
adds, on the claim path, an in-memory address (or one meta-store round
trip), a socket bind and one gratuitous ARP; `tun_up` is sent in the
background. Per packet: one parse, one flow-table read, the rate checks, an
incremental checksum update and one copy into the send buffer; a 64 KiB GSO
incremental checksum update, one copy out of the stream buffer and one into
the send buffer; a 64 KiB GSO
frame costs about 9 µs to segment and checksum (`BenchmarkForwardGSO`), a
small packet about 100 ns, a delivered packet about 250 ns, all without
allocation.

### Configuration

The node's `tun` block turns the mode on; a pool opts in with `"tun": true`
(the `none` lane only).
(the `none` lane only). Like `egress`, the flag is config-owned: `PUT /v1/pools` rejects it.

```json
"tun": {
Expand Down
3 changes: 1 addition & 2 deletions docs/benchmarks.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,7 @@ p50) on a newer host kernel (7.0.0-30 vs -28). The same-day A/B of this
sandboxd (4604b69) against the pre-round main (d5ba35d), three interleaved
reps each on this host and kernel, read burst p50 104 / 75 / 51 ms vs
85 / 134 / 159 ms, warm 0.3 ms on both, cold 216–221 vs 213–219 ms, so the
delta is the host, not the round's code (cocoon-specs
`tests/2026-09-02-batch0-hardware-regression.md`, B0-05).
delta is the host, not the round's code.

| data plane | measured |
|---|---|
Expand Down
36 changes: 12 additions & 24 deletions docs/cluster.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,21 +135,7 @@ data-plane addresses it currently sees:
curl -s -H "Authorization: Bearer $TOKEN" http://node-a:7777/v1/info | jq .
```

```json
{
"pools": [
{"key": {"template": "base:24.04", "net": "none", "size": "small"},
"warm": 4, "refilling": 0, "target": 4, "golden": true}
],
"templates": [],
"claimed": 2,
"running": 2,
"hibernated": 0,
"archived": 0,
"advertise_addr": "10.0.0.5:7777",
"peers": ["10.0.0.6:7777"]
}
```
The [API page](sandboxd-api.md#get-v1info) describes the payload.

`peers`, served by `GET /v1/peers` (root or tenant token), is what the SDK
scatters across in `Lookup`. An empty/absent `peers` means a mesh of one.
Expand Down Expand Up @@ -397,10 +383,10 @@ never fit), the source pauses the guest for the final
round and the target receives the series and starts the guest. A move whose
deltas never fit answers 503 and the guest runs on; from the second delta on, one
that shrinks by under 10% and is still too large answers at once. `downtime_ms` in the answer
is the pause the source measured, from the final capture until the target
answers the commit, after it started the guest. Each round has one minute plus its size at 10 MB/s to arrive, so a
target that stops reading fails the move. The final capture, its transfer and the target's commit have 2 minutes
together; past that the source resolves the move through the record, and the
is what the source measured from the final capture's start, including its wait
for a free dump slot, until the target answers the commit, after it started the guest. Each round has one minute plus its size at 10 MB/s to arrive, so a
target that stops reading fails the move. A replicated claim's registry unrow, the final capture, its transfer and the
target's commit have 2 minutes together; past that the source resolves the move through the record, and the
guest resumes on the source unless the target committed.

**Counting.** The target admits the move like a new claim: drain,
Expand All @@ -416,7 +402,7 @@ before the receive, and a target that started draining refuses the commit. A new
replaces it and resets its staged files, so a move retried after a source crash
does not wait out the idle drop; one from another source answers 409. A
reservation with no round arriving and none finished or
commit asked for 2 minutes is dropped with its staged files.
commit asked for 15 minutes, the longest a capture may take, is dropped with its staged files.

**Same paths.** Both nodes need the same path strings in cocoon's `root_dir` and
`run_dir`. The requirement is on the string, not the real directory: a symlink
Expand All @@ -432,7 +418,9 @@ the call answers 502 and the guest resumes on the source.
holding the claim moves it.
- Without `sync_claims` a host crash can roll back a node's journal. A source that
rolled back to a journal whose record is gone drops the claim, since a missing
record reads as moved on.
record reads as moved on. A journal write that keeps failing for over an hour
has the same effect after a restart: the hourly sweep goes by the claims in
memory, so it can advance or delete a record the unwritten journal still names.
- A move call on a claim that moved further than the peer it named answers 200
with that peer as `owner_addr`; `Lookup` finds the claim.

Expand Down Expand Up @@ -663,9 +651,8 @@ at a node that does not hold the record runs a tier order:
answered, the node pulls the record from a probed peer, validates its
shape and id before trusting it, and publishes it locally so later
branches are local — paid once per node. The pull is bounded to one
overall time budget across however many peers it tries (`healBudget` in
`store/peer`), and a node accepts only so many heals at once
(`maxConcurrentHeals` in `pool`) — past that cap it answers `503` rather
30-minute budget across however many peers it tries, and a node accepts at
most 4 heals at once — past that cap it answers `503` rather
than queuing indefinitely.

### Delete is eventual, not a fleet-wide revocation
Expand Down Expand Up @@ -792,6 +779,7 @@ Some config must match on every node or the cluster fails in confusing ways:
| `mesh.cluster_key` | nodes cannot join / decrypt gossip at all |
| `egress_ca` cluster root | a guest checkpointed/redirected across nodes trusts the root; a divergent root fails interception |
| egress: `egress_classes`, each pool's `egress` and `egress_upstream_env`, `secrets` names, headers and `value_env`, `egress_internal_allow`, `egress_upstream` | a claim or tenant redirected to a divergent node reaches different hosts, and its [claim credentials](egress.md#claim-credentials) are refused or not injected there |
| `tun.subnet` | every node of a cell draws overlay addresses from one shared table, and a claim keeps its address across a move or an adoption, so a node with another subnet serves claims outside it (see [advanced network mode](advanced-network.md#configuration)). Not part of the gossiped digest below |
| `checkpoint_ttl_hours` | a healed replica a delete broadcast missed ages out at a different time on each node, and nodes report a digest mismatch until every node has reloaded the same value |
| the engine root path (cocoon's `root_dir`) | a checkpoint or template export pins the base image blobs' absolute path under the root that captured it; a node whose root sits at another path refuses to clone a record it healed or pulled (`untrusted storage path in snapshot metadata`) — the heal succeeds, the branch fails. Not part of the gossiped digest below |

Expand Down
Loading
Loading