Skip to content

feat: --proxy and --source-addr on every connecting command; go-s7comm v0.15.0 - #23

Merged
boeboe merged 3 commits into
mainfrom
feat/dial-flags
Nov 9, 2026
Merged

boeboe merged 3 commits into
mainfrom
feat/dial-flags

Conversation

@boeboe

@boeboe boeboe commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Two flags on every command that connects to a PLC set how the TCP connection is opened:

  • --proxy socks5://host:port (S7COMMCTL_PROXY) connects through a SOCKS5 proxy, for example an SSH jump host into a plant network (ssh -D 1080 jumphost). It speaks RFC 1928 CONNECT with no authentication. socks5h:// sends host names to the proxy to resolve.
  • --source-addr <ip> (S7COMMCTL_SOURCE_ADDR) connects from that local address, on hosts with separate OT and office interfaces.

They apply to identify, read, write, list, block, blocks, diagnostic, fingerprint, scan, record, probe rackslot, compare read, profile and discover. They use go-s7comm v0.15.0: client.WithDialer (otfabric/go-s7comm#27), the scan helpers' dialer (otfabric/go-s7comm#28), and CompareRead bounding its read by Timeout (otfabric/go-s7comm#29).

Breaking (v0 minor): compare read --timeout now bounds each candidate's read too. A PLC that goes silent after setup used to hang the command; it now times out, and so does a PLC that answers more slowly than --timeout. RELEASE.md ## v0.7.0 (previous release v0.6.1) lists this under Breaking changes.

Behaviour

  • An invalid value is a usage error (exit 2) before anything is sent: another scheme, proxy credentials, a missing or invalid port, a path, or a --source-addr that is not an IP.
  • A proxy that refuses is a connection failure. The error names the proxy and its reply, for example socks5: connection not allowed by ruleset.
  • The SOCKS5 handshake honours the command's timeout and Ctrl-C.
  • No new dependency: the SOCKS5 client in internal/app/netdial is about 150 lines.

Not changed: the exposure warning

During the review I suggested moving the public-IP warning into the dialer, so it would check the resolved address. That is unnecessary: --ip accepts only an IP literal (validate.ValidateIP), so the typed value already is the resolved address. Through a proxy the warning still checks the PLC's address. SECURITY.md says so.

Tests

  • internal/app/netdial (99.0%): flag validation; source-address binding; CONNECT for IPv4, IPv6 and host names (socks5 resolves locally, socks5h sends the name); every reply code and handshake failure (scripted over net.Pipe); a silent proxy against the deadline; cancel
  • internal/socks5test (97.8%): the scripted test proxy itself
  • cmd/s7commctl:
    • identify, probe rackslot, compare read and profile through the proxy to the fake PLC
    • discover end to end with the real client.Discover: the proxy forwards every CONNECT to 192.0.2.0/30:102 to the fake PLC. Before, the tests had to replace client.Discover.
    • a refusing proxy; --source-addr; invalid values as usage errors on every command family
  • internal/app/config: env variables and the options and probe request that the flags produce

make check (per-package coverage floor 95%, docs-check) and make e2e green.

Docs

README (new "Reaching the PLC: proxy and source address" section and env list), docs/commands.md (both flags on all 16 connecting commands), SECURITY.md, RELEASE.md v0.7.0.

Codecov patch gap: in netdial, the check of ctx after the SOCKS5 handshake now runs on every successful handshake. Before, it sat in a branch that only a timing race reached. The behaviour is the same: a ctx that ends as the handshake finishes fails the dial.

🤖 Generated with Claude Code

…m v0.14.0

--proxy socks5://host:port (S7COMMCTL_PROXY) connects through a SOCKS5
proxy (RFC 1928 CONNECT, no authentication; socks5h:// resolves names at
the proxy), for example an SSH jump host into a plant network.
--source-addr <ip> (S7COMMCTL_SOURCE_ADDR) binds the local address on
multi-homed hosts. Both go through client.WithDialer and, with
go-s7comm v0.14.0, the dialer of Discover, ProbeRackSlots and
CompareRead, so discover, probe rackslot, compare read and profile are
covered too. Invalid values are usage errors (exit 2); a refusing proxy
is a connection failure naming the proxy and its reply.

internal/app/netdial holds the dialer and a hand-written SOCKS5 client
(no new dependency); internal/socks5test a scripted proxy for tests.
discover is now tested end to end with the real client.Discover: the
test proxy forwards every CONNECT to the fake PLC.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

- go-s7comm v0.15.0: compare read's --timeout now bounds the read too
  (listed under Breaking changes; README notes it).
- netdial: after the SOCKS5 handshake, check ctx on every success instead
  of in a branch only a timing race reached (Codecov patch gap).
- RELEASE.md v0.7.0: previous release is v0.6.1 (v0.6.2 never existed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@boeboe boeboe changed the title feat: --proxy and --source-addr on every connecting command; go-s7comm v0.14.0 feat: --proxy and --source-addr on every connecting command; go-s7comm v0.15.0 Nov 9, 2026
Closes the last Codecov patch gaps on #23: Start's listen failure is tested
through a replaceable listen and a recording testing.TB, and a failed reply
write no longer needs its own branch (the copies end on the broken conn).
socks5test and netdial are at 100 %.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@boeboe
boeboe merged commit 650e503 into main Nov 9, 2026
22 checks passed
@boeboe
boeboe deleted the feat/dial-flags branch November 9, 2026 22:04
boeboe added a commit to otfabric/modbusctl that referenced this pull request Nov 9, 2026
…s v1.14.2 (#32)

* feat: --proxy and --source-addr on every connecting command; go-modbus v1.14.1

Every client command (sunspec included) and discover take --proxy
(socks5:// or socks5h://, RFC 1928 CONNECT, no auth) and --source-addr,
set as modbus.Config.Dialer. --proxy applies to tcp, tcp+tls, rtuovertcp
and asciiovertcp; with udp or rtuoverudp it is invalid input, as is either
flag with rtu:// or ascii://, and discover --resolve-mac with --proxy.
--source-addr binds a UDP address for udp transports. TLS verification
stays against the URL host.

The SOCKS5 dialer and the scripted test proxy are ported from
otfabric/s7commctl#23.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: go-modbus v1.14.2

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant