Files
vt1200/lat/local/HANDOFF.md
T

9.0 KiB

latd ↔ VT1200 — status, instructions & handoff

Current state: latd is STOPPED. No latd, login, osc-filter.py or tcpdump processes remain; lat/local/run/ is empty. Everything is inside lat/ — nothing was installed system-wide.

Root of this directory = lat/ (referred to as $LAT below).

$LAT = /home/arti/code/k-space/vt1200/lat

1. Endpoint facts (needed to reproduce)

Thing Value
VT1200 LAT/Ethernet MAC 08:00:2b:16:4b:5a
VT1200 IP 100.103.100.103
Host interface on that segment enp197s0f3u1 (host 100.103.255.254)
latd node name SID (this host's hostname)
LAT Ethertype 0x6004
LAT service-announcement multicast 09:00:2b:00:00:0f

Important: the VT1200's LAT Terminal Window connects by default to the service named after the latd node name (SID), i.e. the node-name service — not to a service you type, unless you type one explicitly.


2. One-command workflow (lat/run-local.sh)

cd $LAT
sudo ./run-local.sh build      # bootstrap + configure + make  -> lat/local/build-debug/
sudo ./run-local.sh start      # start latd + apply group/service config
sudo ./run-local.sh status     # show config + advertised services
sudo ./run-local.sh capture    # tcpdump LAT frames to lat/local/capture-*.pcap
sudo ./run-local.sh stop       # stop latd
sudo ./run-local.sh restart

Env overrides:

IFACE=enp197s0f3u1 VARIANT=debug sudo -E ./run-local.sh start
#   IFACE   interface latd uses        (default enp197s0f3u1)
#   VARIANT release|debug              (default debug; debug = verbose, -DNO_FORK)

build writes its output to lat/local/build.log. start writes the verbose latd log to lat/local/latd.log.

What start does

  1. setsid latcp -s -d -v -v -i $IFACE (foreground/verbose latd, detached)

  2. latcp -m 10 — advertise every 10 s

  3. latcp -G 1 — advertise in group 1 (must match the VT1200's group code)

  4. removes the default SID service, then re-adds SID and VT1200 both running $LAT/tools/osc-filter.py

    The default service that latd creates at startup is named after the node (SID) and runs /bin/login unfiltered — it must be replaced, or the VT1200 will freeze on it. latcp -A on an existing name fails ("Local service already exists"), so start deletes it first.


3. Manual equivalent (exactly how it was run by hand)

All commands run as root (sudo). BUILD below is the chosen build dir.

Build

cd $LAT/code/latd && ./bootstrap.sh          # only once; needs autoconf+automake

# release build
mkdir -p $LAT/local/build && cd $LAT/local/build
../../code/latd/configure \
  --with-latcp-socket=$LAT/local/run/latcp \
  --with-llogin-socket=$LAT/local/run/latlogin
make -j"$(nproc)"

# verbose/debug build (what was used for the investigation)
mkdir -p $LAT/local/build-debug && cd $LAT/local/build-debug
../../code/latd/configure --enable-debug \
  --with-latcp-socket=$LAT/local/run/latcp \
  --with-llogin-socket=$LAT/local/run/latlogin
make -j"$(nproc)"

--enable-debug adds -g3 -O0 -DVERBOSE_DEBUG -DNO_FORK; the running latd was the debug one. Sockets are kept under lat/local/run/ (configure options), so nothing lands in /var/run. /etc/latd.conf does not exist, so no global startup script is run.

Run

cd $LAT/local/build-debug
sudo setsid ./latcp -s -d -v -v -i enp197s0f3u1 >$LAT/local/latd.log 2>&1 </dev/null &
sudo ./latcp -m 10
sudo ./latcp -G 1
sudo ./latcp -D -a SID                                  # drop the unfiltered default
sudo ./latcp -A -a SID    -i 'filtered login (node name)' -r 100 -C $LAT/tools/osc-filter.py
sudo ./latcp -A -a VT1200 -i 'filtered login'             -r 100 -C $LAT/tools/osc-filter.py
sudo ./latcp -d                                         # verify
sudo ./latcp -h                                         # stop

Capture / decode

sudo tcpdump -i enp197s0f3u1 -w $LAT/local/out.pcap 'ether proto 0x6004'

tshark -r $LAT/local/out.pcap -T fields -e frame.number -e eth.src \
       -e lat.msg_typ -e lat.slot.type -e lat.slot.slot_data
tshark -r $LAT/local/out.pcap -Y 'lat.msg_typ==10' -V -O lat   # announcements

4. Root cause of the "login hangs" behaviour

Modern Linux writes OSC strings (ESC ] … BEL / ESC ] … ESC \) into the terminal stream. The VT1200's 1990 terminal emulator is not a general VT emulator for control strings and locks up on them. Sources observed here:

