Skip to content

Commit de47e82

Browse files
committed
Add bounded route dossiers
1 parent 4a0b4a9 commit de47e82

19 files changed

Lines changed: 3920 additions & 19 deletions

.github/workflows/ci.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,24 @@ jobs:
102102
--locked \
103103
--root "$CARGO_INSTALL_ROOT" \
104104
skbx-cli
105+
cargo install \
106+
--git "file://$GITHUB_WORKSPACE" \
107+
--rev "$GITHUB_SHA" \
108+
--locked \
109+
--root "$CARGO_INSTALL_ROOT" \
110+
skbx-route
105111
version="$(cargo metadata --no-deps --format-version 1 |
106112
jq -r '.packages[] | select(.name == "skbx-cli") | .version')"
107113
"$CARGO_INSTALL_ROOT/bin/skbx" describe --format json | jq -e --arg version "$version" '
108114
.name == "skbx"
109115
and .version == $version
110116
and .contract_version == "traceq/0.1.0"
111117
'
118+
"$CARGO_INSTALL_ROOT/bin/skbx-route" describe | jq -e --arg version "$version" '
119+
.name == "skbx-route"
120+
and .version == $version
121+
and .contract_version == "routeq/0.1.0"
122+
'
112123
113124
- name: Verify agent contract
114125
run: |
@@ -121,6 +132,19 @@ jobs:
121132
."$schema" == "https://json-schema.org/draft/2020-12/schema"
122133
and ."$defs".EventEnvelope
123134
'
135+
target/release/skbx-route describe | jq -e '
136+
.contract_version == "routeq/0.1.0"
137+
and .limits.max_packets == 256
138+
and (.invariants | index("a silent hop is unknown and is not labeled as a drop"))
139+
'
140+
route_file="$RUNNER_TEMP/demo.routeq.jsonl"
141+
target/release/skbx-route demo --output "$route_file"
142+
target/release/skbx-route replay "$route_file" --format json | jq -e '
143+
.hops == 4
144+
and .responsive_hops == 3
145+
and .destination_reached == true
146+
and .complete == true
147+
'
124148
125149
- name: Verify embedded eBPF metadata
126150
run: |

.github/workflows/publish.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,5 @@ jobs:
8585
publish_crate skbx-contract
8686
publish_crate skbx-core
8787
publish_crate skbx-sensor
88+
publish_crate skbx-route
8889
publish_crate skbx-cli

Cargo.lock

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ members = [
66
"crates/skbx-contract",
77
"crates/skbx-core",
88
"crates/skbx-mission",
9+
"crates/skbx-route",
910
"crates/skbx-sensor",
1011
]
1112
resolver = "2"

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,52 @@ loopback-only by default, and there is no authentication or live remote capture
145145
backend. See [the Arc architecture and rollout guide](docs/arc.md) for the
146146
evidence contract, correlation algorithm, trust boundary, and production gates.
147147

148+
## Route dossiers: beyond a line of hops
149+
150+
`skbx-route` is an experimental sibling CLI that performs bounded, rootless
151+
IPv4 UDP route observation and records every TTL as a replayable `routeq`
152+
evidence dossier. It keeps one five-tuple across the run to reduce per-flow
153+
load-balancing artifacts, and it never treats a silent router as proof of a
154+
drop. [Explore the interactive route-dossier field guide](https://copyleftdev.github.io/skbx/route.html)
155+
for the evidence levels, visibility boundaries, and packet-budget model.
156+
157+
```console
158+
# Install the sibling CLI from the repository.
159+
cargo install --git https://github.com/copyleftdev/skbx --locked skbx-route
160+
161+
# Inspect the exact packet and time budget without resolving or probing.
162+
skbx-route plan example.com --json
163+
164+
# Exercise replay and explanation without touching the network.
165+
skbx-route demo --output demo.routeq.jsonl
166+
skbx-route replay demo.routeq.jsonl --format json
167+
168+
# Run a bounded live observation on Linux without root.
169+
skbx-route trace example.com \
170+
--max-hops 20 \
171+
--probes 2 \
172+
--timeout-ms 500 \
173+
--max-duration-ms 30000 \
174+
--output route.routeq.jsonl
175+
176+
# Opt in to passive prefix, origin-ASN, and RPKI context from RIPEstat.
177+
skbx-route enrich route.routeq.jsonl \
178+
--max-lookups 64 \
179+
--max-duration-ms 30000 \
180+
--output route.enriched.routeq.jsonl
181+
```
182+
183+
The initial vertical slice performs standards-based UDP path observation only.
184+
It does not port-scan intermediate routers, enumerate their services, or claim
185+
to observe remote forwarding decisions. Passive RIPEstat enrichment is a
186+
separate opt-in command with independent HTTPS request and duration budgets.
187+
It skips private and special-purpose addresses and sends each remaining public
188+
responder address to the
189+
[RIPEstat Data API](https://stat.ripe.net/docs/data-api/ripestat-data-api);
190+
RIPEstat's service terms apply. The dossier contract distinguishes observed,
191+
enriched, correlated, candidate, and unknown facts so public routing metadata
192+
and future cooperative skbx-sensor evidence cannot blur that boundary.
193+
148194
## What it can observe
149195

150196
- BTF-discovered `struct sk_buff *` arguments in positions 1–5;

crates/skbx-route/Cargo.toml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[package]
2+
name = "skbx-route"
3+
version.workspace = true
4+
edition.workspace = true
5+
license.workspace = true
6+
repository.workspace = true
7+
homepage.workspace = true
8+
rust-version.workspace = true
9+
description = "Bounded route observation with per-hop evidence dossiers"
10+
keywords = ["linux", "networking", "observability", "traceroute", "security"]
11+
categories = ["command-line-utilities", "network-programming"]
12+
13+
[dependencies]
14+
anyhow.workspace = true
15+
blake3.workspace = true
16+
clap.workspace = true
17+
libc.workspace = true
18+
nix = { workspace = true, features = ["net", "poll", "socket", "uio"] }
19+
reqwest = { workspace = true, features = ["query"] }
20+
serde.workspace = true
21+
serde_json.workspace = true
22+
thiserror.workspace = true
23+
tokio.workspace = true
24+
25+
[dev-dependencies]
26+
tempfile = "3.27.0"
27+
28+
[lib]
29+
name = "skbx_route"
30+
path = "src/lib.rs"
31+
32+
[[bin]]
33+
name = "skbx-route"
34+
path = "src/main.rs"

0 commit comments

Comments
 (0)