mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-31 12:05:05 +02:00
424 lines
19 KiB
Markdown
424 lines
19 KiB
Markdown
# DEC MOP Remote Console (console carrier) — protocol notes and VT1200 DWT results
|
||
|
||
Status: **the protocol works against the MAME VT1200**. `HELP` and `SHOW` were
|
||
answered by the firmware; `TEST` and `INIT` were accepted but do not return a
|
||
usable console reply (details in §6). The client is `dwt/mop_console.py`.
|
||
|
||
Everything below is either read from a cited source or was observed on the
|
||
emulator. Where the two disagree, the observation is called out as such.
|
||
|
||
---
|
||
|
||
## 1. Sources
|
||
|
||
| source | what it establishes |
|
||
|---|---|
|
||
| DEC, *DECnet Digital Network Architecture — Maintenance Operations Protocol Functional Specification*, **v3.0.0**, Dec 1983, order AA-X436A-TK. PDF: <http://decnet.ipv7.net/docs/dundas/aa-x436a-tk.pdf> (text extracted locally with `pdftotext -layout`) | MOP message codes and bodies, §5.3 “Remote Console” pp. 79–87; Ethernet protocol types and multicast addresses, Appendix B.3 pp. B-1/B-2 |
|
||
| DEC, *MOP Functional Specification*, **v4.0.0**, Jun 1992, EK-DNA11-FS-001. PDF: <https://bitsavers.org/pdf/dec/decnet/EK-DNA11-FS-001_MOP_Functional_Specification_V4.0.0_Jun92.pdf> | later revision of the same protocol (linked from the revision table at <https://gunkies.org/wiki/Maintenance_Operations_Protocol>) |
|
||
| Mats O Jansson, `mopd` 2.5.3, `common/mopdef.h` and `common/rc.c`: <https://sources.debian.org/src/mopd/1:2.5.3-21/common/mopdef.h/> | independent constants: `MOP_K_PROTO_RC 0x6002`; codes 5/7/9/11/13/15/17/19; multicast `ab:00:00:02:00:00` |
|
||
| Christine Caulfield, `latd`'s `moprc.cc` / `moprc.h`: <https://github.com/cguldenschuh/latd/blob/main/moprc.cc> | a working console **requester**: the on-wire header (2-byte little-endian length, then code), the Reserve + Request ID handshake, the System ID function-bit check, CCP/CRA flag handling, Release |
|
||
| libpcap `ethertype.h`: <https://github.com/the-tcpdump-group/libpcap/blob/master/ethertype.h> | all four ethertypes: `MOPDL 0x6001`, `MOPRC 0x6002`, `DN 0x6003`, `LAT 0x6004` |
|
||
| VSI, *DECnet-Plus for OpenVMS Network Management Guide*, §10.2.2.1 and Appendix F: <https://docs.vmssoftware.com/vsi-decnet-plus-network-management-guide/> | the 8-byte Reserve-Console **verification** value is the MOP service password; byte order for NCL vs NCP; `ccr` is the DECnet-Plus console client |
|
||
| `docs/EK-V1000-SV-002_service-guide.txt` §2.2 (this repo) | the VT1200 side: NCP `CONNECT VIA <circuit> PHYSICAL ADDRESS <MAC>`, `DWT>>>` prompt, `INIT`/`SHOW`/`TEST`/`HELP`, `TEST` parameters and P3 mode bits |
|
||
|
||
Ethertypes were **not** taken on trust:
|
||
|
||
* `0x6001` MOP Dump/Load — libpcap; also `60-01` in MOP §B.3.
|
||
* `0x6002` MOP Remote Console — libpcap, `mopdef.h`, MOP §B.3 (“60-02 Remote Console”), and observed on the wire.
|
||
* `0x6003` DECnet (DNA Routing) — libpcap; the MOP spec does not claim it.
|
||
* `0x6004` LAT — libpcap.
|
||
|
||
Only `0x6002` is used here.
|
||
|
||
---
|
||
|
||
## 2. Frame layout
|
||
|
||
Ethernet II, protocol type **0x6002**, destination = the terminal’s unicast MAC
|
||
(or the protocol’s multicast `ab:00:00:02:00:00`; the VT1200 answers unicast,
|
||
which is all we needed):
|
||
|
||
```
|
||
offset size field
|
||
0 6 destination MAC
|
||
6 6 source MAC
|
||
12 2 ethertype 0x6002 (big-endian on the wire)
|
||
14 2 MOP length, little-endian
|
||
16 1 MOP function code
|
||
17 ... code-specific body
|
||
end ... zero padding to a 46-byte Ethernet payload minimum
|
||
```
|
||
|
||
The **length counts the bytes after the length field** (code + body), and
|
||
excludes the length itself and any padding. `moprc.cc` builds every message
|
||
that way (`buf[0] = len+2; buf[1] = 0; buf[2] = code; …`) and the VT1200 does
|
||
the same: a `SHOW` reply is a 527-byte frame with length `0x01FF` = 511 =
|
||
1 code + 1 flags + 509 data.
|
||
|
||
**The length field is authoritative, not the frame size.** This is not
|
||
theoretical: when polled for a second response the VT1200 emits a 60-byte frame
|
||
whose length field is `2` but whose bytes after the CRA flags are **stale text
|
||
from the previous response buffer** (observed: `SHOW` leftovers after the
|
||
`TEST` reply). A client that trusts the frame size will print garbage.
|
||
`dwt/evidence/mop-script3.pcap` contains both cases.
|
||
|
||
There is **no version byte, no session id and no checksum** on this path. The
|
||
MOP spec’s Remote Console messages are additions to MOP 2.1 (§5.3); the fields
|
||
above are the whole header.
|
||
|
||
### Function codes used
|
||
|
||
From MOP §5.3 and `mopdef.h`:
|
||
|
||
| code | message | body |
|
||
|---|---|---|
|
||
| 5 | Request ID | reserved (1, = 0) + receipt number (2, LE) |
|
||
| 6 | Boot | verification + processor/control/software id (not used) |
|
||
| 7 | System ID | reserved (1, = 0) + receipt (2, LE) + TLVs |
|
||
| 9 | Request Counters | receipt (2) |
|
||
| 11 | Counters | receipt (2) + counter block |
|
||
| **13** | **Reserve Console** | **verification (8)** — the MOP service password |
|
||
| **15** | **Release Console** | empty |
|
||
| **17** | **Console Command and Poll** | control flags (1) + console data (*) |
|
||
| **19** | **Console Response and Acknowledge** | control flags (1) + console data (*) |
|
||
|
||
Codes 16 and 18 are explicitly reserved by MOP §5.
|
||
|
||
### System ID TLVs
|
||
|
||
Body after the reserved byte and receipt is a sequence of
|
||
`type (2, LE) | length (1) | value (length)` entries. The VT1200’s reply
|
||
(`dwt/evidence/mop-hello.pcap`) decodes as:
|
||
|
||
```
|
||
37 00 length = 55
|
||
07 code = System ID
|
||
00 reserved
|
||
00 00 receipt
|
||
01 00 03 03 00 00 maintenance version 3.0.0
|
||
02 00 02 e1 00 functions = 0x00E1
|
||
03 00 06 c6 fc cc 16 f5 66 console user = host MAC
|
||
04 00 02 0c 00 reservation timer = 12 s
|
||
05 00 02 20 00 console command buffer = 32 bytes
|
||
06 00 02 58 02 console response buffer = 600 bytes
|
||
07 00 06 08 00 2b 16 4b 5a hardware address
|
||
64 00 01 72 communication device = 0x72
|
||
00 00 00 padding (parsed as an empty type-0 entry)
|
||
```
|
||
|
||
Function bitmap (**type 2, bit 5** = console carrier, 0x20):
|
||
|
||
```
|
||
bit 0 loop, 1 dump, 2 primary loader, 3 multi-block loader, 4 boot,
|
||
bit 5 console carrier, 6 data link counters, 7 console reservation
|
||
```
|
||
|
||
`0xE1` therefore means loop + console carrier + counters + reservation.
|
||
`moprc` refuses to proceed unless `functions & 0x20` (and prints “target does
|
||
not support remote console” otherwise).
|
||
|
||
---
|
||
|
||
## 3. Session and sequence handling
|
||
|
||
`moprc`’s sequence is the reference implementation and was followed exactly:
|
||
|
||
1. **Reserve Console (13)** with an 8-byte verification value.
|
||
2. **Request ID (5)** with reserved `0` and a 2-byte receipt number.
|
||
3. Wait for **System ID (7)**. This is the “connected” condition; check
|
||
function bit 5.
|
||
4. **Console Command and Poll (17)** carries console input. The control-flags
|
||
byte has:
|
||
* bit 0 — **message number**, a one-bit sequence number toggled for each
|
||
command;
|
||
* bit 1 — **break**, prepend a serial break to the command data.
|
||
5. **Console Response and Acknowledge (19)** carries console output. Its flags:
|
||
* bit 0 — message number being acknowledged;
|
||
* bit 1 — command data lost (resend);
|
||
* bit 2 — response data lost (overrun).
|
||
6. **Release Console (15)** with no body.
|
||
|
||
There is no connection id beyond the 48-bit MAC pair, and no per-message
|
||
acknowledgement other than the poll/response alternation. The protocol is
|
||
half-duplex and polled: the terminal buffers console output and sends it in a
|
||
CRA when it is polled. **Do not keep polling after data arrives** unless you
|
||
want the response repeated — the VT1200 re-sends its whole response buffer on
|
||
every poll, and an empty response is a CRA with length 2 whose frame may still
|
||
carry stale bytes (§2). `mop_console.py` polls only until the first data, then
|
||
drains, treating an exact repeat as a duplicate and a superset as a buffer
|
||
that grew.
|
||
|
||
Data is a raw byte stream. A command line is the ASCII text plus `\r` (the
|
||
DWT parser and the real terminal use CR; `moprc` translates LF to CR). The
|
||
terminal echoes the input and answers with CRLF text and the `DWT>>> ` prompt.
|
||
|
||
---
|
||
|
||
## 4. Password / verification
|
||
|
||
`Reserve Console`’s 8-byte VERIFICATION is the **MOP service password**
|
||
(MOP §5.3.6: “a verification code that must match before the receiving system
|
||
can honor the request”). VSI §10.2.2.1 says a server that requires a password
|
||
**ignores the request** if it is wrong — there is no negative reply, so the
|
||
symptom is exactly “Target does not respond”. The default is
|
||
`%X0000000000000000`.
|
||
|
||
Byte order (VSI §10.2.2.1):
|
||
|
||
* **NCL / DECnet-Plus**: the value is an octet string; the **leftmost** hex
|
||
pair is the first byte on the wire.
|
||
* **NCP (Phase IV)**: the value is an unsigned integer; the **rightmost**
|
||
digits are the low byte, so bytes go out reversed. NCP `123456789ABCD`
|
||
becomes NCL `%XCDAB896745230100`, transmitted `CD AB 89 67 45 23 01 00`.
|
||
|
||
The client supports both: `--password HEX` (NCL order, zero-padded to 8) and
|
||
`--password-int HEX` (NCP integer order, little-endian).
|
||
|
||
**The VT1200 did not challenge us.** Reserve with eight zero bytes works, and
|
||
Reserve with the arbitrary non-zero value `0102030405060708` also works
|
||
(`dwt/evidence/wrong-password.log`): the firmware accepted the reservation and
|
||
answered `HELP`. So in this configuration the verification field is not
|
||
validated (or no password is configured). There is no challenge/response
|
||
handshake on the MOP path.
|
||
|
||
The ROM string `dwt_prf: challenge failed - size error (expecting %d, got %d)`
|
||
is **not** this path. Per `dwt/notes/rdebug-static.md` §6, `dwt_prf`
|
||
(`0x07EB3FB8`) is the 16-byte challenge of the **TCP remote-debug listener**
|
||
(`DEBUG TCP Port`, 771), and that listener is separately gated closed by the
|
||
configuration word at bit `0xAECD2`. MOP never calls it.
|
||
|
||
---
|
||
|
||
## 5. What the emulator actually replied
|
||
|
||
Setup: `VT1200_SKIP_LEVEL1=1`, `-cfg_directory cfg-net -networkprovider taptun`,
|
||
private NVRAM, OS booted to the Terminal Manager. Capture:
|
||
`dwt/evidence/mop-hello.pcap` (tcpdump filter `ether proto 0x6002`), log:
|
||
`dwt/evidence/hello.log`.
|
||
|
||
Sequence (timestamps from the pcap):
|
||
|
||
```
|
||
19.715674 host -> terminal Reserve Console payload 0900 0d00 0000000000000000
|
||
19.715796 host -> terminal Request ID payload 0400 0500 0000
|
||
19.716371 term -> host System ID 71-byte frame, length 0x37
|
||
19.716620 host -> terminal CCP "HELP\r" payload 0700 1100 48454c500d
|
||
19.716674 host -> terminal CCP poll (flags 1)
|
||
19.719929 term -> host CRA 443-byte frame, length 0x01AB = 427
|
||
19.720308 term -> host CRA (empty ack, stale bytes, length 2)
|
||
...
|
||
20.822062 term -> host CRA SHOW 527-byte frame, length 0x01FF = 511
|
||
```
|
||
|
||
The first CRA data is the whole 425-byte HELP response in **one frame**:
|
||
|
||
```
|
||
ab01 1300 48454c50 0d0a 0d0a
|
||
2020 2044 4543 7769 6e64 6f77 7320 5465 726d 696e 616c ...
|
||
...
|
||
0d0a 0a0a 0d44 5754 3e3e 3e20
|
||
```
|
||
|
||
i.e. `HELP\r\n\r\n DECwindows Terminal Remote Console\r\n…DWT>>> `.
|
||
`SHOW` is 509 bytes (`dwt/evidence/dwt-show.txt`):
|
||
|
||
```
|
||
SHOW
|
||
|
||
DECwindows Terminal System Configuration
|
||
|
||
Configuration Parameter Parameter Value
|
||
--------------------------------------------------------
|
||
Firmware Revision V2.2
|
||
Option Memory Present
|
||
SIMM 1 installed
|
||
SIMM 2 installed
|
||
SIMM 3 installed
|
||
Image Option Present
|
||
Display Type Monochrome
|
||
Monitor Type VR150
|
||
|
||
DWT>>>
|
||
```
|
||
|
||
`HELP` (`dwt/evidence/dwt-help.txt`) lists exactly the four documented
|
||
commands and their syntax; it matches the ROM strings and the static analysis
|
||
(`rdebug-static.md` §9).
|
||
|
||
---
|
||
|
||
## 6. `TEST` and `INIT`
|
||
|
||
The service guide says remote diagnostics requires a reset and **no active work
|
||
(no open windows)**. DWT lives inside the running session manager, so with the
|
||
Terminal Manager open we could not give it that state. The observed behaviour
|
||
is consistent with that precondition, but note what is and is not established:
|
||
|
||
**`TEST 0 0` and `TEST 12`** (two different tests, two fresh boots, private
|
||
NVRAM): the terminal accepts the line and replies
|
||
|
||
```
|
||
TEST 0 0
|
||
|
||
Testing...
|
||
```
|
||
|
||
(22 bytes, CRA length 24; `dwt/evidence/test00b.log`, `test12.log`), and then
|
||
**no further console output is produced**, even with continued polling for
|
||
10 s. Worse, the MOP server then stops answering: a following `Reserve` gets
|
||
no System ID (`help-after-test.log`, `help-after-test12.log`). We tried a
|
||
longer wait (test 0: more than a minute after the command) with the same
|
||
result. So the documented `TEST` is reachable and parses, but does not return
|
||
the `OK` / `DWT>>>` the service guide shows in this OS-up configuration. We
|
||
cannot distinguish “test is running and has taken over the machine” from “the
|
||
session manager was stopped by the test” from the console alone. This is an
|
||
honest gap, not a protocol failure: the frame carrying `Testing...` has correct
|
||
length, code and flags.
|
||
|
||
**`INIT`**: accepted, **no reply at all**, and the terminal stops answering
|
||
MOP for >90 s afterwards (`dwt/evidence/init.log`, `help-after-init2.log`);
|
||
consistent with a reset/reboot. Do not run this against the real terminal.
|
||
|
||
**Why not run `TEST` in the pre-OS menu?** DWT does not exist there — see §7.
|
||
|
||
---
|
||
|
||
## 7. Does DWT answer before the OS boots? No.
|
||
|
||
Two configurations were tested.
|
||
|
||
**OS-up** (`VT1200_SKIP_LEVEL1=1`, `-cfg_directory cfg-net`, private NVRAM):
|
||
System ID in 0.5 ms, HELP/SHOW as above.
|
||
|
||
**Bare boot** (no `VT1200_SKIP_LEVEL1`), which is the repo’s level-0
|
||
diagnostics-menu baseline `PC=3FDCE400` (`harness/mame/README.md`). A Lua probe
|
||
printed the PC and a client sent Reserve/RequestID at the same time:
|
||
|
||
```
|
||
PC_PROBE frame=100 PC=3FDCE400
|
||
PC_PROBE frame=200 PC=3FDCE400
|
||
PC_PROBE frame=400 PC=3FDCE400
|
||
PC_PROBE frame=600 PC=3FDCE400
|
||
PC_PROBE frame=1200 PC=3FDCE400
|
||
PC_PROBE frame=2400 PC=3FDCE400
|
||
PC_PROBE frame=3600 PC=3FDCE400
|
||
```
|
||
|
||
while **8 Reserve/RequestID pairs over 8 s got zero frames back**
|
||
(`dwt/evidence/noos-attempt3.log`, `mop-noos.pcap`, `mame-noos-probe.log`).
|
||
Earlier without the probe, two 8 s attempts (16 pairs) were also silent
|
||
(`noos-attempt1.log`, `noos-attempt2.log`).
|
||
|
||
So the DWT console is part of the booted OS/session manager, not the power-on
|
||
diagnostics code. That matches the ROM string
|
||
`Session Manager : Cannot create MOP-Server` and the static analysis showing
|
||
the TEXT parser and the four-command table live in the OS image.
|
||
|
||
Caveat: the OS-up runs use `VT1200_SKIP_LEVEL1=1`, a **deliberate harness
|
||
divergence** (modelled board input; the real machine reads IP3 low). These
|
||
results are evidence about the OS/DWT path, not about the real terminal’s POST.
|
||
|
||
---
|
||
|
||
## 8. Client
|
||
|
||
`dwt/mop_console.py`, stdlib only (`socket.AF_PACKET`, `select`, `struct`,
|
||
`fcntl`). No scapy.
|
||
|
||
* `--iface` (default `vt1200`), `--mac` (default `08:00:2b:16:4b:5a`),
|
||
`--src-mac` (default: the interface MAC).
|
||
* `connect` = Reserve + Request ID; prints the System ID decode; requires
|
||
function bit 5.
|
||
* one command: `… HELP` or `… --raw HEX`.
|
||
* `--script [CMDS]` defaults to `HELP,SHOW,TEST 0 0`; `--script HELP,SHOW`
|
||
runs a subset. Every TX/RX frame is hexdumped and decoded.
|
||
* `--raw HEX` sends arbitrary **console data** inside a CCP. `--raw-frame HEX`
|
||
sends a complete MOP payload (length + code + body) for protocol experiments.
|
||
* `--password HEX` / `--password-int HEX`, `--ethertype` (default 0x6002),
|
||
`--dst` (unicast/broadcast/multicast override), `--no-connect`,
|
||
`--no-release`, `--quiet`, `--connect-timeout`, `--dry-run`.
|
||
* Timeout diagnosis prints the phase, the last frames sent (full hex and
|
||
payload-only hex), whether anything was received, and the common causes
|
||
(wrong password = silent ignore; pre-OS menu; wrong ethertype).
|
||
* `--dry-run` needs no socket and prints the exact frames.
|
||
|
||
**Privileges:** `AF_PACKET` send needs `CAP_NET_RAW`. Run with `sudo`, or grant
|
||
the interpreter the capability once:
|
||
|
||
```sh
|
||
sudo setcap cap_net_raw+ep "$(readlink -f "$(which python3)")"
|
||
```
|
||
|
||
`tcpdump` on this host works for user `arti` via the `wireshark` group; the
|
||
client itself does not.
|
||
|
||
### Reproduce
|
||
|
||
```sh
|
||
# once per boot of the host
|
||
./harness/net-setup.sh
|
||
|
||
# terminal: OS-up headless run (private NVRAM, ~20 s wall to the Terminal Manager)
|
||
cd harness/mame
|
||
VT1200_SKIP_LEVEL1=1 /home/arti/code/k-space/mame0289/vt1200 vt1200 \
|
||
-rompath roms -sound none -skip_gameinfo -lightgun -lightgun_device mouse \
|
||
-cfg_directory cfg-net -networkprovider taptun \
|
||
-video none -nothrottle -seconds_to_run 600 \
|
||
-nvram_directory /home/arti/code/k-space/vt1200/.vt1200-scratch/dwt-nv
|
||
|
||
# capture (second shell)
|
||
sudo tcpdump -i vt1200 -U -w dwt/evidence/mop-hello.pcap 'ether proto 0x6002'
|
||
|
||
# client (third shell, after the Terminal Manager is up)
|
||
sudo python3 dwt/mop_console.py --iface vt1200 --mac 08:00:2b:16:4b:5a \
|
||
--script HELP,SHOW
|
||
```
|
||
|
||
Expected: 425 bytes for HELP, 509 for SHOW, each in one CRA frame.
|
||
|
||
For the pre-OS check, drop `VT1200_SKIP_LEVEL1=1` (and add
|
||
`-debug -debugger none -autoboot_script dwt/evidence/pc_probe.lua` if you want
|
||
the `PC=3FDCE400` confirmation). For `TEST`/`INIT`:
|
||
|
||
```sh
|
||
sudo python3 dwt/mop_console.py --iface vt1200 --mac 08:00:2b:16:4b:5a \
|
||
--quiet 10 --raw "$(printf 'TEST 0 0\r' | xxd -p | tr -d '\n')"
|
||
```
|
||
|
||
`--dry-run` version of the whole script (no privileges):
|
||
|
||
```sh
|
||
python3 dwt/mop_console.py --dry-run --script HELP,SHOW
|
||
```
|
||
|
||
---
|
||
|
||
## 9. What changed between attempts
|
||
|
||
* The first attempt used the `moprc` framing verbatim and worked on the first
|
||
OS-up try; no ethertype or addressing fallback was needed. (`0x6001` is
|
||
Dump/Load and was not tried because `0x6002` answered.)
|
||
* The first collector polled continuously. The VT1200 answers every poll by
|
||
re-sending its buffer, so the first HELP capture had **1235 frames** for one
|
||
HELP (not kept; the behaviour is visible in the duplicate 60-byte CRAs in
|
||
`mop-script3.pcap`). The client now stops polling once data has arrived, and
|
||
treats a repeated chunk as a duplicate and a superset as a grown buffer. The
|
||
clean run is `mop-hello.pcap` — 135 packets because the terminal emits an
|
||
empty CRA for every poll, but only three carry data (`System ID`, HELP, SHOW).
|
||
* A 0.8–1.0 s quiet window cut `TEST` off after `Testing...`; extending it to
|
||
10 s and then polling for over a minute confirmed there is no further data,
|
||
so the `TEST` result is not a client timeout.
|
||
* The “no DWT before OS” claim was first one silent attempt (not enough), then
|
||
repeated 16 silent handshakes, then tied to `PC=3FDCE400` with a frame probe.
|
||
|
||
## 10. Open points
|
||
|
||
* Whether `TEST` would complete if the terminal were reset with no active
|
||
windows (the service guide’s precondition). DWT lives in the session
|
||
manager, so “no session manager” and “DWT reachable” appear mutually
|
||
exclusive in this firmware; a real terminal may behave differently.
|
||
* Whether the VT1200 has a non-empty maintenance password setting in NVR that
|
||
would make the 8-byte verification meaningful. Our fresh private NVR is
|
||
all-zero and the firmware ignored even a wrong non-zero value.
|
||
* `dwt_prf` / TCP port 771 is the other agent’s path, not this one.
|