@tikoci/centrs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +207 -0
- package/GLOSSARY.txt +420 -0
- package/README.md +196 -0
- package/SECURITY.md +30 -0
- package/commands/AGENTS.md +50 -0
- package/commands/btest/README.md +257 -0
- package/commands/btest/examples.md +190 -0
- package/commands/check/README.md +59 -0
- package/commands/devices/README.md +405 -0
- package/commands/devices/examples.md +488 -0
- package/commands/discover/README.md +170 -0
- package/commands/discover/examples.md +98 -0
- package/commands/execute/README.md +208 -0
- package/commands/execute/examples.md +353 -0
- package/commands/mcp/README.md +260 -0
- package/commands/mcp/examples.md +158 -0
- package/commands/retrieve/README.md +209 -0
- package/commands/retrieve/examples.md +306 -0
- package/commands/settings/README.md +69 -0
- package/commands/stream/README.md +70 -0
- package/commands/terminal/README.md +177 -0
- package/commands/terminal/examples.md +144 -0
- package/commands/transfer/README.md +315 -0
- package/commands/transfer/examples.md +368 -0
- package/docs/CONSTITUTION.md +417 -0
- package/docs/MATRIX.md +224 -0
- package/docs/errors/README.md +74 -0
- package/docs/errors/auth/failed.md +9 -0
- package/docs/errors/cdb/already-exists.md +9 -0
- package/docs/errors/cdb/created.md +9 -0
- package/docs/errors/cdb/decrypt-failed.md +9 -0
- package/docs/errors/cdb/empty-group.md +9 -0
- package/docs/errors/cdb/invalid-option.md +9 -0
- package/docs/errors/cdb/invalid-record.md +9 -0
- package/docs/errors/cdb/not-found-target.md +9 -0
- package/docs/errors/cdb/not-found.md +9 -0
- package/docs/errors/cdb/override-applied.md +9 -0
- package/docs/errors/cdb/parse-failed.md +9 -0
- package/docs/errors/cdb/password-not-needed.md +9 -0
- package/docs/errors/cdb/password-required.md +9 -0
- package/docs/errors/cdb/reserved-key.md +9 -0
- package/docs/errors/cdb/reserved-option.md +9 -0
- package/docs/errors/cdb/target-not-registered.md +9 -0
- package/docs/errors/cdb/unknown-field.md +9 -0
- package/docs/errors/cdb/unknown-option.md +9 -0
- package/docs/errors/cdb/write-not-permitted.md +9 -0
- package/docs/errors/discover/broadcast-unavailable.md +9 -0
- package/docs/errors/discover/failed.md +9 -0
- package/docs/errors/identity/ambiguous.md +9 -0
- package/docs/errors/identity/no-match.md +9 -0
- package/docs/errors/input/invalid-command.md +9 -0
- package/docs/errors/input/invalid-match.md +9 -0
- package/docs/errors/input/invalid-path.md +9 -0
- package/docs/errors/input/invalid-routeros-path.md +9 -0
- package/docs/errors/input/invalid-target.md +9 -0
- package/docs/errors/input/local-file-not-found.md +9 -0
- package/docs/errors/input/mac-address.md +9 -0
- package/docs/errors/input/max-results-exceeded.md +9 -0
- package/docs/errors/internal/devices-failed.md +9 -0
- package/docs/errors/internal/mcp-start.md +9 -0
- package/docs/errors/internal/mcp-tool.md +9 -0
- package/docs/errors/internal/native-api-length.md +9 -0
- package/docs/errors/internal/unhandled.md +9 -0
- package/docs/errors/internal/unreachable.md +9 -0
- package/docs/errors/mndp/encode-failed.md +9 -0
- package/docs/errors/mndp/listen-failed.md +9 -0
- package/docs/errors/mndp/malformed.md +9 -0
- package/docs/errors/routeros/api-fatal.md +9 -0
- package/docs/errors/routeros/api-protocol.md +9 -0
- package/docs/errors/routeros/api-trap.md +9 -0
- package/docs/errors/routeros/btest-protocol.md +12 -0
- package/docs/errors/routeros/btest-too-many-sessions.md +9 -0
- package/docs/errors/routeros/command-failed.md +14 -0
- package/docs/errors/routeros/ec-srp5-protocol.md +11 -0
- package/docs/errors/routeros/error.md +9 -0
- package/docs/errors/routeros/invalid-value.md +14 -0
- package/docs/errors/routeros/mac-telnet-error.md +9 -0
- package/docs/errors/routeros/mac-telnet-not-ready.md +9 -0
- package/docs/errors/routeros/mac-telnet-protocol.md +9 -0
- package/docs/errors/routeros/mac-telnet-unsupported-auth.md +9 -0
- package/docs/errors/routeros/protocol-not-implemented.md +9 -0
- package/docs/errors/routeros/request-failed.md +9 -0
- package/docs/errors/routeros/session-closed.md +13 -0
- package/docs/errors/routeros/unknown-attribute.md +13 -0
- package/docs/errors/routeros/unknown-path.md +15 -0
- package/docs/errors/routeros/unsupported-capability.md +9 -0
- package/docs/errors/settings/invalid-boolean.md +9 -0
- package/docs/errors/settings/invalid-format.md +9 -0
- package/docs/errors/settings/invalid-integer.md +9 -0
- package/docs/errors/settings/invalid-timeout.md +9 -0
- package/docs/errors/settings/invalid-via.md +9 -0
- package/docs/errors/settings/unsafe-protocol-blocked.md +9 -0
- package/docs/errors/target/mac-not-in-arp.md +14 -0
- package/docs/errors/target/mac-required.md +23 -0
- package/docs/errors/target/mac-unresolved.md +17 -0
- package/docs/errors/target/unresolved.md +9 -0
- package/docs/errors/transport/auth-failed.md +9 -0
- package/docs/errors/transport/auto-method.md +9 -0
- package/docs/errors/transport/capability-unsupported.md +22 -0
- package/docs/errors/transport/checksum-unavailable.md +9 -0
- package/docs/errors/transport/connection-closed.md +9 -0
- package/docs/errors/transport/connection-refused.md +9 -0
- package/docs/errors/transport/dns.md +9 -0
- package/docs/errors/transport/host-key-mismatch.md +9 -0
- package/docs/errors/transport/incomplete-transfer.md +9 -0
- package/docs/errors/transport/insecure-trust.md +9 -0
- package/docs/errors/transport/local-tool-missing.md +11 -0
- package/docs/errors/transport/network.md +9 -0
- package/docs/errors/transport/timeout.md +9 -0
- package/docs/errors/transport/tls-certificate.md +9 -0
- package/docs/errors/transport/unreachable.md +9 -0
- package/docs/errors/transport/unsupported-operation.md +9 -0
- package/docs/errors/usage/confirmation-required.md +9 -0
- package/docs/errors/usage/conflicting-flags.md +9 -0
- package/docs/errors/usage/invalid-concurrency.md +9 -0
- package/docs/errors/usage/missing-group.md +9 -0
- package/docs/errors/usage/not-implemented.md +9 -0
- package/docs/errors/usage/target-exists.md +9 -0
- package/docs/errors/usage/timeout-out-of-range.md +9 -0
- package/docs/errors/validation/not-implemented.md +9 -0
- package/docs/errors/validation/option.md +9 -0
- package/docs/errors/validation/syntax.md +9 -0
- package/docs/errors/validation/unknown-attribute.md +9 -0
- package/docs/errors/validation/unknown-path.md +9 -0
- package/package.json +96 -0
- package/src/AGENTS.md +7 -0
- package/src/btest.ts +906 -0
- package/src/cli/btest.ts +455 -0
- package/src/cli/common.ts +53 -0
- package/src/cli/devices.ts +527 -0
- package/src/cli/discover.ts +186 -0
- package/src/cli/execute.ts +333 -0
- package/src/cli/mcp.ts +109 -0
- package/src/cli/retrieve.ts +383 -0
- package/src/cli/terminal.ts +303 -0
- package/src/cli/transfer.ts +471 -0
- package/src/cli.ts +115 -0
- package/src/core/envelope.ts +221 -0
- package/src/core/error-catalog.ts +469 -0
- package/src/core/routeros-errors.ts +268 -0
- package/src/data/AGENTS.md +11 -0
- package/src/data/dude-db.ts +0 -0
- package/src/data/mndp-cache.ts +132 -0
- package/src/data/mndp.ts +405 -0
- package/src/data/winbox-cdb-write.ts +200 -0
- package/src/data/winbox-cdb.ts +913 -0
- package/src/devices-schema.ts +107 -0
- package/src/devices.ts +1717 -0
- package/src/discover.ts +802 -0
- package/src/errors.ts +189 -0
- package/src/execute.ts +1350 -0
- package/src/index.ts +353 -0
- package/src/mcp/config.ts +66 -0
- package/src/mcp/resources.ts +98 -0
- package/src/mcp/safety.ts +119 -0
- package/src/mcp/server.ts +225 -0
- package/src/mcp/tools.ts +657 -0
- package/src/mcp.ts +33 -0
- package/src/protocols/AGENTS.md +14 -0
- package/src/protocols/adapter.ts +866 -0
- package/src/protocols/btest-session.ts +1493 -0
- package/src/protocols/btest.ts +447 -0
- package/src/protocols/ec-srp5.ts +466 -0
- package/src/protocols/index.ts +118 -0
- package/src/protocols/mac-telnet-console.ts +655 -0
- package/src/protocols/mac-telnet.ts +1299 -0
- package/src/protocols/mtwei.ts +47 -0
- package/src/protocols/native-api.ts +796 -0
- package/src/protocols/sftp.ts +427 -0
- package/src/protocols/ssh.ts +297 -0
- package/src/resolver/cdb.ts +580 -0
- package/src/resolver/comment-kv.ts +360 -0
- package/src/resolver/index.ts +74 -0
- package/src/resolver/mac.ts +227 -0
- package/src/resolver/settings.ts +228 -0
- package/src/resolver/target.ts +442 -0
- package/src/retrieve-fanout.ts +533 -0
- package/src/retrieve.ts +1215 -0
- package/src/terminal.ts +444 -0
- package/src/transfer.ts +1554 -0
- package/src/tui.ts +22 -0
- package/src/webproxy.ts +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# centrs
|
|
2
|
+
|
|
3
|
+
`centrs` is the tikoci RouterOS interaction hub: a Bun/TypeScript library and
|
|
4
|
+
CLI (with MCP, TUI, and HTTP proxy frontends planned) for talking to MikroTik
|
|
5
|
+
RouterOS devices through a regularized, validated interface.
|
|
6
|
+
|
|
7
|
+
It is a **friendly conduit**, not a high-level configuration abstraction. It
|
|
8
|
+
helps humans and agents reach RouterOS over the right protocol, with the right
|
|
9
|
+
credentials and ports, and validates RouterOS-shaped commands before running
|
|
10
|
+
them. It does **not** hide RouterOS behind helpers like `createVlanOnBridge()`
|
|
11
|
+
— you still speak RouterOS. Validation and structured diagnostics are the
|
|
12
|
+
product; without them this would just be a worse `curl`.
|
|
13
|
+
|
|
14
|
+
> **Status:** early and in active development. The library, device registry, and
|
|
15
|
+
> first MCP server phases are CHR-verified; the CLI is wired (`coded`); encrypted-CDB
|
|
16
|
+
> writes are still blocked, and the TUI/proxy frontends are planned.
|
|
17
|
+
> `docs/MATRIX.md` is the single source of truth for what works today — treat
|
|
18
|
+
> anything not green there as not-yet-shipped.
|
|
19
|
+
|
|
20
|
+
## What it talks to, and how
|
|
21
|
+
|
|
22
|
+
- **Frontends:** TypeScript API, CLI, MCP server (Phase 1 + first Phase 2 CDB
|
|
23
|
+
mutation `CHR-passed` — see `commands/mcp/`), TUI and HTTP proxy/daemon
|
|
24
|
+
(future).
|
|
25
|
+
- **Protocols:** REST API (preferred), native API, and SNMP for reads;
|
|
26
|
+
SSH / MAC-Telnet / RoMON / WinBox Terminal for interactive and L2 execute;
|
|
27
|
+
MNDP for discovery. centrs picks the protocol for you (and lets you pin one
|
|
28
|
+
with `via=` or `--via`).
|
|
29
|
+
- **Commands:** every command resolves a *target* — a router you name on the
|
|
30
|
+
CLI — to a real address, credentials, and a protocol, then runs.
|
|
31
|
+
|
|
32
|
+
| Command | Purpose |
|
|
33
|
+
| ---------- | ------- |
|
|
34
|
+
| `retrieve` | Read RouterOS state over REST/native API and SNMP OID/MIB values. |
|
|
35
|
+
| `stream` | Follow live RouterOS output (`print follow`/monitor/sniffer) as an NDJSON stream of envelopes. |
|
|
36
|
+
| `execute` | Run RouterOS CLI-shaped read/write commands (add/set/remove) over native API/REST/L2 surfaces. |
|
|
37
|
+
| `terminal` | Open an interactive console, primarily SSH or MAC-Telnet. |
|
|
38
|
+
| `check` | Probe reachability and management protocol availability. |
|
|
39
|
+
| `devices` | View and maintain the CDB-backed device registry (the only writer). |
|
|
40
|
+
| `discover` | Discover MNDP neighbors and optionally save them into the CDB. |
|
|
41
|
+
| `settings` | Manage centrs's own settings (`centrs.env`, `__default__`) and run first-time setup. |
|
|
42
|
+
|
|
43
|
+
Each command's `commands/<name>/` directory carries its design and the
|
|
44
|
+
executable example list that gates "done".
|
|
45
|
+
|
|
46
|
+
## Your WinBox address book *is* the device registry
|
|
47
|
+
|
|
48
|
+
centrs deliberately has **no config store, credential vault, or inventory
|
|
49
|
+
database of its own**. Instead it piggybacks on the one most RouterOS admins
|
|
50
|
+
already keep: the WinBox connection database (CDB), the `winbox.cdb` file that
|
|
51
|
+
WinBox writes when you save a router in its address book.
|
|
52
|
+
|
|
53
|
+
centrs reads (and, through `devices`, writes) that same file at its well-known
|
|
54
|
+
location, `~/.config/tikoci/winbox.cdb` (override with `--cdb-file` /
|
|
55
|
+
`CENTRS_CDB_FILE`). From each saved entry it takes the fields WinBox already
|
|
56
|
+
stores:
|
|
57
|
+
|
|
58
|
+
- **`target`** — the address you saved (IPv4, IPv6, MAC, or DNS name); a literal
|
|
59
|
+
you can type at the CLI to reach the box. centrs *also* resolves a `<router>`
|
|
60
|
+
against three sanctioned comment lookup keys — `identity=`, `mac=`, and `ip=` —
|
|
61
|
+
so a router whose `target` is `192.0.2.5` is still reachable as `edge1` when its
|
|
62
|
+
comment carries `identity=edge1`. Every other comment token stays inert prose.
|
|
63
|
+
See `commands/devices/README.md` (Identity model) for the full resolution rules.
|
|
64
|
+
- **`user` / `password`** — the credentials centrs connects with. They stay in
|
|
65
|
+
the CDB and are redacted from MCP results/resources.
|
|
66
|
+
- **`group`, `profile`, `session`, port** — used as-is.
|
|
67
|
+
|
|
68
|
+
So `centrs retrieve edge1 /ip/address` works with no flags: `edge1` is looked
|
|
69
|
+
up in the CDB, its credentials and address come along for the ride. A target
|
|
70
|
+
that *isn't* in the CDB still works, but then `--username` / `--password` (or
|
|
71
|
+
`CENTRS_USERNAME` / `CENTRS_PASSWORD`) become mandatory.
|
|
72
|
+
|
|
73
|
+
### Overloading the comment: per-device settings as `key=value`
|
|
74
|
+
|
|
75
|
+
A WinBox CDB entry has no field for "always use SSH for this box" or "skip
|
|
76
|
+
validation here" — those are centrs concepts WinBox knows nothing about. Rather
|
|
77
|
+
than invent a parallel store, centrs **overlays its own settings onto the one
|
|
78
|
+
free-text field WinBox does have: the comment.** It parses `key=value` tokens
|
|
79
|
+
(the "comment kv-soup") out of that free text and treats the recognized ones as
|
|
80
|
+
per-device defaults. Everything else in the comment stays inert human prose.
|
|
81
|
+
|
|
82
|
+
A comment like:
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
edge1 prod border router via=ssh validate=false mcp=rw
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
leaves `edge1 prod border router` as plain text for you and WinBox, while
|
|
89
|
+
centrs reads three settings from it. The recognized keys:
|
|
90
|
+
|
|
91
|
+
| Key | Effect |
|
|
92
|
+
| ---------- | ----------------------------------------------------------------------- |
|
|
93
|
+
| `via` | Default transport for this device (`rest-api`, `native-api`, `ssh`, …). |
|
|
94
|
+
| `validate` | `true` / `false`. `false` is the escape hatch; a CLI flag still wins. |
|
|
95
|
+
| `timeout` | Default request timeout in ms (per-transport caps still apply). |
|
|
96
|
+
| `port` | Non-default transport port. |
|
|
97
|
+
| `ssh-key` | Path to a per-device SSH private key (a path only — never key material).|
|
|
98
|
+
| `source` | Provenance marker for discovered/imported records (`mndp`, `dude`, …). |
|
|
99
|
+
| `mcp` | MCP write policy: `ro` (default) or `rw`. See below. |
|
|
100
|
+
|
|
101
|
+
The first-class fields WinBox already stores (`user`, `password`, `group`,
|
|
102
|
+
`profile`, `session`) must **not** be written into the comment — they have real
|
|
103
|
+
CDB tags, and `devices set` refuses to put them there. Unknown keys produce a
|
|
104
|
+
`cdb/unknown-option` warning and are ignored, so a stray `=` in human prose
|
|
105
|
+
never breaks anything.
|
|
106
|
+
|
|
107
|
+
Settings resolve lowest-to-highest: **built-in defaults → project config →
|
|
108
|
+
comment-kv → `CENTRS_*` env → CLI flag / API arg.** Every result envelope
|
|
109
|
+
reports which source won each setting under `meta.settings`, so you can always
|
|
110
|
+
see *why* a given port or transport was chosen.
|
|
111
|
+
|
|
112
|
+
### Groups, discovery, and the MCP allowlist all ride on the CDB
|
|
113
|
+
|
|
114
|
+
- **Groups** are CDB-native: an entry's `group` field is just a string, and
|
|
115
|
+
`--group prod-edge` fans a single command out to every entry that shares it.
|
|
116
|
+
There is no separate group-definition file.
|
|
117
|
+
- **Discovery** writes back into the CDB: `discover --save` records MNDP
|
|
118
|
+
neighbors as new entries tagged `group=discovered` with a `source=mndp`
|
|
119
|
+
marker — hints to curate, not authoritative inventory.
|
|
120
|
+
- **The MCP server uses the CDB as its authorization boundary.** Agents can
|
|
121
|
+
only reach targets that exist in the CDB; RouterOS write-shaped execution
|
|
122
|
+
requires the resolved entry to carry `mcp=rw` plus per-call confirmation.
|
|
123
|
+
`centrs_devices` can also add/set/remove CDB records in-band, and
|
|
124
|
+
`centrs_discover` can save MNDP neighbors, with `confirm: true`. The allowlist
|
|
125
|
+
and the write policy are *the same CDB data* you already manage — see
|
|
126
|
+
`commands/mcp/`.
|
|
127
|
+
|
|
128
|
+
`devices` is the only command that writes the CDB, and it does so safely
|
|
129
|
+
(timestamped backup, atomic rename, unknown WinBox fields preserved verbatim).
|
|
130
|
+
Encrypted-CDB **reads** work with `--cdb-password` / `CENTRS_CDB_PASSWORD`;
|
|
131
|
+
encrypted **writes** are currently blocked pending a verified WinBox round-trip.
|
|
132
|
+
The full contract — identity resolution, ambiguity handling, write strategy,
|
|
133
|
+
and the comment grammar — lives in `commands/devices/README.md` and
|
|
134
|
+
`docs/CONSTITUTION.md` (Identity and CDB).
|
|
135
|
+
|
|
136
|
+
## The result envelope, validation, and errors
|
|
137
|
+
|
|
138
|
+
- **Validation is on by default** (`validate=true`). Reads are checked through
|
|
139
|
+
`/console/inspect`; CLI-shaped `execute` commands through `[:parse]`. Set
|
|
140
|
+
`validate=false` (per device or per call) only as a deliberate escape hatch.
|
|
141
|
+
- **Every call returns one envelope:** `{ ok, data?, warnings?, error?, meta }`,
|
|
142
|
+
with provenance and source-of-truth reporting in `meta`.
|
|
143
|
+
- **Errors are typed values**, not thrown strings, with slash-namespaced codes
|
|
144
|
+
(`routeros/…`, `transport/…`, `validation/…`, `cdb/…`) and a `details_url`
|
|
145
|
+
of the form `https://tikoci.github.io/centrs/errors/<code>` for the
|
|
146
|
+
human-readable explanation.
|
|
147
|
+
|
|
148
|
+
The load-bearing contract — envelope shape, error model, protocol selection,
|
|
149
|
+
settings precedence, and the "done" definition — lives in
|
|
150
|
+
`docs/CONSTITUTION.md`. The command×protocol status grid is `docs/MATRIX.md`.
|
|
151
|
+
|
|
152
|
+
## Repository layout
|
|
153
|
+
|
|
154
|
+
For contributors. An admin only needs the CLI and their CDB; this is where the
|
|
155
|
+
code and authority docs live.
|
|
156
|
+
|
|
157
|
+
- **`docs/CONSTITUTION.md`** — load-bearing rules (validation, envelope, error
|
|
158
|
+
model, settings precedence, identity/CDB, protocol selection, done).
|
|
159
|
+
- **`docs/MATRIX.md`** — command×protocol grid; the only status surface.
|
|
160
|
+
- **`commands/<name>/`** — per-command README and executable examples.
|
|
161
|
+
- **`src/`** — Bun/TypeScript source.
|
|
162
|
+
- **`test/`** — unit and CHR-backed integration tests.
|
|
163
|
+
|
|
164
|
+
There is no `docs/specs/`, no `work/`, no roadmap doc. The matrix is the
|
|
165
|
+
roadmap.
|
|
166
|
+
|
|
167
|
+
## Development
|
|
168
|
+
|
|
169
|
+
Requirements:
|
|
170
|
+
|
|
171
|
+
- Bun 1.3.11 or newer (CI pinned to 1.3.13; Bun 2.x not yet validated).
|
|
172
|
+
- Git.
|
|
173
|
+
- For integration tests: QEMU plus `@tikoci/quickchr` (handles CHR image
|
|
174
|
+
download and boot).
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
bun install
|
|
178
|
+
bun run lint
|
|
179
|
+
bun run test
|
|
180
|
+
bun run test:integration # CHR-backed; required before claiming "done" on transport code
|
|
181
|
+
bun run test:integration:long-term # channel-specific CHR gate
|
|
182
|
+
bun run build
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Generated API docs through TypeDoc:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
bun run build:doc:api
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Done definition (short version)
|
|
192
|
+
|
|
193
|
+
A feature is done when every line in `commands/<name>/examples.md` is green on
|
|
194
|
+
real CHR via `bun run test:integration`. Code existing is not done. Unit tests
|
|
195
|
+
passing is not done. Full rule:
|
|
196
|
+
`.github/instructions/done-definition.instructions.md`.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Reporting a Vulnerability
|
|
4
|
+
|
|
5
|
+
Report privately via [GitHub Security Advisories](https://github.com/tikoci/centrs/security/advisories/new). Do **not** open a public issue for an undisclosed vulnerability.
|
|
6
|
+
|
|
7
|
+
Please include the affected files or workflow, reproduction details, and impact. Initial response within a few business days; fixes land on `main` and flow into the next published artifact.
|
|
8
|
+
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
`centrs` will mediate RouterOS device access, credentials, local discovery data, and optional HTTP/MCP proxy surfaces. Security-sensitive areas include credential import/cache, RouterOS write execution, local network discovery, file transfer, and any daemon that binds a network port.
|
|
12
|
+
|
|
13
|
+
## Code scanning
|
|
14
|
+
|
|
15
|
+
The repository's [Security tab](https://github.com/tikoci/centrs/security) is the live source of current alerts and advisories. This section describes *what* checks run and *why*, so the doc stays meaningful even when the badge is at 0.
|
|
16
|
+
|
|
17
|
+
- **CodeQL** — repo-managed workflow at [`.github/workflows/codeql.yaml`](.github/workflows/codeql.yaml) with config [`.github/codeql-config.yml`](.github/codeql-config.yml). Query suite: `security-and-quality` (security-extended + code-quality). Languages: `javascript-typescript`, `actions`. Schedule: push to `main`, pull requests to `main`, weekly cron, and manual dispatch.
|
|
18
|
+
- **Code Quality (AI findings, preview)** — intended to be enabled in GitHub. [`.github/workflows/codeql.yaml`](.github/workflows/codeql.yaml) includes a non-failing forward-compat probe for a future AI-findings API. AI findings are noisy and self-contradicting; we accept the noise because the second-opinion catches real issues that the static suite misses. Steady-state goal is 0 open findings. False positives are dismissed via the GitHub UI with a written justification — that text is the audit-log contract.
|
|
19
|
+
- **Dependency review** — not enabled. GitHub dependency review follows dependency-graph ecosystem support, and this Bun-only repository currently does not have a supported lockfile for that gate.
|
|
20
|
+
- **Dependabot security updates** — enabled and configured in [`.github/dependabot.yaml`](.github/dependabot.yaml).
|
|
21
|
+
- **Secret scanning** — enabled in GitHub, with push protection.
|
|
22
|
+
- **Private vulnerability reporting** — enabled via GitHub Security Advisories.
|
|
23
|
+
|
|
24
|
+
Some GitHub Security tab data, especially Code Quality AI findings, is not currently available through stable unauthenticated APIs. Human review in the GitHub UI remains part of the security workflow.
|
|
25
|
+
|
|
26
|
+
## Supported versions
|
|
27
|
+
|
|
28
|
+
| Version | Supported |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| `main` | yes |
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# commands/
|
|
2
|
+
|
|
3
|
+
Each `commands/<name>/` holds the **executable spec** for one command:
|
|
4
|
+
|
|
5
|
+
- `README.md` — intent, flags, behavior. The "designed" tier.
|
|
6
|
+
- `examples.md` — numbered, runnable examples. Each example is one assertion in
|
|
7
|
+
the matching `test/integration/<name>.test.ts`; example N ↔ assertion N. This
|
|
8
|
+
is what `CHR-passed` is measured against.
|
|
9
|
+
|
|
10
|
+
Do not restate the constitution here or in a command README — link to
|
|
11
|
+
`docs/CONSTITUTION.md` for envelope, errors, settings precedence, target
|
|
12
|
+
selection, and protocol selection. The identity/CDB **resolution depth** (lookup
|
|
13
|
+
keys, ambiguity, `__default__`, comment-kv allowlist, write strategy) lives in
|
|
14
|
+
`commands/devices/README.md` — the registry command owns it; the constitution
|
|
15
|
+
keeps only the identity/CDB principle. A command file covers only what is
|
|
16
|
+
specific to that command. `docs/MATRIX.md` is the only status surface; a
|
|
17
|
+
README's "Status" line must agree with it.
|
|
18
|
+
|
|
19
|
+
## Verb vocabulary
|
|
20
|
+
|
|
21
|
+
Commands are centrs **verbs** (`retrieve`, `stream`, `execute`, `devices`,
|
|
22
|
+
`discover`, `check`, `terminal`, `settings`), never one tool per RouterOS command. Sub-verbs and their
|
|
23
|
+
aliases are canonical across CLI / API / MCP:
|
|
24
|
+
|
|
25
|
+
| Canonical | Aliases | Meaning |
|
|
26
|
+
| --------- | ---------------- | ---------------------------------------- |
|
|
27
|
+
| `list` | `print` | Read many records. (`print` is RouterOS muscle-memory.) |
|
|
28
|
+
| `show` | `get` | Read one resolved record. |
|
|
29
|
+
| `add` | — | Create; refuses an existing target. |
|
|
30
|
+
| `set` | — | Modify an existing record (same flags as `add`). |
|
|
31
|
+
| `remove` | `rm`, `delete` | Delete a record. |
|
|
32
|
+
| `edit` | — | **Reserved** for the future clack/TUI wizard. Not a field-editing verb. |
|
|
33
|
+
| `groups` | — | List distinct groups. |
|
|
34
|
+
|
|
35
|
+
Canonical names win in help text and docs; aliases resolve to the canonical verb
|
|
36
|
+
silently. `add` and `set` are symmetric (same flags + `k=v` tokens), differing
|
|
37
|
+
only on existence. There is no `update` verb — RouterOS add/set/remove ride
|
|
38
|
+
`execute` (constitution: protocol selection).
|
|
39
|
+
|
|
40
|
+
## Help system
|
|
41
|
+
|
|
42
|
+
Shared across CLI / API / MCP help rendering:
|
|
43
|
+
|
|
44
|
+
- **Level-aware help.** `<command> <subcommand> --help` is more specific than
|
|
45
|
+
`<command> --help` — e.g. `centrs devices remove --help` shows the `remove`
|
|
46
|
+
usage and flags, not the whole `devices` surface.
|
|
47
|
+
- **"Did you mean?"** On an unknown verb/sub-verb or flag, the error
|
|
48
|
+
(`input/invalid-command` / usage error) lists the closest canonical matches
|
|
49
|
+
(Levenshtein) plus accepted aliases — extending the invalid-command behavior
|
|
50
|
+
that already enumerates canonical verbs + aliases.
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# btest
|
|
2
|
+
|
|
3
|
+
Run MikroTik's **bandwidth test** as a cross-platform client or server. `btest`
|
|
4
|
+
is centrs's peer-measurement surface: it mirrors `/tool/bandwidth-test` (client)
|
|
5
|
+
and `/tool/bandwidth-server` (server), so it is a drop-in for the Windows-only
|
|
6
|
+
`btest.exe` on macOS/Linux.
|
|
7
|
+
|
|
8
|
+
Status: server **and** client both `CHR-passed`. The protocol **codec**
|
|
9
|
+
(`src/protocols/btest.ts`), the shared **EC-SRP5** core including the net-new
|
|
10
|
+
server role (`src/protocols/ec-srp5.ts`), the **session state machine + TCP/UDP
|
|
11
|
+
data engines** (`src/protocols/btest-session.ts`), the **orchestrator**
|
|
12
|
+
(`src/btest.ts`), and the **CLI** (`centrs btest client|server`) all exist and are
|
|
13
|
+
unit/loopback-tested. Two gated CHR integration tests **pass on CHR 7.23.1**:
|
|
14
|
+
`test/integration/btest.test.ts` lands a real RouterOS `/tool/bandwidth-test`
|
|
15
|
+
client's TCP-receive, UDP-transmit (with loss accounting), and **EC-SRP5** sessions
|
|
16
|
+
on the centrs **server**; `test/integration/btest-client.test.ts` runs the centrs
|
|
17
|
+
**client** against a real RouterOS `/tool/bandwidth-server` over a host→guest
|
|
18
|
+
`tcp:2000` forward — unauthenticated TCP receive, an **EC-SRP5 client proof
|
|
19
|
+
verified by RouterOS's own verifier**, and a wrong-password reject. TCP
|
|
20
|
+
multi-connection (`connection-count > 1`) data fan-out is a follow-up. See
|
|
21
|
+
`docs/MATRIX.md` ("Peer measurement (`btest`)") for the cell states — that is the
|
|
22
|
+
only status surface. v1 scope: **both** modes (client + server), **EC-SRP5 +
|
|
23
|
+
unauthenticated** auth, **TCP and UDP** tests. Legacy pre-6.43 double-MD5 is out of
|
|
24
|
+
scope.
|
|
25
|
+
|
|
26
|
+
## Why this is not `execute`
|
|
27
|
+
|
|
28
|
+
btest is **not** a RouterOS-shaped command — it issues no CLI string and there is
|
|
29
|
+
no `:parse` / `/console/inspect` gate. It is a **peer protocol** (TCP/UDP port
|
|
30
|
+
2000) that centrs speaks directly to a RouterOS bandwidth server/client (or to
|
|
31
|
+
another `btest.exe`/centrs). `docs/CONSTITUTION.md` adds it as a deliberate
|
|
32
|
+
capability axis (*measure*, see Protocol selection): the envelope, identity
|
|
33
|
+
resolution, and friendly-error contract still hold, but the RouterOS-command
|
|
34
|
+
validation gate does not apply. btest is **explicit-only** — never auto-selected,
|
|
35
|
+
never a downgrade target, and no other command rides it.
|
|
36
|
+
|
|
37
|
+
## Synopsis
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
centrs btest client <router> [--protocol udp|tcp] [--direction receive|transmit|both] \
|
|
41
|
+
[--duration 15s] [--interval 1s] [--connection-count N] \
|
|
42
|
+
[--local-udp-tx-size N] [--remote-udp-tx-size N] \
|
|
43
|
+
[--local-tx-speed bps] [--remote-tx-speed bps] [--random-data] \
|
|
44
|
+
[--user U] [--password P] [--port 2000] [--csv | --format text|csv|json|yaml]
|
|
45
|
+
|
|
46
|
+
centrs btest server [--authenticate[=false]] [--user U] [--password P] \
|
|
47
|
+
[--bind 127.0.0.1] [--port 2000] \
|
|
48
|
+
[--allocate-udp-ports-from 2001] [--max-sessions 100] \
|
|
49
|
+
[--duration 1h] [--csv | --format text|csv|json|yaml]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Intent
|
|
53
|
+
|
|
54
|
+
- **Speak RouterOS, not an abstraction.** Flag names mirror
|
|
55
|
+
`/tool/bandwidth-test` and `/tool/bandwidth-server` verbatim (`direction`,
|
|
56
|
+
`protocol`, `local-udp-tx-size`, `connection-count`, `authenticate`,
|
|
57
|
+
`allocate-udp-ports-from`, …) so RouterOS muscle memory transfers. centrs adds
|
|
58
|
+
only `--bind` (a server safety control) and the shared `--format`.
|
|
59
|
+
- **Client** connects to a RouterOS bandwidth server (or a `btest.exe`/centrs
|
|
60
|
+
server), authenticates if required, runs the test in the requested direction,
|
|
61
|
+
and streams live throughput reports plus a final summary.
|
|
62
|
+
- **Server** listens on TCP/UDP port 2000, answers the handshake, performs the
|
|
63
|
+
EC-SRP5 server role when `authenticate=yes`, runs the requested test, and
|
|
64
|
+
streams session events. It runs in the foreground until `--duration` elapses or
|
|
65
|
+
Ctrl-C.
|
|
66
|
+
- **Identity resolution (client).** `<router>` resolves through the CDB exactly
|
|
67
|
+
like every command (constitution: identity and CDB) — `target` and the
|
|
68
|
+
`identity=`/`mac=`/`ip=` lookup keys yield the host, and `user`/`password` come
|
|
69
|
+
from the record (or `__default__`, env, CLI). A target not in the CDB still
|
|
70
|
+
works when `--user`/`--password` (or `CENTRS_USERNAME`/`CENTRS_PASSWORD`) are
|
|
71
|
+
given. The server takes no target.
|
|
72
|
+
- **Validation is option-grammar validation** (constitution: validation is the
|
|
73
|
+
product). centrs rejects incoherent option combinations *before* opening a
|
|
74
|
+
socket — e.g. `connection-count` with `protocol=udp`, `*-udp-tx-size` with
|
|
75
|
+
`protocol=tcp`, an out-of-range size (28..64000) or direction — with a
|
|
76
|
+
`validation/*` / `usage/*` error. There is no `:parse` step.
|
|
77
|
+
|
|
78
|
+
## Flags
|
|
79
|
+
|
|
80
|
+
### `btest client <router>`
|
|
81
|
+
|
|
82
|
+
| Flag | RouterOS analog / default | Notes |
|
|
83
|
+
| ---- | ------------------------- | ----- |
|
|
84
|
+
| `--protocol <udp\|tcp>` | `protocol`, default `udp` | UDP measures raw throughput + loss; TCP measures goodput. |
|
|
85
|
+
| `--direction <receive\|transmit\|both>` | `direction`, default `receive` | `receive` = download (server→client), `transmit` = upload. |
|
|
86
|
+
| `--duration <dur>` | `duration` | Bounds the run; reuses `parseDuration`. Omit for open-ended (Ctrl-C). |
|
|
87
|
+
| `--interval <dur>` | `interval`, default `1s` | Report cadence (`20ms`..`5s`). One report frame per interval. |
|
|
88
|
+
| `--connection-count <n>` | `connection-count` (1..255) | **TCP only.** Parallel TCP data connections. |
|
|
89
|
+
| `--local-udp-tx-size <n>` | `local-udp-tx-size` (28..64000) | **UDP only.** Client→server packet size. |
|
|
90
|
+
| `--remote-udp-tx-size <n>` | `remote-udp-tx-size` (28..64000) | **UDP only.** Server→client packet size. |
|
|
91
|
+
| `--local-tx-speed <bps>` | `local-tx-speed` | Cap on client→server rate (bits/sec). |
|
|
92
|
+
| `--remote-tx-speed <bps>` | `remote-tx-speed` | Cap on server→client rate (bits/sec). |
|
|
93
|
+
| `--random-data` | `random-data`, default off | Incompressible payload (defeats link compression; CPU-heavier). |
|
|
94
|
+
| `--user` / `--password` | `user` / `password` | Fall back to CDB / `CENTRS_*`. Required when the server has `authenticate=yes`. |
|
|
95
|
+
| `--port <n>` | — (default `2000`) | Control port override. |
|
|
96
|
+
| `--format <text\|csv\|json\|yaml>` | — (default `text`) | `text` = live human reports; `csv` = streaming CSV records; `json`/`yaml` = final summary envelope. |
|
|
97
|
+
| `--csv` | — | Shortcut for `--format csv`. |
|
|
98
|
+
|
|
99
|
+
### `btest server`
|
|
100
|
+
|
|
101
|
+
| Flag | RouterOS analog / default | Notes |
|
|
102
|
+
| ---- | ------------------------- | ----- |
|
|
103
|
+
| `--authenticate[=false]` | `authenticate`, **default `true`** | Require EC-SRP5 auth; `--authenticate=false` accepts anonymous clients. |
|
|
104
|
+
| `--user` / `--password` | — | The accepted credential when `authenticate=true` (v1: one user). Falls back to `CENTRS_*`. |
|
|
105
|
+
| `--bind <addr>` | **centrs-only** | Listen address; **default `127.0.0.1`**. Use `0.0.0.0` to expose on the LAN. |
|
|
106
|
+
| `--port <n>` | — (default `2000`) | Control-connection port. |
|
|
107
|
+
| `--allocate-udp-ports-from <n>` | `allocate-udp-ports-from` | Base of the UDP data-port range; default `2001` (just above the TCP/2000 control port). |
|
|
108
|
+
| `--max-sessions <n>` | `max-sessions` (1..1000), default `100` | Concurrent test cap; further clients get `routeros/btest-too-many-sessions`. |
|
|
109
|
+
| `--duration <dur>` | — | Optional auto-stop; otherwise runs until Ctrl-C. |
|
|
110
|
+
| `--format <text\|csv\|json\|yaml>` | — (default `text`) | `text` = human session log; `csv` = streaming CSV records; `json`/`yaml` = final summary envelope. |
|
|
111
|
+
| `--csv` | — | Shortcut for `--format csv`. |
|
|
112
|
+
|
|
113
|
+
## How it works
|
|
114
|
+
|
|
115
|
+
The control channel is TCP on port 2000. The server sends a hello; the client
|
|
116
|
+
replies with a 16-byte command packet (protocol type, direction, data type, TCP
|
|
117
|
+
connection count, UDP tx size, buffer size, remote/local speed limits). For a UDP
|
|
118
|
+
test, data then flows on the server's allocated UDP ports
|
|
119
|
+
(`allocate-udp-ports-from`+) and the client's `+256`-offset ports; loss is derived
|
|
120
|
+
from sequence gaps. For a TCP test, the bulk data rides `connection-count` TCP
|
|
121
|
+
connections and goodput is measured on the data stream (TCP/IP headers excluded,
|
|
122
|
+
matching RouterOS accounting). For multi-connection TCP the server's OK response
|
|
123
|
+
carries a 2-byte **session token**; the extra connections reconnect and present
|
|
124
|
+
that token to join the same test (no re-auth). Throughput and CPU are exchanged
|
|
125
|
+
once per `interval` as a 12-byte **status message** on the TCP channel
|
|
126
|
+
(`[0x07][0x80|cpu][00 00][seq u32 LE][bytesReceived u32 LE]`).
|
|
127
|
+
|
|
128
|
+
### Authentication (EC-SRP5)
|
|
129
|
+
|
|
130
|
+
RouterOS ≥ 6.43 (so 7.x, including the 7.23 target) authenticates btest with
|
|
131
|
+
**EC-SRP5** on the Curve25519-in-short-Weierstrass curve — the same curve centrs
|
|
132
|
+
already ships for mac-telnet. The password never crosses the wire. The curve math
|
|
133
|
+
is shared from `src/protocols/ec-srp5.ts` (extracted from `mtwei.ts`); btest adds
|
|
134
|
+
its own control-stream framing on top. The **server** role (generate a keypair,
|
|
135
|
+
accept the client offer, derive the shared secret, and *verify* the client proof)
|
|
136
|
+
is net-new — `mtwei.ts` only implements the client. A bad proof (or a client that
|
|
137
|
+
will not offer EC-SRP5 against an `authenticate=yes` server) maps to
|
|
138
|
+
`transport/auth-failed`. Legacy pre-6.43 double-MD5 is out of scope for v1.
|
|
139
|
+
|
|
140
|
+
## Output
|
|
141
|
+
|
|
142
|
+
btest runs over time, so it emits **live records** during the run plus a final
|
|
143
|
+
**summary**. v1 keeps the formats simple and **defers a broader JSON-streaming
|
|
144
|
+
decision** — it does *not* adopt the `stream` command's NDJSON-stream-of-envelopes
|
|
145
|
+
contract yet (see Open questions):
|
|
146
|
+
|
|
147
|
+
- **`text`** (default) — human-readable live reports during the run
|
|
148
|
+
(RouterOS / `btest.exe`-style `tx`/`rx` current/average/total; the server
|
|
149
|
+
prints a session log), then a final summary line.
|
|
150
|
+
- **`--csv`** (shortcut for `--format csv`) — **the streaming format**: one CSV
|
|
151
|
+
record per `--interval` report (client) or per session event (server), after a
|
|
152
|
+
header row. Pipe-friendly for logging/plotting.
|
|
153
|
+
- **`--format json` / `yaml`** — a **single summary envelope** emitted at
|
|
154
|
+
completion (`--duration` elapsed or Ctrl-C): the standard
|
|
155
|
+
`{ ok, data, warnings, tips, error, meta }` with `meta.via = "btest"`. `data`
|
|
156
|
+
aggregates the whole run — totals plus `data.reports[]` (client interval
|
|
157
|
+
samples) / `data.sessions[]` (server) — so it is **lossless** against what
|
|
158
|
+
`text` and `csv` streamed. It is not itself streamed (one envelope, at the end).
|
|
159
|
+
|
|
160
|
+
CSV columns — client:
|
|
161
|
+
`seq,direction,protocol,tx_bps,rx_bps,lost_packets,tx_bytes,rx_bytes` (one row per
|
|
162
|
+
interval; `seq` is the status sequence number, `tx_bytes`/`rx_bytes` the per-interval
|
|
163
|
+
byte counts); server:
|
|
164
|
+
`duration_ms,event,client,protocol,direction,user,tx_bps,rx_bps,lost_packets` (one row
|
|
165
|
+
per session; `tx_bps`/`rx_bps` are session averages, `lost_packets` is the UDP loss
|
|
166
|
+
total — 0 for TCP). A mid-run error ends
|
|
167
|
+
the stream and is carried in the summary envelope's `error` (`json`) or a final
|
|
168
|
+
`error` row (`csv`); the process exit code reflects whether the test/server
|
|
169
|
+
*started* cleanly.
|
|
170
|
+
|
|
171
|
+
## Server is a network listener
|
|
172
|
+
|
|
173
|
+
`centrs btest server` opens a TCP/UDP listener on port 2000 — a security-sensitive
|
|
174
|
+
surface (`docs/CONSTITUTION.md`, Network listeners). It is an **explicit,
|
|
175
|
+
foreground, user-invoked** command, **not** a daemon and **not** the proxy. It
|
|
176
|
+
**binds `127.0.0.1` by default** (pass `--bind 0.0.0.0` to expose on the LAN) and
|
|
177
|
+
**requires auth by default** (`authenticate=true`, mirroring RouterOS). The
|
|
178
|
+
accepted credential (`--user`/`--password`/`CENTRS_*`) and any client credentials
|
|
179
|
+
are redactable in bug-report output.
|
|
180
|
+
|
|
181
|
+
## Errors
|
|
182
|
+
|
|
183
|
+
- `validation/option` — an option is incompatible with the chosen
|
|
184
|
+
protocol/direction (e.g. `connection-count` with `protocol=udp`,
|
|
185
|
+
`*-udp-tx-size` with `protocol=tcp`) **or** out of range (UDP size `28..64000`,
|
|
186
|
+
`connection-count 1..255`, `interval 20ms..5s`, `max-sessions 1..1000`). Caught
|
|
187
|
+
before any socket opens; `error.context.option` names the offending flag.
|
|
188
|
+
- `transport/connection-refused` — no bandwidth server answered on port 2000
|
|
189
|
+
(server disabled, firewalled, or wrong host). Fix: enable
|
|
190
|
+
`/tool/bandwidth-server`, check the port/`--bind`.
|
|
191
|
+
- `transport/auth-failed` — EC-SRP5 proof rejected, or the server requires auth
|
|
192
|
+
and the client offered none / wrong credentials.
|
|
193
|
+
- `routeros/btest-too-many-sessions` — (server) a client was refused because
|
|
194
|
+
`max-sessions` is reached.
|
|
195
|
+
- `routeros/btest-protocol` — a malformed/unexpected control packet from the peer.
|
|
196
|
+
|
|
197
|
+
## Validation policy (CHR + loopback + btest.exe)
|
|
198
|
+
|
|
199
|
+
Decided with the user. Both cells have direct gated `CHR-passed` evidence:
|
|
200
|
+
|
|
201
|
+
- **Server cell — CHR client → centrs server** (`test/integration/btest.test.ts`):
|
|
202
|
+
`@tikoci/quickchr` boots a CHR whose `/tool/bandwidth-test` dials the **host**
|
|
203
|
+
(the QEMU SLIRP gateway `10.0.2.2`) where the centrs server listens on
|
|
204
|
+
`--bind 0.0.0.0 --port 2000`. Guest→host needs **no hostfwd**. Proven paths are
|
|
205
|
+
**TCP** (any direction — TCP is bidirectional on the outbound control connection)
|
|
206
|
+
and **UDP transmit** (client→server, which the guest originates, so SLIRP NATs it
|
|
207
|
+
cleanly), with unauth + EC-SRP5 and throughput cross-checked against the CHR's own
|
|
208
|
+
status. **UDP receive / both** depend on the server's packets reaching the guest's
|
|
209
|
+
UDP port *back* through SLIRP NAT (a one-shot smoke cycle only — see Open
|
|
210
|
+
questions).
|
|
211
|
+
- **Client cell — centrs client → CHR `/tool/bandwidth-server`**
|
|
212
|
+
(`test/integration/btest-client.test.ts`): the CHR boots with a host→guest
|
|
213
|
+
`extraPorts` forward `{ name: "btest", host: 0, guest: 2000, proto: "tcp" }`, so a host TCP port
|
|
214
|
+
maps onto the guest bandwidth server on 2000 (the same SLIRP inbound path that
|
|
215
|
+
already carries REST/SSH — no firewall change). The centrs client dials
|
|
216
|
+
`127.0.0.1:<host port>` and proves **TCP receive**, unauth and **EC-SRP5** (its
|
|
217
|
+
client proof verified by RouterOS's real server verifier), plus a wrong-password
|
|
218
|
+
reject. **TCP only** — UDP would need the server's datagrams to traverse SLIRP
|
|
219
|
+
back to the host, which this forward does not provide, so UDP client→server stays
|
|
220
|
+
loopback/transitive.
|
|
221
|
+
|
|
222
|
+
The fast/deterministic backstop under both is **centrs client ↔ centrs server
|
|
223
|
+
loopback** (`127.0.0.1`, injected sockets) covering the full client↔server matrix,
|
|
224
|
+
EC-SRP5 both roles, and the option-grammar rejects.
|
|
225
|
+
**`btest.exe` (via wine) is a coding-time grounding aid only** — used by hand
|
|
226
|
+
while implementing the EC-SRP5 framing and the server-side verifier; it is **not**
|
|
227
|
+
an integration test (it cannot run in CI) and **not** part of the long-term plan.
|
|
228
|
+
|
|
229
|
+
**Honest grounding caveat:** both cells are now directly CHR-passed. The remaining
|
|
230
|
+
transitive-only paths are **UDP client→CHR-server** (the SLIRP reverse-NAT path the
|
|
231
|
+
TCP forward does not cover) and TCP **multi-connection** fan-out — both deferred,
|
|
232
|
+
not yet gated.
|
|
233
|
+
|
|
234
|
+
## Open questions
|
|
235
|
+
|
|
236
|
+
- **JSON streaming is deliberately deferred.** btest does not adopt the `stream`
|
|
237
|
+
command's NDJSON-stream-of-envelopes contract yet; v1 streams `text` / `csv` and
|
|
238
|
+
returns a single summary envelope for `json` / `yaml`. Revisit a streaming-JSON
|
|
239
|
+
shape — ideally shared with `stream` — once that contract is settled.
|
|
240
|
+
- **UDP receive/both through SLIRP is unproven.** Before the integration suite
|
|
241
|
+
relies on server→guest UDP, a **one-shot CHR smoke test** must confirm the path
|
|
242
|
+
works — i.e. that the RouterOS client originates an initial UDP/NAT probe that
|
|
243
|
+
opens the SLIRP reverse mapping. Until then the gated UDP coverage is
|
|
244
|
+
**transmit** (guest→host); TCP already covers both directions.
|
|
245
|
+
- **UDP client→CHR-server and TCP multi-connection are not yet gated.** The direct
|
|
246
|
+
client→CHR-server gate (`test/integration/btest-client.test.ts`) is **TCP only**;
|
|
247
|
+
a UDP variant needs the server's datagrams to traverse SLIRP back to the host
|
|
248
|
+
(the reverse-NAT path), and `connection-count > 1` needs the parallel-stream
|
|
249
|
+
fan-out. Both remain loopback/transitive until gated. `btest.exe` (wine) is the
|
|
250
|
+
coding-time grounding peer, not CI.
|
|
251
|
+
|
|
252
|
+
## Out of scope (v1)
|
|
253
|
+
|
|
254
|
+
Legacy pre-6.43 double-MD5 auth; a UDP client→CHR-server gated test (TCP is gated;
|
|
255
|
+
UDP needs SLIRP reverse-NAT); the ≥3-router "test-through" topology RouterOS
|
|
256
|
+
recommends for measuring a *transit* device (centrs is an endpoint peer); TUI/proxy
|
|
257
|
+
btest frontends (later, over the stable core).
|