Skip to content

SessionKey handshake for V1-initial S7-1200 PLCs - #775

Open
gijzelaerr wants to merge 26 commits into
masterfrom
fix-tls-layering-v2
Open

SessionKey handshake for V1-initial S7-1200 PLCs#775
gijzelaerr wants to merge 26 commits into
masterfrom
fix-tls-layering-v2

Conversation

@gijzelaerr

@gijzelaerr gijzelaerr commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Replaces #761, updated onto the s7commplus/ package split (#774).

Summary

Adds the full session_auth package for V1-initial S7-1200 PLCs that require a SessionKey handshake before data operations:

  • s7commplus/session_auth/: public-key-based session authentication (Family 0 key derivation, fingerprint matching, SecurityKeyEncryptedKey blob generation, legitimation challenge solver, HMAC key derivation)
  • V3 HMAC framing for post-auth data operations
  • TIA-style CreateObject attributes required by V1-initial firmware
  • PAOM string stripping in ServerSessionVersion echo
  • Post-auth legitimation flow (challenge read + solved blob write)
  • Connection.connect(password=) parameter for auth flow
  • Compatibility gating so TLS/V2 sessions do not enter the V1 SessionKey handshake
  • Correct standard GetVarSubStreamed/SetVariable legitimation framing and IntegrityId placement

Test plan

Known limitations

