You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The desktop GUI remembers its window position and size. The
configuration window now reopens where you last left it — its outer position
and inner size are saved (debounced) to gui_window_geometry in egateway.conf and restored on the next launch. It is auto-managed: there is
no config-UI field for it, and an empty value means "use the default size and
let the window manager place it." Works on X11/Windows/macOS; Wayland
compositors don't expose a window's position, so it isn't remembered there.
Peer-dial: call another serial port directly. With the new allow_peer_dial opt-in (default off; wired into telnet Configuration > M >
P, web, and GUI), a modem-mode port can dial another port by address — ATD <Port>@<IP> (e.g. ATD B@192.168.1.50) — or select that port in the
Serial Gateway menu, and bridge straight through to the device on it (the
gateway equivalent of calling a friend's modem). A modem-mode target rings and answers per its own AT rules (S0 auto-answer / manual ATA); a console-mode target connects directly. The connection is a transparent
byte pipe, so a file-transfer protocol runs end to end between the two
devices. Result codes follow ATX (CONNECT/BUSY/NO ANSWER/NO CARRIER).
Works on the same gateway and, over the master/slave relay, from a slave
device to a port on its master (ATD <Port>@<master-ip>): the slave relays
the call and the master resolves the address to one of its own ports and
rings/connects it (gated by the master's master_accept_relays + allow_peer_dial). Cross-gateway is symmetric: the master routes a peer
address to any port a slave has registered — a slave's console port
and its modem port (a slave modem port announces itself to the master and,
when dialed, rings the attached device) — so <Port>@<slave-ip> reaches a
slave's port from the master or, via the master as a crossbar, from another
slave (device ↔ slave-A ↔ master ↔ slave-B ↔ device). Addressing is by IP, so
gateways need distinct addresses (normal for separate machines). See README
"Peer-Dial" and user manual §9.2.3.
Live relay status in the telnet Master/Slave screen. A master now lists
the remote console ports slaves have registered (so you can see connected
slaves at a glance); a slave shows each console port's link state to its
master (down/connecting/registered/bridging) — relay connectivity is
now visible without reading the logs.
Relay channel handshake / protocol version. The master now writes a small
hello (EGR magic + a protocol-version byte) as the first bytes on every
accepted master/slave relay or console-registration channel; the slave
validates it before using the channel. A master/slave version skew now fails
cleanly with an "upgrade the older gateway" message instead of desyncing, and
a slave pointed at a master that is declining relays (standalone, master_accept_relays=false, or at capacity) now detects the refusal — the
absence of the hello — and backs off with a clear message, instead of
mistaking the refused-but-open channel for a live registration and idling.
Optional hardware carrier (DCD) signalling. New per-port opt-in serial_a_drive_carrier / serial_b_drive_carrier (default false; also a
checkbox in the GUI/web config and the C key in the telnet per-port modem
menu). When enabled, the modem emulator drives DTR as a carrier proxy
(a PC/USB-serial adapter is a DTE and can't drive a DCD output, so you cross
DTR→DCD in a null-modem cable, as tcpser does), following AT&C: &C0 forces
it always asserted while the port is open, &C1 (default) asserts on CONNECT and drops on NO CARRIER / ATH / hangup / relay-link-loss (so a
slave-attached machine sees loss-of-carrier in hardware too). When off, the
gateway makes zero modem-control-line calls, so ports without DCD wiring are
byte-for-byte unaffected. Modem mode only.
Master/Slave serial extender (optional). A gateway set to gateway_role = slave extends its serial ports to a master gateway over
the master's existing SSH port; the serial device reaches the master's menu,
file transfer, and dial-out as if attached to the master, and files always
land on the master. Default gateway_role = standalone leaves the feature
entirely inert. Modem-mode ports relay on connect (the slave resolves its local dial map; the master dials onward — "resolve local, dial central");
console-mode ports register with the master and appear in the master's Serial
Gateway picker (local ports + registered remote ports). New config keys
(telnet/web/GUI): gateway_role, master_accept_relays, slave_master_host, slave_master_port, slave_master_username, slave_master_password, relay_transport (only ssh implemented). The slave authenticates with the
master's unified username/password and pins the master's SSH host key (TOFU,
in gateway_hosts); relay connections are gated by master_accept_relays and
count against the session cap. The slave's main menu shows a SLAVE-mode notice
with the master address, and reconnects automatically if the link drops.
Serial sessions can now receive administrative broadcasts. A process-global
broadcast channel (serial::broadcast_to_serial) fans a message out to every
open serial port, delivered at the command prompt only — an in-call
(online) serial session, which may be carrying a binary file transfer, drains
its queued messages when it next returns to command mode (+++, hangup, or
call end) so a notice can never corrupt a transfer. This is the serial-side
counterpart to the telnet/SSH/relay broadcast_to_sessions list, completing
broadcast coverage across all connection types. The shutdown "Goodbye" keeps
its own reliable shutdown-flag write (which fires even mid-online) and is not
routed through this channel. Modem mode only. (Extension point: no production
broadcast is routed to it yet — the first admin-notice feature plugs in here.)
Fixed
Serial AT&C now updates the hardware carrier (DCD/DTR) line immediately.
With serial_X_drive_carrier enabled, changing AT&C at the command prompt
used to take effect only at the next connect/hangup; it now re-applies the
DCD line right away — &C0 asserts DTR (carrier forced on regardless of call
state) and &C1 restores follow-the-carrier — matching the documented
contract and the existing ATZ/AT&F behavior. Found during on-hardware DCD
validation (DTR→DCD crossover).
GUI console started as a boot service now waits for the window manager.
When launched as a boot-time systemd service, the console window could come
up undecorated (no title bar / minimize / close) or with its title bar tucked
under the desktop panel, because it opened as soon as the X server accepted a
connection — before the window manager had taken over decoration and
placement. The display-wait now also waits (bounded, X11-only) for an EWMH
window manager (_NET_SUPPORTING_WM_CHECK on the root window) before opening
the window. Degrades safely: no xprop, a bare X server, or a non-EWMH WM
falls through after a short cap and opens anyway, and the server is never
delayed (only the window waits). Non-X11 targets (Windows, macOS, headless,
pure-Wayland) are unaffected — the wait returns immediately without DISPLAY.
Serial Gateway menu shows peer-dial addresses without spaces around @.
Remote (slave) port entries are now displayed as <Port>@<ip> — exactly the
string you type to dial them (ATDT <Port>@<ip>). The previous spaced form
(<Port> @ <ip>) invited mistyped dial strings with embedded spaces. The
remote-bridge screen title and the master's registered-ports status list were
unspaced to match.
Master/Slave configuration now guides the operator by role. Across the
telnet menu, web, and desktop GUI, fields that don't apply to the selected
role are greyed out / disabled: accept relays is editable only for a Master (and now defaults on when you switch to Master, since a master
with it off can't accept slaves), while the master host / port / user / pass
are editable only for a Slave. Switching to Master while the SSH server is
off now surfaces a warning (a popup in web/GUI, a dedicated screen in telnet)
explaining that slaves connect over SSH — it points you at the setting but
never toggles SSH for you.
Peer-dial now reminds you about local echo. A peer-dial connection is a
transparent link with no host echoing keystrokes back, so the Serial Gateway
picker shows a "enable local echo to see typing" tip, and the README /
user-manual peer-dial sections explain that each terminal needs local echo
(half-duplex) — and that ATE does not affect the online data path.
Shutdown "Goodbye" now reaches every session, not just when telnet is
enabled. The shutdown broadcast used to live inside the telnet accept loop,
so an SSH-only deployment (telnet_enabled = false) tore SSH and relay
sessions down with no notice. It is now a transport-neutral broadcast invoked
centrally at shutdown, so telnet, SSH, and master/slave relay sessions all
receive it for any combination of enabled servers (serial ports already emit
their own notice). The mechanism is reusable for future all-session messages.
File transfers over telnet no longer apply NVT CR-NUL stuffing, which
corrupted binary transfers through telnet↔serial bridges (e.g. tcpser) and
telnet-aware WiFi modems that don't symmetrically un-stuff. The shared
transfer I/O layer (tnio, used by XMODEM/YMODEM/ZMODEM/Kermit/Punter) now
escapes only IAC (0xFF → IAC IAC) and passes every other byte —
including CR (0x0D) — through literally, matching RFC 856 binary-transmission
semantics that 8-bit file transfer requires. CR-NUL stuffing (RFC 854 §2.2)
is a text-mode rule and was inserting/deleting 0x00 bytes around 0x0D,
which manifested as endless mid-transfer checksum failures and a hung peer
(a Commodore Punter sender, whose S/B wait loops are unbounded, would
strand). Validated against the genuine CCGMS Punter reference
(ccgmsterm/test/punter.c) in both directions, including through a
telnet-bridge emulation. IAC escaping (the I toggle) is unchanged.
GUI: external changes to the Kermit idle-timeout are no longer reverted on
save.kermit_idle_timeout was rendered and saved in the desktop config
editor but missing from its refresh-from-global and dirty-detection paths, so
a value changed via the web/telnet UI while the GUI was open could be silently
overwritten by the GUI's stale field on the next Save.
Serial modem mode now auto-reconnects when the device behind the port
disappears (e.g. a socat/USB-serial bridge that exits when its attached
terminal closes). Command-mode previously hit a hard I/O error and re-looped,
spamming the error ~twice/second forever with no recovery; it now logs the
outage once, backs off 1 s, and reopens the port automatically when the device
returns — matching console mode.
ATDT to a hostname now tries every resolved address. Dialing resolved
via to_socket_addrs() but only attempted the first address, so a host whose
DNS returns an unreachable IPv6 record first could fail with a silent NO CARRIER even when a working IPv4 address followed. It now attempts each
resolved address until one connects, and logs the failure reason instead of
failing silently.
Config save failures are now surfaced.write_config_file/save_config
return a Result; the explicit-save paths (desktop GUI Save buttons, telnet
reset-to-defaults) report a failure instead of always logging success.
Hand-edited serial_*_parity / serial_*_flowcontrol values are honored.
Both are now normalized (trim + lowercase) on read and apply, consistent with mode, so e.g. serial_a_parity = Even no longer silently reverts.
Config values round-trip without whitespace drift.sanitize_value now
trims surrounding whitespace (the reader already trimmed), and the dialup
number/host are sanitized on save so an embedded newline can't corrupt dialup.conf framing.
GUI waits for the X display before opening the console window, fixing the
headless drop when the gateway is started as a boot-time service before the
desktop session's X auth cookie is ready. The wait is adaptive (no delay on a
normal manual launch) and degrades safely when there is no display.
Kermit's async server/receive paths no longer stall a runtime worker —
blocking std::fs calls moved to tokio::fs and the directory listing
offloaded via spawn_blocking.
Security
SSH: warn when a pre-existing host/client private key is group- or
world-readable. New keys are written 0600; a key restored from a backup or
created by an older build could be more permissive. The gateway now logs a chmod 600 recommendation on load (warn-only — it does not refuse the key,
matching the trusted-LAN threat model).
ZMODEM: bound consecutive empty data subpackets (MAX_EMPTY_SUBPACKETS)
so a peer can't tar-pit the receive loop with CRC-valid zero-progress
subpackets.
Telnet: bound in-subnegotiation reads (SB_DRAIN_TIMEOUT) so a peer that
opens an IAC SB and then stalls can't pin the reader (slowloris); the outer
idle wait is unchanged.