Source Sequence Guard
pam_systemd (via login) OSC 3008 type=session only if TERM != dumb (pam_systemd.c: if (streq_ptr(term,"dumb")) return PAM_SUCCESS;)
/etc/profile.d/80-systemd-osc-context.sh OSC 3008 type=shell/command bash-only; skipped when TERM=dumb
~/.config/zsh/functions/semantic-prompt.zsh OSC 133, OSC 122 none — unconditional
~/.config/zsh/functions/osc7-pwd.zsh OSC 7 none — unconditional

Evidence: vt-session.pcap — latd sends a 222-byte OSC 3008 then a 160-byte prompt containing OSC 133, after which the VT1200 never transmits again. Confirmation: a service running a bare /bin/sh (no OSC) works.

Upstream context: systemd issues #38527 and #41973.

Because the zsh scripts emit OSC unconditionally, TERM=dumb alone is not enough — the output must be filtered.


5. Fix: lat/tools/osc-filter.py

A pty filter used as the latd service command (default inner command /bin/login):

  • forks login on an inner pty and relays both directions;
  • drops control strings (ESC ] OSC, ESC P DCS, ESC X SOS, ESC ^ PM, ESC _ APC) from child→terminal; keeps all CSI (e.g. colours);
  • handles sequences split across reads;
  • puts the outer pty (latd's) into raw mode, so it is a transparent pipe — this is what makes passwords hidden (no local echo) and makes ^C reach login (no ISIG on the outer tty).

Verified locally against the exact captured OSC 3008 / OSC 133 bytes.

Status: with the filter, login and authentication succeeded on the VT1200 and the password/^C problems were fixed by the raw-mode change. The final test produced a VT1200 coredump — see §7.


6. Pitfalls / gotchas learned

  • The VT1200 connects to the node-name service (SID) by default; that service is created by latd unfiltered. Always replace/re-point it.
  • latcp -A refuses an existing service name ("Local service already exists"); delete with latcp -D -a NAME first.
  • Services need group 1 on both ends (latcp -G 1 + the terminal's Customize → LAT… group code), or they won't appear in the VT's list.
  • latcp -s runs /etc/latd.conf if it exists (hardcoded) — it does not here.
  • latd must be started via latcp -s (it refuses LATCP_STARTED otherwise); -C <cmd> and -u <user> on latcp -A set the service's command/user.
  • Don't pkill -f 'login -- …' — the pattern matches your own shell; use pkill -x login.

7. VT1200 CRASH (open item for the next session)

During the last test (login via osc-filter.py, outer pty in raw mode) the terminal crashed with a coredump. This may be a usefully reachable firmware bug — the whole EXPLOIT_NOTES.md effort is about exactly that.

  • Capture of that session: lat/local/filter-test2.pcap (547 LAT frames).
  • Related earlier capture (OSC leaking, using the unfiltered SID): lat/local/filter-test.pcap.

Suggested next steps: replay/step the frames in filter-test2.pcap, isolate the byte sequence that trips the firmware, and compare against vt-session.pcap (which merely froze, no crash).


8. Evidence / artifacts (lat/local/)

File What
filter-test2.pcap capture around the run that crashed the VT1200
filter-test.pcap earlier filtered attempt where OSC still leaked (unfiltered SID)
vt-session.pcap the original freeze, with the OSC 3008 + OSC 133 payloads
probe.pcap, lat-announce.pcap, session-*.pcap service announcements / early probes
latd.log full verbose latd log (sessions, slots, credits)
build.log last run-local.sh build output
svc-plain.sh, svc-osc.sh A/B probe scripts used to confirm the OSC trigger
HANDOFF.md this file

Other relevant paths:

lat/code/latd/            # latd 1.36 source (GPL-2.0)
lat/manuals/              # DEC LAT specification + network manager's guide
lat/tools/osc-filter.py   # the filter
lat/run-local.sh          # build/run/capture helper
lat/local/build/          # release build
lat/local/build-debug/    # debug build (used for the investigation)

9. Remaining open items

  1. Reproduce/root-cause the VT1200 coredump from filter-test2.pcap (§7).
  2. Set a proper TERM for the VT1200 session (currently unset) — e.g. vt220.
  3. Decide the service naming (node-name SID vs an explicit VT1200).
  4. Optional: build latd with --disable-defservice so the unfiltered default service never exists (then run-local.sh needn't delete SID).