Rebased onto the s7commplus/ package split (#774). All session_auth
code now lives under s7commplus/session_auth/ instead of s7/session_auth/.

Adds the full session_auth package for V1-initial S7-1200 PLCs that
require a SessionKey handshake before data operations:

- s7commplus/session_auth/: Public-key-based session authentication
  (Family 0 key derivation, fingerprint matching, SecurityKeyEncryptedKey
  blob generation, legitimation challenge solver, HMAC key derivation)
- V3 HMAC framing for post-auth data operations
- TIA-style CreateObject attributes (required by V1-initial firmware)
- PAOM string stripping in ServerSessionVersion echo
- Post-auth legitimation flow (challenge read + solved blob write)
- Connection.connect(password=) parameter for auth flow

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
gijzelaerr and others added 3 commits July 16, 2026 19:49
…ation

- Add type annotations for hashmod parameters in HKDF helpers
- Fix frame_version type annotation (int vs ProtocolVersion)
- Remove duplicated _skip_typed_value type-dispatch block (dead code after return)
- Guard auth_result unpacking with explicit None check for mypy
- Add None guards for session_key and challenge in _post_auth_legitimation
- Add assert/guard for Optional connection in substreamed client methods

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Analysis of TIA Portal V19 pcap captures (xBiggs, GH-710) revealed the
root cause of the ConnectionReset after SessionKey auth:

TIA Portal's post-SetupSession sequence:
  1. SET_VARIABLE (0x04F2) — write attribute 323 on session
  2. GET_VAR_SUBSTREAMED — read from InObj=50, addr 7920 (finalize)
  3. Data reads work immediately (no password needed)
  4. Legitimation (addr 303/1846) happens later, optionally

Our code was skipping steps 1-2 and jumping straight to legitimation
(read challenge → write blob → finalize), which caused the PLC to
return V254 SYSTEM_EVENT error 0xE9 and RST subsequent requests.

Changes:
- Add _session_activate() that mirrors TIA Portal's activation:
  SET_VARIABLE attr 323 + GET_VAR_SUBSTREAMED InObj=50 addr 7920
- Only call _post_auth_legitimation() when a password is provided
  (previously ran unconditionally, even with empty password)
- Reorder: activate first, then legitimation (if password given)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace duplicate _skip_typed_value (73 lines) with codec.skip_typed_value
  import; fixes DWORD bug (was VLQ instead of fixed 4 bytes)
- Initialize _session_auth_public_key/_session_auth_family in __init__
  instead of using hasattr() guards
- Remove redundant lazy imports (DataType, LegitimationId already at
  top level)
- Make SessionKey and TLS auth mutually exclusive in client.connect()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@gijzelaerr
gijzelaerr force-pushed the fix-tls-layering-v2 branch 2 times, most recently from 3a5660f to 6a3b019 Compare July 16, 2026 18:15
gijzelaerr and others added 2 commits July 16, 2026 20:33
Move the 95K+ lines of machine-transpiled monolith transforms, constant
tables, and binary data into family0/_generated/ to clearly separate
opaque generated code from the human-readable orchestration layer.

Add ARCHITECTURE.md documenting:
- When SessionKey auth is needed (V1-initial vs TLS)
- The full authentication flow (diagram)
- Module map: which file does what
- How the 180-byte SecurityKeyEncryptedKey blob is built
- References to upstream HarpoS7, Black Hat papers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Data2Collection table 1 had 2111 entries (4222 bytes) but its 33
operations require 2112 entries (4224 bytes). The missing final word
0x4C28 caused IndexError during session key renewal when a challenge
selected the last lookup block.

Backported from HarpoS7 commit 22b9dc0 (bonk-dev/HarpoS7#24).

Adds:
- The missing 0x4C28 entry in fp_data2.bin
- Lookup table size invariant test
- Regression test with captured renewal challenge 5B15B469...

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
gijzelaerr and others added 2 commits July 16, 2026 21:14
Analysis of all 4 TIA Portal pcaps (GH-710 + GH-728) shows TIA
consistently sends 4 requests after SetupSession, not 2:

1. SET_VARIABLE attr 323 = USINT(5)
2. GET_VAR_SUBSTREAMED InObj=50, addr 7920
3. GET_VAR_SUBSTREAMED InObj=session, addr 1842
4. GET_VAR_SUBSTREAMED InObj=50, addr 7920 (again)

Previously we only did steps 1-2. Add steps 3-4 and extract a
_build_get_var_substreamed helper to reduce boilerplate in both
_session_activate and _post_auth_legitimation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move _parse_create_object_response (120 lines, connection.py) into
  codec.py as parse_create_object_attributes — a reusable function that
  returns ServerSessionVersion, public key fingerprint, and session
  challenge in a CreateObjectAttributes dataclass. Both sync connection
  and async_client can use it.
- Keep parse_server_session_version as a thin wrapper for backward compat.
- Fix: add session_auth/**/*.bin to pyproject.toml package-data so
  binary data files (fp_data, transform12 metadata) ship in wheels.

Net: -69 lines.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@bonk-dev

Copy link
Copy Markdown

Hi, I'm the creator of HarpoS7. I see that you're porting it to Python which is awesome! However, could you please include the original license text somewhere in your repository?: LICENSE-HarpoS7

@gijzelaerr

Copy link
Copy Markdown
Owner Author

hey! thanks for making HarpoS7!

But i don't fully understand your request. the licenses we put on our software is the same, it's both MIT right?

https://github.com/gijzelaerr/python-snap7/blob/master/LICENSE

Or are you talking about credits? I think the parts that are ported from HaroS7 should indicate it comes from your source code, but i happily do another iteration and check credit is given where applicable. I'll aso add you to the credits file/section.

Also, i'm not 100% convinced yet that the SessionKey handshake for V1 s7commplus should land in this package, since it is very reverse-engineery with tons of binary blobs and magic. I've been trying to polish it, but there is still quite some magic. Maybe my cleanups can be helpful on your side again :)

Let me know if i can help you out in another way.

gijzelaerr and others added 14 commits July 17, 2026 11:38
The session_auth package is a Python port of HarpoS7 by bonk-dev.
Include the original MIT license text as requested in #775, and add
credits in the changelog and docs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GH-710)

The 4-step activation sequence (SET_VARIABLE attr 323 + finalize reads)
was modeled after TIA Portal pcaps but is TIA-specific behavior — the
HarpoS7 reference implementation skips it entirely and goes straight
from SetupSession to data operations with V3+HMAC framing.

V1-initial PLCs (FW < 4.5) RST the connection when they receive the
SET_VARIABLE request, and the finalize read to addr 7920 in
_post_auth_legitimation caused V254 SYSTEM_EVENT error 0xE9.

Remove both and align with HarpoS7's proven flow: SetupSession →
optional legitimation (read 303, write 1846) → data ops.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…-710)

Real S7-1200 testing by @xBiggs showed that the PLC requires the
legitimation handshake after SetupSession even when no password is
configured. Without it, db_read fails with access-denied error
0xE03388000081FFE0. The legitimation solver already handles an empty
password correctly (SHA-1 of ""), so the conditional guard was simply
wrong.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…H-710)

The GET_VAR_SUBSTREAMED response for the legitimation challenge (addr
303) has the format: UInt64Vlq ReturnValue | byte | PValue(datatype +
count_vlq + length_vlq + data) | UInt32Vlq IntegrityId. The code was
skipping only the 1-byte return value VLQ, then taking the next 20
bytes as the challenge — but those first 4 bytes (00 10 02 14) are
BLOB metadata (unknown byte, datatype tag, count, length). The actual
challenge starts 4 bytes later.

This fed wrong input to the legitimation solver, producing a wrong
DEADBEEF blob that the PLC correctly rejected (ReturnValue
0xE605CC0004C6FEA1 = negative = error). The code ignored this error
and logged "completed", then all subsequent data reads failed with
V254 SystemEvent error 0xE9.

Fix both issues:
- Parse the PValue BLOB header properly to extract the challenge at
  the correct offset (matching thomas-v2 GetVarSubstreamedResponse)
- Check the SET_VAR_SUBSTREAMED response return value and raise on
  negative (= legitimation rejected)

Root-caused from @xBiggs's real S7-1200 (6ES7 215-1BG40-0XB0, FW
V4.2) debug traces.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
V1-initial PLCs accept GET_VAR_SUBSTREAMED (0x0586) for session-level
operations (legitimation challenge/response), but reject it for DB
data reads with V254 SystemEvent error 0xE9. This matches the
thomas-v2 S7CommPlusDriver which uses GetMultiVariables (0x054C) for
all data reads, even over TLS/V2.

Change requires_substreamed to return False so data operations route
through GetMultiVariables. V3+HMAC framing is controlled separately
by session_key presence in send_request, so HMAC signing is preserved.

Tested by @xBiggs on S7-1200 (6ES7 215-1BG40-0XB0, FW V4.2) —
legitimation now succeeds (positive return value) after the challenge
extraction fix in 1c0cae5, but db_read still hit V254/0xE9 because
GET_VAR_SUBSTREAMED was used for the data read.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…hange (GH-710)

xBiggs's TIA Portal pcap (TIAPortalWatchDB7.pcapng) proves that TIA sends
SET_VARIABLE writing USINT(5) to address 323 immediately after the V2 key
exchange, BEFORE any data reads or legitimation. Without this step, data
reads fail with 0xE9.

Previous attempt (432d9c6) bundled this with 3 extra GET_VAR_SUBSTREAMED
reads (addr 7920, 1842) that are NOT in TIA's flow at that point. The
combined 4-step sequence caused RST and was removed in e73f915. This
version sends only the SET_VARIABLE, matching pcap frame 17 exactly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…change (GH-710)

xBiggs's test of a4be9de showed the SET_VARIABLE at addr 323 returning
V254/0xE9. Byte-by-byte comparison with the TIA Portal pcap revealed
two differences:

1. IntegrityId not included — _with_integrity_id was never set to True
   for V1-initial PLCs because the V2 post-setup code path doesn't run.
   TIA's frame 17 has IntegrityId=4, ours had none.

2. Transport flags 0x36 instead of 0x34 — TIA uses 0x34 for ALL requests
   after the key exchange (SET_VARIABLE, GET_VAR_SUBSTREAMED, etc.).
   Our code only used 0x34 for GET_MULTI_VARIABLES and EXPLORE.

Fix: enable IntegrityId tracking immediately when the SecurityKey blob
is accepted, and use 0x34 transport flags for all requests when a
session key is active.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The IntegrityId insertion and counter increment were gated behind
_protocol_version >= V2, which prevented V1 PLCs that complete
SessionKey authentication from receiving the IntegrityId in their
request frames. The _with_integrity_id flag alone is sufficient
since it's only set when IntegrityId is actually required.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
V1-initial PLCs reject SET_VARIABLE addr 323 with error 0xE9 in a
V254 SystemEvent — the session activation is TIA-specific behavior
that doesn't apply to V1 PLCs (documented in ARCHITECTURE.md but
contradicted by the connect flow comments).

After SetupSession with the SecurityKeyEncryptedKey blob, V1+SessionKey
PLCs are ready for data operations immediately with V3+HMAC framing.
The legitimation handshake is only needed when a password is provided.

Confirmed by xBiggs's trace: the V254 response to SET_VARIABLE
contained ReturnValue=0xE9, followed by PLC RST on the next request.
…-710)

