README
¶
proxy
Overview
The proxy program acts as a multi‑user terminal server and
relay 📡, accepting incoming SSH client connections on the
front‑end (listeners 👂) and proxying these connections to one or
more TELNET servers on the back‑end (targets 🎯).
This project was originally developed to meet the needs of the BAN.AI Public Access Multics system and the DPS8M Simulator project, but may be useful to anyone who wants to offer SSH access to legacy systems.
Features
- ✅ SSH ⟷ TELNET gateway
- ✅ Full IPv6 support
- ✅ Access control whitelist/blacklist (by IP address or CIDR block)
- ✅ Independent console and session logging (by date/time and host)
- ✅ Automatic log‑file compression (using gzip, lzip, xz, or zstandard)
- ✅ Banners for accepted, denied, and blocked connections (configurable per target)
- ✅ Session connection monitoring and idle time tracking (with optional timeouts)
- ✅ Translation of SSH
window‑changeevents to TELNET NAWS messages - ✅ Interactive connection management for administrators
- ✅ User access to TELNET features (e.g., line BREAK, AYT) and statistics
- ✅ Transparent key remapping mode (translating movement keys to Emacs sequences)
- ✅ Optional support for management using
systemdon Linux (running in a sandbox) - ✅ Optional mDNS (Multicast DNS) DNS-SD service advertisements for listeners
- ✅ Link filtering
- ✅ Live streaming connection sharing (read‑only)
- 🤝 Allows users to share their session with one or more viewers
Installation
Binaries
- We currently publish more than 40 binaries supporting 13
operating systems (IBM AIX, IBM i, Android, Apple macOS, Dragonfly
BSD, FreeBSD, illumos, Linux, NetBSD, OpenBSD, Plan 9, Solaris, and
Microsoft Windows) on 14 hardware architectures.
- You can download pre-compiled binaries for all of these systems
(except IBM i) from
https://dps8m.gitlab.io/proxy/. - Look here if you need binaries for IBM i (OS/400) that run under the PASE subsystem.
- You can download pre-compiled binaries for all of these systems
(except IBM i) from
Source
A recent version of Go 🐹 is required to build
proxy from source code.
-
You can clone the
gitrepository 🌱 and build the source code usingmake:git clone https://gitlab.com/dps8m/proxy.git cd proxy make-
If you don’t have a (POSIX)
makeavailable for some reason, then building withgo buildis sufficient. -
The
.cross.shcross‑compilation helper script is provided (which can be called withmake cross) that attempts to buildproxybinaries for all supportedGOOSandGOARCHcombinations (except some specific Android builds, which are handled by.cross-android.shfor building the Android binaries that require the Android NDK).
-
-
You can also install this software using
go install📦:go install gitlab.com/dps8m/proxy@latest- Installations using
go installdownload the required sources, compile, and install the binary to${GOEXE}/proxy(which will be${HOME}/go/bin/proxyfor most users).
- Installations using
Usage
Invocation
- The
proxycommand can be invoked with the following command‑line arguments:
DPS8M Proxy v1.0.27 (2025-Dec-23 g5fdc6b2) [linux/amd64]
Usage for /home/jhj/dps8m-proxy/proxy:
--allow-root Allow running as root (UID 0)
--cert-dir <string> Directory containing SSH host certificates
(default: current working directory)
--cert-perm <octal> Permissions (octal) for new certificate files
[e.g., "600", "644"] (default 600)
--cert-rsa-bits <uint> RSA key size in bits for new certificates
["1024" to "4096"] (default 2048)
--cert-ecdsa-bits <uint> ECDSA key size in bits for new certificates
["256", "384", "521"] (default 256)
--ssh-addr <string> SSH listener address(es)
[e.g., ":2222", "[::1]:8000"]
(multiple allowed) (default ":2222")
--ssh-delay <float> Delay for incoming SSH connections
["0.0" to "30.0" seconds] (no default)
--no-banner Disable SSH connection banner
--telnet-host <string> Default TELNET target [host:port]
(default "127.0.0.1:6180")
--alt-host <string> Alternate TELNET target(s) [sshuser@host:port]
(multiple allowed)
--debug-telnet Debug TELNET option negotiation
--debug-server <string> Enable HTTP debug server listening address
[e.g., ":6060", "[::1]:6060"]
--no-sanitize Disable ASCII sanitization of error messages
(allowing non-ASCII error reports via SSH)
--gops Enable the "gops" diagnostic agent
(see https://github.com/google/gops)
--mdns Enable mDNS (Multicast DNS) advertisements
(i.e., Bonjour, Avahi announcements)
--keymap Enable Emacs keymapping mode by default
--log-dir <string> Base directory for logs (default "log")
--no-log Disable all session logging
(for console logging see "--console-log")
--no-console Disable the interactive admin console
--console-log <string> Enable console logging ["quiet", "noquiet"]
(disabled by default)
--compress-algo <string> Compression algorithm for log files
["gzip", "lzip", "xz", "zstd"]
(default "gzip")
--compress-level <string> Compression level for gzip, lzip, and zstd
algorithms ["fast", "normal", "high"]
(default "normal")
--no-compress Disable session and/or console log compression
--log-perm <octal> Permissions (octal) for new log files
[e.g., "600", "644"] (default 600)
--log-dir-perm <octal> Permissions (octal) for new log directories
[e.g., "755", "750"] (default 750)
--db-file <string> Path to persistent statistics storage database
(disabled by default)
--db-time <uint> Elapsed seconds between database updates
[0 disables periodic writes] (default 30)
--db-perm <octal> Permissions (octal) for new database files
[e.g., "600", "644"] (default 600)
--db-loglevel <string> Database engine (BBoltDB) logging output level
[level: "0" - "6", or "none" - "debug"]
(default "error")
--idle-max <uint> Maximum connection idle time allowed [seconds]
--time-max <uint> Maximum connection link time allowed [seconds]
--blacklist <string> Enable blacklist [filename] (no default)
--whitelist <string> Enable whitelist [filename] (no default)
--utc Use UTC (Coordinated Universal Time) for time
display and timestamping in log files
--license Show license terms and conditions
--version Show version information
--help Show this help and usage information
proxy home page (bug reports): <https://gitlab.com/dps8m/proxy/>
Most of these command‑line arguments are straightforward with usage that should be obvious, and those that require demystification are, hopefully, documented here:
-
Logging of sessions is enabled by default. Logging of console messages is disabled by default.
-
Console logging, if enabled, supports two modes:
quietandnoquiet. Inquietmode, all non‑fatal messages are logged only to the log file, where innoquietmode, messages are logged to both the console and the log file. -
By default, the local time zone is used for time display and writing log files. Users can specify the
‑‑utcoption to use UTC (Coordinated Universal Time) instead. Additionally, on Unix-like systems, theTZenvironment variable is respected. -
If the proxy fails to create log directories or files, a warning will be displayed on the console and the session and/or console logging feature may be (but is not guaranteed to be) disabled. In a future version, this behavior may be configurable (e.g., to allow either immediately or gracefully exiting on logging failures).
-
-
Enabling the database (with the
‑‑db-fileoption) persists to disk the connection statistics (viewable with thesadmin console command) so the stats are not lost when restarting the proxy. It is customary to use a name ending with the extensiondb(e.g.,proxy.db). -
The default TELNET target for
--telnet-hostis specified as ahost:portorpath(for connecting to a UNIX domain socket). Valid examples includehostname:23,1.2.3.4:2323,[2607:f8b0:4008:805::2000]:23,./socket, and/path/socket. -
All incoming SSH users are connected to the default TELNET target, unless their supplied SSH username matches an alternate target enabled with the
‑‑alt‑hostflag. The alt‑host syntax issshuser@host:portorsshuser@path, wheresshuseris the SSH username, and thehost:port(orpath, an absolute or relative path to a UNIX domain socket) is the TELNET target. -
All users connecting with SSH are shown a banner which includes details such as the date and time of the session, their IP address, and possibly a resolved host name. This can be disabled with
‑‑no‑banner. -
The
‑‑no‑bannercommand disables only those lines described above. It does not disable the file‑based banner content. These are the three primary text files which can be displayed to connecting SSH users:File Purpose block.txtDisplayed before disconnecting connections matching the blacklist deny.txtDisplayed when denying target sessions (e.g., during graceful shutdown) issue.txtDisplayed to users before their actual session with the target begins - When multiple targets are defined using the
‑‑alt‑hostfunctionality, the system will display a file that matches‑NAMEbefore the.txtextension. For example, if you have defined a target asoldunix@10.0.5.9:3333the proxy will look forblock‑oldunix.txt,deny‑oldunix.txt, andissue‑oldunix.txtfiles to serve to the connected user, before beginning their session with the target (via TELNET to10.0.5.9:3333). If any of the target‑specific text files do not exist, then the standard files will be served. - To disable the file‑based banner for specific targets only, you can create empty files using the naming scheme described above. You can also remove all of these files if you don’t want to use this functionality.
- When multiple targets are defined using the
-
You need to start
proxyusing the‑‑whitelistand/or‑‑blacklistargument to enable the access control functionality. If only the whitelist is enabled, then all connections will be denied by default. Note that if only the blacklist is enabled, it will be impossible to exempt individual IP addresses within a range that has been blocked. It is recommended that you enable both lists when using the access control features.-
The format of the whitelist and blacklist is an IPv4 or IPv6 address (e.g.,
23.215.0.138,2600:1406:bc00:53::b81e:94ce), or a CIDR block (e.g.,123.45.0.0/17which covers123.45.0.0to123.45.127.255, or2600:1408:ec00:36::/64covering2600:1408:ec00:36:0000:0000:0000:0000to2600:1408:ec00:36:ffff:ffff:ffff:ffff). -
The whitelist always takes precedence over the blacklist. If an address is allowed due to a whitelist match that would have otherwise been blocked by the blacklist, it is tagged as
EXEMPTEDin the logs.
-
-
The
‑‑versioncommand shows detailed version information, which includes the versions of any embedded dependencies as well as the name and version of the Go toolchain used to build the software:
DPS8M Proxy v1.0.27 (2025-Dec-23 g5fdc6b2) [linux/amd64]
+===========================+==================================+
| Component | Version |
+===========================+==================================+
| dps8m/proxy | v1.0.27 |
| arl/statsviz | v0.8.0 |
| google/gops | v0.3.29* (2025-May-14, ga2d8f77) |
| gorilla/websocket | v1.5.3 |
| hashicorp/mdns | v1.0.6 |
| klauspost/compress | v1.18.2 |
| miekg/dns | v1.1.69 |
| sorairolake/lzip-go | v0.3.8 |
| spf13/pflag | v1.0.11* (2025-Oct-07, g6fcfbc9) |
| ulikunitz/xz | v0.5.15 |
| go.etcd.io/bbolt | v1.4.3 |
| golang.org/x/crypto | v0.46.0 |
| golang.org/x/net | v0.48.0 |
| golang.org/x/sys | v0.39.0 |
| golang.org/x/term | v0.38.0 |
| kernel.org/.../libcap/cap | v1.2.77 |
| kernel.org/.../libcap/psx | v1.2.77 |
| Go compiler (gc) | v1.25.5 |
+===========================+==================================+
- If you need to see additional details about the
proxybinary, you can rungo version ‑m proxy.
Port binding
-
If you want to listen on the regular SSH port of 22 (without running as
root, which is strongly discouraged), on Linux systems you can usesetcapto allow the proxy to bind to privileged ports:sudo setcap 'cap_net_bind_service=+ep' "/path/to/proxy" -
If this is necessary (i.e., a non‑root user on Linux is attempting to bind an SSH listener to a privileged port and the
CAP_NET_BIND_SERVICEcapability is not currently effective), the software should provide a warning message with the above instructions. -
Note that some Android distributions restrict usage of ports below 8000.
Admin interaction
- The running proxy can be controlled interactively with the following admin console commands:
Most of these admin console commands are straightforward and should be self‑explanatory, although there are a few options that merit further clarification:
-
When the Graceful shutdown mode is active, all new connections are denied (and are served an appropriate
deny.txtbanner). Once all clients have disconnected, the proxy software will exit. Note that new monitoring sessions can still connect to observe active users, as these sessions are automatically closed when their observation target disconnects. -
When the Deny new connections mode is active, all new connections are denied (and are served an appropriate
deny.txtbanner). In addition, all logging of new connection attempts, including any denied and/or rejected connections, is suppressed. This can be useful when the logs or admin console are overwhelmed with activity (such as during bot attacks, busy periods, or when troubleshooting). Activating this mode can help reduce console noise, making it easier to perform admin actions such as viewing the configuration, or listing and killing active connections. -
The
kcommand, which kills a connection, takes either a Session ID as an argument (shown when listing active connections with thelcommand) or*, which kills all active connections.
If it is detected that you have a UTF-8 capable terminal, then some console output will be augmented with icons or emoji glyphs (and in a future version, UTF-8 box drawing symbols may be used for drawing tables).
Signals
-
The proxy also acts on the following signals (on systems where signals are supported):
Signal Action SIGINTEnables the Immediate shutdown mode SIGQUITEnables the Immediate shutdown mode SIGUSR1Enables the Graceful shutdown mode SIGUSR2Enables the Deny new connections mode SIGHUPReloads access control lists ( ‑‑whitelist,‑‑blacklist)SIGDANGERAttempts to immediately free as much memory as possible (AIX‑only)
Management with systemd
If you’re running the proxy on a Linux system, you can use systemd
to manage the service (while maintaining access to the interactive
admin console).
- The
systemdintegration requiressystemdversion 247 or later (Nov. 2020), and a recent version oftmux.
- With minor changes 🔧 to the unit file, this setup can also work
with
systemdas old as version 229 (Feb. 2016). - See the detailed instructions in the
systemd/dps8m‑proxy.servicefile for full installation instructions.
User interaction
Users connected via SSH can send ^] (i.e., Control + ]) during
their session to access the following following TELNET control
features:
-
]— sends a literalControl‑]to the target TELNET host -
0— sends a literalNULto the target TELNET host -
A— sends an IACAYT(Are You There?) to the target TELNET host -
B— sends an IACBREAKsignal to the target TELNET host -
I— sends an IACINTERRUPTsignal to the target TELNET host -
K— toggles the transparent key remapping mode, which translates modernxterm/VT320movement key inputs to Emacs sequences:Input Output Control + UpEscape, [Control + DownEscape, ]Control + RightEscape, fControl + LeftEscape, bHomeControl + ADeleteControl + DEndControl + EUpEscape + vDownControl + VUpControl + PDownControl + NRightControl + FLeftControl + B -
N— sends an IACNOP(No Operation) to the target TELNET host -
S— displays the status of the session, sharing information, and some statistics:>> LNK ‑ The username '_gRSyWHxPcMp2MWvtmWWF' can be used to share this session. >> SSH ‑ in: 58 B, out: 4.82 KiB, in rate: 4 B/s, out rate: 381 B/s >> NVT ‑ in: 4.82 KiB, out: 57 B, in rate: 381 B/s, out rate: 4 B/s >> LNK ‑ link time: 13s (Emacs keymap enabled) -
X— disconnects from the target TELNET host (and ends the SSH session)
Connection sharing
-
The user can share 🤝 the username presented above with others, allowing the session to be viewed live 👀 (read‑only) by one or more viewers:
$ ssh _gRSyWHxPcMp2MWvtmWWF@proxybox CONNECTION from remote.com [18.17.16.15] started at 2025/07/15 08:22:55. This is a READ‑ONLY shared monitoring session. Send Control‑] to disconnect.
Compressed logs
-
By default, all session log files are compressed 🗜️ automatically when the session terminates, and console log files are compressed when the log rolls over (i.e., when starting a new day).
-
When reviewing logs, administrators often need to search through all the past data, including through the compressed files. We recommend using
ripgrep(with the‑zoption) for this task.
Using OpenSSH host keys
If you have existing OpenSSH Ed25519, RSA, or ECDSA host keys that you want to use with the proxy, you’ll first need to convert those keys to standard PEM format.
🚨 NB: These instructions do not include any specific details
for safe handling of key file permissions—we assume you are root
and that you know what you’re doing!
-
Make a copy of the key files you wish to convert. Be aware that these copies will be overwritten in the conversion process:
cp /etc/ssh/ssh_host_ed25519_key ssh_host_ed25519_key.tmp cp /etc/ssh/ssh_host_rsa_key ssh_host_rsa_key.tmp cp /etc/ssh/ssh_host_ecdsa_key ssh_host_ecdsa_key.tmp -
Convert the keys (using
ssh‑keygen) and rename them appropriately:ssh-keygen -p -m PEM -N '' -P '' -f ssh_host_ed25519_key.tmp mv ssh_host_ed25519_key.tmp ssh_host_ed25519_key.pem ssh-keygen -p -m PEM -N '' -P '' -f ssh_host_rsa_key.tmp mv ssh_host_rsa_key.tmp ssh_host_rsa_key.pem ssh-keygen -p -m PEM -N '' -P '' -f ssh_host_ecdsa_key.tmp mv ssh_host_ecdsa_key.tmp ssh_host_ecdsa_key.pem
History
This is a from‑scratch re‑implementation (in Go 🐹) of an older legacy program of the same name.
The original software used a multi‑process architecture and consisted
of nearly 15,000 lines of haphazardly constructed code: ≅14,000
lines of mostly C‑Kermit 🐸 (yes,
the
programming language)
and ksh93 🐚 (along with some C 💻,
Python 🐍, and Perl 🐪) which was difficult to maintain, configure,
and securely install.
This new implementation uses many lightweight Goroutines 🚀 instead of spawning multiple processes, resulting in significantly improved performance and reduced system overhead.
Code statistics
The new proxy program is considerably simpler than its legacy
predecessor (code statistics 📈 provided by
scc):
| Language | Files | Lines | Blank | Comment | Code | Complexity | Bytes | Uloc |
|---|---|---|---|---|---|---|---|---|
| Go | 20 | 8844 | 1941 | 582 | 6321 | 1550 | 212365 | 3847 |
| Shell | 4 | 436 | 99 | 112 | 225 | 34 | 12876 | 207 |
| Makefile | 1 | 537 | 83 | 92 | 362 | 154 | 18514 | 324 |
| Markdown | 1 | 583 | 109 | 0 | 474 | 0 | 27472 | 459 |
| Systemd | 1 | 209 | 35 | 107 | 67 | 0 | 7595 | 135 |
| YAML | 1 | 84 | 6 | 10 | 68 | 0 | 3961 | 75 |
| Total | 28 | 10693 | 2273 | 903 | 7517 | 1738 | 282783 | 5029 |
Future plans
- Some features of the legacy software are still missing in this implementation and may be added in future updates. These features include text CAPTCHAs, load‑balancing, fail‑over, flow control, SSH targets, and TELNET listeners.
- When users access an SSH listener, the connecting client may supply a password or present public keys for authentication. These authentication attempts are currently logged, but are not otherwise used by the proxy. A future update may allow for passwords and public keys to be used for pre‑authentication or to influence target routing.
- While TELNET protocol support will improve in the future, there are
no plans to support the
linemode,
environment,
authentication,
or encryption
features at this time.
- If you need these features, you should look into C‑Kermit or Kermit 95.
- Although directly executing programs isn’t something on the
roadmap, it’s not difficult to use
socatcreatively to connect C‑Kermit to the proxy using a UNIX domain socket (i.e.,socat UNIX‑LISTEN:socket,fork,reuseaddr EXEC:kermit,pty,setsid,echo=0,rawer,opost=1,icrnl=1,onlcr,cread). - ⚠️ Be aware that doing this securely—safe for public usage—is more involved than one might imagine. Safely configuring the proxy for this type of operation is possible, but beyond the scope of this documentation.
Development
Required
- For
proxydevelopment, along with the most recent version of Go, you’ll also need to have a standard POSIX.1 shell environment (at a minimumsh,make,diff,grep,awk, &sed), andreuse,staticcheck,gopls,revive,errcheck,gofumpt,govulncheck, NilAway,scc,scspell,codespell, and Perl. - If you plan to make any changes to the
Makefile(or.cross.shand other scripts), you’ll need to have the ShellCheck andshfmtlinters available. - Additionally, all modifications to the
Makefileand.cross.shand other scripts must be tested againstpdpmake(withPDPMAKE_POSIXLY_CORRECTset) andyashto ensure POSIX conformance. - The
Makefileprovides alintconvenience target to help you run all this. You can also examine our.gitlab-ci.ymlfile. There is also a convenience script,.lintsetup.sh, to help install the Go-based linters, and the Python-based linters can be installed viapip(i.e.,pip install --upgrade scspell3k codespell reuse).
Recommended
- Source code tags are generated using
gogtagsandgotags(oruniversal-ctags) if installed. - While not absolutely required, it’s a good idea to have the latest
golangci-lint(v2) installed. We ship a config file file for it, and try to make sure that all the tests pass when using the most recently released version. - It’s also recommended to (manually) use
hunspellfor spell checking—in addition to usingcodespellandscspell.
Security
- The canonical home of this software is
https://gitlab.com/dps8m/proxy, with a mirror on GitHub. - This software is intended to be secure 🛡️.
- If you find any security‑related problems, please don’t hesitate to open a GitLab Issue (or send an email to the author).
Licenses
- The
proxyprogram is made available under the terms of the MIT License. - Some bundled example and miscellaneous files distributed under the terms of the MIT No Attribution License.
- All direct and indirect dependencies are licensed under permissive open-source licenses: