Files
vt1200/dwt/notes/mop-protocol.md

19 KiB
Raw Permalink Blame History

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:

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

# 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:

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):

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.