V1+SessionKey PLCs accept GET_VAR_SUBSTREAMED for session-level ops
(legitimation) but return errors for DB data reads. This matches the
thomas-v2 S7CommPlusDriver which uses GET_MULTI_VARIABLES for all data
reads regardless of protocol version. V3+HMAC framing is controlled
separately by session_key presence in send_request.
…H-710)

Real PLCs prepend an IntegrityId VLQ to the response payload when
integrity tracking is active. Strip it in send_request so callers
see only the application-level data (ReturnValue + items + errors).

Also format error codes as hex in _parse_read_response for easier
debugging against S7CommPlus protocol documentation.
Byte-for-byte comparison with TIA Portal pcap (frame 17) revealed
two issues in the ObjectQualifier that caused the 0xE9 rejection:

1. KEY_QUALIFIER value was VLQ(0) (1 byte) — must be uint32
   containing the frame sequence number (4 bytes)
2. OQ terminator byte was redundant — the IntegrityId takes its
   position when send_request splices it with integrity_tail=3

Session activation (SetMultiVariables addr 323) and post-auth
legitimation (GetVarSubstreamed) are restored in the connect flow.
# Conflicts:
#	pyproject.toml
… RST-happy firmware

browse() does an EXPLORE (list_datablocks) then a symbolic GetMultiVariables
read (_read_typeinfo_rid) then another EXPLORE (type-info container). On
firmware that sends a TCP RST after the first symbolic read per connection
(e.g. S7-1200 FW V4.1 — the behaviour already documented in read_symbolic's
docstring), the final EXPLORE runs on a dead socket and browse() raises
S7ConnectionError("Not connected").

Add a lazy reconnect-and-retry: connect() now stores its arguments, and a new
_with_reconnect() helper retries an operation once on a fresh session if the
PLC dropped the socket. browse() wraps the per-DB reads and the container
EXPLORE with it; _read_typeinfo_rid propagates S7ConnectionError so the retry
can act. Well-behaved firmware never triggers the retry (the first call
succeeds), so there is no behaviour change there.

Also downgrade the misleading "CreateObject returned error ... PLC may require
TLS" warning to debug when TLS is already active: this PLC returns a non-zero
CreateObject value on a fully functional TLS session.

Validated live on an S7-1200 FW V4.1 over TLS: browse() now returns the full
I/Q/M symbol tree with correct types (input_1 BOOL, mtag_byte BYTE %MB100,
mtag_word WORD %MW102, ...). Tests: 107 passed, ruff + format clean.

Refs #793, #775
…connect

Add `rst_after_symbolic_read` flag to S7CommPlusServer that closes the
TCP connection after responding to GetMultiVariables — matching the
behaviour of S7-1200 FW V4.1 that RSTs after a symbolic read.

This lets the reconnect logic from PR #801 be exercised without real
hardware.
@tommasofaedo

Copy link
Copy Markdown
Contributor

Hardware validation — TLS path on real S7-1200 (re #793)

I validated the TLS layering on this branch against real TLS-required hardware: a Siemens S7-1200, FW V4.1, with secure PG/PC communication enabled (so TLS is mandatory, not optional). This covers the "validate against real hardware" ask in #793.

What works end-to-end (validated live):

  • TLS 1.3 handshake negotiated: TLS_AES_256_GCM_SHA384.

  • OMS keying material captured (32 bytes) via the branch's TLS keylog hook — this is the piece CPython's SSLObject can't provide through export_keying_material, so the keylog approach on this branch is what makes it work.

  • Data framing ProtocolVersion.V2 over TLS, accepted by the PLC.

  • get_cpu_state()RUN, stable across repeated ops on one connection (the PLC does not drop after the first op).

  • list_datablocks() and _explore_type_info_container() (large-PDU reassembly) both succeed.

  • Symbolic browse of I/Q/M returns the full tree with correct, direct types:

    IArea.input_1    BOOL
    QArea.output_1   BOOL
    MArea.merker_1   BOOL
    MArea.mtag_byte  BYTE   %MB100
    MArea.mtag_word  WORD   %MW102
    MArea.mtag_dword DWORD  %MD104
    MArea.mtag_bool  BOOL
    

    Types and addresses cross-checked against TIA Portal.

One issue found and fixed (already merged here as #801): browse() did a symbolic GetMultiVariables read between two EXPLOREs, and on this RST-happy firmware the final EXPLORE ran on a dead socket → S7ConnectionError('Not connected'). The lazy reconnect-and-retry in #801 resolves it; re-validated live, browse now returns the complete tree.

Minor notes (not blockers): the CreateObject returned … PLC may require TLS warning fires on a fully healthy TLS session (CreateObject returns a non-zero value here) — #801 downgrades it to debug when TLS is already active; and db_read(25,0,2) on an optimized DB fails as expected (optimized DBs require symbolic access, not offset-based).

Net: the TLS transport on this branch is solid on real V4.1 hardware over TLS. 👍

@bvanelli

bvanelli commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

As it stands, this PR breaks compatibility with previously working PLCs from master (after merging master into this branch).

Here is the behaviour on master on a variable browse:

INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
WARNING s7commplus.connection: CreateObject returned error 0x60228000012D0011 — PLC may require TLS (use_tls=True)
INFO s7commplus.connection: ServerSessionVersion captured: 88 bytes
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052252, tls=True
INFO __main__: Connected to 192.168.101.51: protocol=V2 session=0x70000FDC session_setup_ok=True tls_active=True
Found 207851 variables

Here is the behaviour on this branch:

Stacktrace
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 493f6ba407b0715366c0ee17648178e10c6512ca
INFO s7commplus.connection: SessionKey auth blob generated (180 bytes)
INFO s7commplus.connection: SecurityKey blob included in session setup, IntegrityId tracking enabled
INFO snap7.connection: Disconnected from 192.168.101.51:102
Traceback (most recent call last):
  File "/Users/brunno.vanelli/Documents/git/python-snap7/s7commplus/connection.py", line 1431, in _recv_s7_data
    return self._ssl_object.read(65536)  # type: ignore[union-attr]
           ~~~~~~~~~~~~~~~~~~~~~^^^^^^^
  File "/Users/brunno.vanelli/.local/share/uv/python/cpython-3.14.5-macos-aarch64-none/lib/python3.14/ssl.py", line 880, in read
    v = self._sslobj.read(len)
ssl.SSLWantReadError: The operation did not complete (read) (_ssl.c:2711)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/brunno.vanelli/Documents/git/python-snap7/snap7/connection.py", line 466, in _recv_exact
    chunk = self.socket.recv(size - len(data))
ConnectionResetError: [Errno 54] Connection reset by peer

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/brunno.vanelli/Documents/git/python-snap7/example/s7commplus1_browse_sync.py", line 28, in <module>
    client.connect(host, use_tls=True)
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
  File "/Users/brunno.vanelli/Documents/git/python-snap7/s7commplus/client.py", line 106, in connect
    self._open_connection()
    ~~~~~~~~~~~~~~~~~~~~~^^
  File "/Users/brunno.vanelli/Documents/git/python-snap7/s7commplus/client.py", line 114, in _open_connection
    self._connection.connect(
    ~~~~~~~~~~~~~~~~~~~~~~~~^
        use_tls=p["use_tls"],
        ^^^^^^^^^^^^^^^^^^^^^
    ...<3 lines>...
        password=p["password"] or "",
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/Users/brunno.vanelli/Documents/git/python-snap7/s7commplus/connection.py", line 314, in connect
    self._session_setup_ok = self._setup_session()
                             ~~~~~~~~~~~~~~~~~~~^^
  File "/Users/brunno.vanelli/Documents/git/python-snap7/s7commplus/connection.py", line 1163, in _setup_session
    response_frame = self._recv_s7_data()
  File "/Users/brunno.vanelli/Documents/git/python-snap7/s7commplus/connection.py", line 1433, in _recv_s7_data
    self._tls_read_incoming()
    ~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/Users/brunno.vanelli/Documents/git/python-snap7/s7commplus/connection.py", line 1445, in _tls_read_incoming
    data = self._iso_conn.receive_data()
  File "/Users/brunno.vanelli/Documents/git/python-snap7/snap7/connection.py", line 204, in receive_data
    tpkt_header = self._recv_exact(4)
  File "/Users/brunno.vanelli/Documents/git/python-snap7/snap7/connection.py", line 476, in _recv_exact
    raise S7ConnectionError(f"Receive error: {e}")
snap7.error.S7ConnectionError: Receive error: [Errno 54] Connection reset by peer

python
INFO snap7.connection: Connected to 192.168.101.53:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 8c9b2b5c8c2e58e9a8a999ff29dee042e90cff64
INFO s7commplus.connection: SessionKey auth blob generated (180 bytes)
INFO s7commplus.connection: SecurityKey blob included in session setup, IntegrityId tracking enabled
INFO snap7.connection: Disconnected from 192.168.101.53:102
Traceback (most recent call last):
  File "/Users/brunno.vanelli/Documents/git/python-snap7/s7commplus/connection.py", line 1431, in _recv_s7_data
    return self._ssl_object.read(65536)  # type: ignore[union-attr]
           ~~~~~~~~~~~~~~~~~~~~~^^^^^^^
  File "/Users/brunno.vanelli/.local/share/uv/python/cpython-3.14.5-macos-aarch64-none/lib/python3.14/ssl.py", line 880, in read
    v = self._sslobj.read(len)
ssl.SSLWantReadError: The operation did not complete (read) (_ssl.c:2711)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/brunno.vanelli/Documents/git/python-snap7/snap7/connection.py", line 466, in _recv_exact
    chunk = self.socket.recv(size - len(data))
ConnectionResetError: [Errno 54] Connection reset by peer

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/brunno.vanelli/Documents/git/python-snap7/example/s7commplus1_browse_sync.py", line 28, in <module>
    client.connect(host, use_tls=True)
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
  File "/Users/brunno.vanelli/Documents/git/python-snap7/s7commplus/client.py", line 106, in connect
    self._open_connection()
    ~~~~~~~~~~~~~~~~~~~~~^^
  File "/Users/brunno.vanelli/Documents/git/python-snap7/s7commplus/client.py", line 114, in _open_connection
    self._connection.connect(
    ~~~~~~~~~~~~~~~~~~~~~~~~^
        use_tls=p["use_tls"],
        ^^^^^^^^^^^^^^^^^^^^^
    ...<3 lines>...
        password=p["password"] or "",
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/Users/brunno.vanelli/Documents/git/python-snap7/s7commplus/connection.py", line 314, in connect
    self._session_setup_ok = self._setup_session()
                             ~~~~~~~~~~~~~~~~~~~^^
  File "/Users/brunno.vanelli/Documents/git/python-snap7/s7commplus/connection.py", line 1163, in _setup_session
    response_frame = self._recv_s7_data()
  File "/Users/brunno.vanelli/Documents/git/python-snap7/s7commplus/connection.py", line 1433, in _recv_s7_data
    self._tls_read_incoming()
    ~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/Users/brunno.vanelli/Documents/git/python-snap7/s7commplus/connection.py", line 1445, in _tls_read_incoming
    data = self._iso_conn.receive_data()
  File "/Users/brunno.vanelli/Documents/git/python-snap7/snap7/connection.py", line 204, in receive_data
    tpkt_header = self._recv_exact(4)
  File "/Users/brunno.vanelli/Documents/git/python-snap7/snap7/connection.py", line 476, in _recv_exact
    raise S7ConnectionError(f"Receive error: {e}")
snap7.error.S7ConnectionError: Receive error: [Errno 54] Connection reset by peer

I have also tried the legitimation without success (different PLC, not working on master nor this branch), it fails earlier:

INFO snap7.connection: Connected to 192.168.101.34:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured: 87 bytes
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.34:102, version=V2, session=1879051445, tls=True
INFO s7commplus.client: Performing PLC legitimation (password authentication)
Traceback (most recent call last):
  File "/Users/brunno.vanelli/Documents/git/python-snap7/example/s7commplus1_browse_sync.py", line 28, in <module>
    client.connect(host, use_tls=True, password="[redacted]")
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/brunno.vanelli/Documents/git/python-snap7/s7commplus/client.py", line 101, in connect
    self._connection.authenticate(password)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
  File "/Users/brunno.vanelli/Documents/git/python-snap7/s7commplus/connection.py", line 333, in authenticate
    challenge = self._get_legitimation_challenge()
  File "/Users/brunno.vanelli/Documents/git/python-snap7/s7commplus/connection.py", line 388, in _get_legitimation_challenge
    raise S7ConnectionError(f"GetVarSubStreamed for challenge failed: return_value={return_value}")
snap7.error.S7ConnectionError: GetVarSubStreamed for challenge failed: return_value=14058136869961596926

@gijzelaerr

Copy link
Copy Markdown
Owner Author

@bvanelli Thanks for the traces — they exposed two distinct framing problems. Fixed in 2f27f37:

  • TLS/V2 connections now skip the V1-only SessionKey handshake, so seeing a fingerprint and challenge no longer causes the extra SecurityKeyEncryptedKey request and PLC reset.
  • Standard TLS/V2 responses retain their trailing IntegrityId; only SessionKey/HMAC responses strip the protocol-specific leading IntegrityId.
  • Password legitimation GetVarSubStreamed and SetVariable requests now match the upstream wire layout, including the address PValue, ObjectQualifier, sequence field, BLOB root ID, and four-byte fill. Non-zero unsigned return values are now rejected correctly.

Local verification: 1,759 passed, 82 skipped; full pre-commit suite passed; sdist and wheel builds passed.

Could you retest both the two TLS PLCs and the password-protected PLC when convenient?

@bvanelli

bvanelli commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Only partially worked now. The connect now triggers a loop that reconnects multiple times. Even when the browse succeeds, the read immediately fails afterwards.

Stack trace
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): ce106bf6251e3da2fee8dd80018013faa53bd313
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO __main__: Connected to 192.168.101.51: protocol=V2 session=0x70000FDE session_setup_ok=True tls_active=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 0aaf26979ce75e931d7eba9b714c3b605e3c1cfc
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 21c779c7123a7c62b9fd6113c9c3dc59235456bb
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 72cf8aece6ddfa97b2345b0ab4a6d49a84a47074
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Receive error: [Errno 54] Connection reset by peer); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 88e70db012b2e2dbdbe6239beb24a4beee279a4b
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 7fd2a39bb79645cf72904170eeef1925b41e51f5
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Receive error: [Errno 54] Connection reset by peer); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 0ff51512801ba169403911e411452451f79c7e88
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Receive error: [Errno 54] Connection reset by peer); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): f86ae64f658cd84b6628a0ed4e699ca6b23777d7
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 987c56d0e90ec9bbc0e4c9b581db36ca8045f949
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): acd5c78765d59889b2744a19ff720acfe9299fe6
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): a13eff8d96e3355f0791b1e1786d01f86dcbae47
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): d2c8ddc54f0937d6db2aa7ca80d863e40fe3ab60
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Receive error: [Errno 54] Connection reset by peer); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): b5ddae028c49fd6603861a3bcbc8248b1fef7313
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 02bba872aaacf924fb12c55f4d1a91ed6e1ed6cf
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 7a081f4ddbc93ad4e5082471f80e38277221e0a0
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 8ba62e547400f23378e51a40632b780077864bca
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 7ffee03cff16a0d5a017847cc429e909e4428a15
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): ee922312a82ebc70bdddfd896d98b88276c96101
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 8e2d7e14f3884b6c5643a188345675c179ae5991
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 23ac2ec877225653296118b0eb33615ad4b1ddad
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 83da7b8ccaacd6c88a72671c0efd658175101cf7
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): c5bbfa091fd2d27c19bdd23120dc392f823b9911
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 088b960e118c78cb66532798b0077bddf3cc87c1
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 2e528af56f0dfc00cc5e90b97e62157f93d68e62
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): d95f036f090df5e20f61d699d1759bc67273c2c9
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): be95d2101beedd2b31d49374dbd81abc08c6c481
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 75594a66d3f96821dc63cea7afef0a16299c9b7f
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Receive error: [Errno 54] Connection reset by peer); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 70d6e96d0d3566fbff4b12dcc3d3a9faa90a4e62
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 34b8070671150bbdd3ea18eca2c787cbf9a57d6e
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 09025fe7e5b3b434522a2e5f342ca633a2379443
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): fb880dd295c629265ad41bd0e21b29887fa88c21
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 54] Connection reset by peer); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 9ae824a7044a54fb6760e7dce2634cb99e564bb4
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 3eec7693c9eb6f62fa7a5e59b9705642404c999b
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
INFO s7commplus.client: Connection dropped by PLC (Send failed: [Errno 32] Broken pipe); reconnecting and retrying
INFO snap7.connection: Disconnected from 192.168.101.51:102
INFO snap7.connection: Connected to 192.168.101.51:102, PDU size: 1024
INFO s7commplus.connection: TLS activated (tunneled inside COTP frames)
INFO s7commplus.connection: ServerSessionVersion captured (88 bytes)
INFO s7commplus.connection: Public key fingerprint captured: 00:181B7B0847D11694
INFO s7commplus.connection: Session challenge captured (20 bytes): 237cbce5769710146842433786be9329c9c14810
INFO s7commplus.connection: Session setup completed successfully
INFO s7commplus.connection: V2 IntegrityId tracking enabled
INFO s7commplus.connection: S7CommPlus connected to 192.168.101.51:102, version=V2, session=1879052254, tls=True
Found 114957 variables
  [redacted var read][0] (BOOL) @ 8A0E005A.0.0
[redacted var read][0] (BOOL) -> read failed: Symbolic read failed
  [redacted var read][1] (BOOL) @ 8A0E005A.9.1
[redacted var read][1] (BOOL) -> read failed: Send failed: [Errno 32] Broken pipe

Regarding the snap7.error.S7ConnectionError: GetVarSubStreamed legitimation error, I still get it, so I doubt that will be solved because the cause is that is also fails on master. I want to get a good binary dump to submit a separate PR for it still this week:

snap7.error.S7ConnectionError: GetVarSubStreamed failed: return_value=0xE201BB002C27FF88

@gijzelaerr

Copy link
Copy Markdown
Owner Author

@bvanelli Thanks for the retest — I found a regression in the previous merge resolution. TLS/V2 responses prepend the IntegrityId before the application payload, but 2f27f37 left it in place. That made successful symbolic reads parse as errors; this PLC then reset the socket, causing the repeated reconnects.

Fixed in 33a4c9e, with a wire-level regression test. The full local test suite, pre-commit checks, and package build pass.

Could you retest the browse followed by at least two consecutive symbolic reads? I left the separate GetVarSubStreamed legitimation failure out of this change pending your binary capture.

@bvanelli

bvanelli commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

@gijzelaerr some findings:

The legitimation issue is completely unrelated, and I'll submit a PR with contract testing once you merge this one. I'll focus on the backwards compatibility here instead.

The breaking compatibility is related to the changes in encode_object_qualifier. Here is a diff:

diff --git a/s7commplus/codec.py b/s7commplus/codec.py
index 17b04a8..237998e 100644
--- a/s7commplus/codec.py
+++ b/s7commplus/codec.py
@@ -324,7 +324,11 @@ def encode_object_qualifier(key_qualifier: int = 0) -> bytes:
     result += bytes([0x00, DataType.AID]) + encode_uint32_vlq(0)
     # KeyQualifier = UDInt(key_qualifier)
     result += encode_uint32_vlq(Ids.KEY_QUALIFIER)
-    result += bytes([0x00, DataType.UDINT]) + struct.pack(">I", key_qualifier)
+    # Framing for the V1 (unconfirmed by me)
+    # result += bytes([0x00, DataType.UDINT]) + struct.pack(">I", key_qualifier)
+    # Framing for the V2 (confirmed by me)
+    result += bytes([0x00, DataType.UDINT]) + encode_uint32_vlq(key_qualifier)
+    result += b"\x00"  # trailing zero byte, as emitted by S7p.EncodeObjectQualifier
     return bytes(result)

You can check that the new framing has different sizes, and I believe that that is causing the connection to be reset:

>>> struct.pack(">I", 0)  # produces a fixed size of 4 bytes
b'\x00\x00\x00\x00'
>>> encode_uint32_vlq(0)  # produces variable size
b'\x00'
>>> encode_uint32_vlq(2555)
b'\x93{'

The V2 version agrees with the original source code for S7p.EncodeObjectQualifier. The new V1 version needs to match this

As a third problem, the change introduce in 33a4c9e is a regression. I don't think is needed at all, as it causes:

ERROR s7commplus.client: _parse_read_response: PLC returned error 0x1

Reverting it back to if self._session_key is not None and len(resp_payload) > 1: resolved it for the S71500 (since the branch gets ignored).

Let me know if you need more input (or message me directly).

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.

4 participants