Skip to content

Add KNX IP Secure server-side TCP tunnelling#5

Draft
evgeny-boger wants to merge 8 commits intofix/max-apdu-length-v2from
tmp/ipsecure
Draft

Add KNX IP Secure server-side TCP tunnelling#5
evgeny-boger wants to merge 8 commits intofix/max-apdu-length-v2from
tmp/ipsecure

Conversation

@evgeny-boger
Copy link
Copy Markdown
Member

@evgeny-boger evgeny-boger commented Mar 22, 2026

Summary

Add KNX IP Secure (03_08_09) support for the TCP tunnel server, allowing encrypted and authenticated KNXnet/IP connections from ETS and other clients.

How it works

  • Both plain and secure connections accepted on the same TCP port (3671)
  • ETS automatically detects IP Secure via Secure Service Families DIB (type 0x06)
  • No KNX IP Router needs to be configured in the ETS project — ETS initiates IP Secure when it sees the DIB
  • UDP multicast discovery is optional — ETS can connect directly via TCP

Passwords

  • user-password (mandatory) — authenticates the client to the server. ETS calls this "Commissioning Password". Set for both management (user 1) and tunnelling (user 2) access.
  • device-auth (optional) — the Device Authentication Code. Authenticates the server to the client to prevent MITM attacks. The client may skip verification if it doesn't know the code. IP Secure works without it.

Configuration

Simple (recommended):

[tcptun]
server = tcptunsrv
port = 3671
user-password = MyPassword123

With device authentication and custom serial number:

[tcptun]
server = tcptunsrv
port = 3671
device-auth = MyAuthCode
user-password = MyPassword123
serial-number = 00FAAABBCCDD

For UDP discovery with security advertisement:

[server]
server = ets_router
secure = true
discover = true
serial-number = 00FAAABBCCDD

Build options

./configure --enable-ipsecure    # default: enabled
./configure --disable-ipsecure   # builds without libcrypto

Dependencies

  • OpenSSL libcrypto — only when --enable-ipsecure (default)

Note on .knxkeys keyring files

Keyring loading was intentionally not implemented. ETS prompts the user for the "Commissioning Password" when connecting to an IP Secure device, regardless of whether the keyring is loaded. Since knxd is not a real KNX device, ETS never programs passwords into it. The simplest approach is to set user-password directly in the config and enter the same password in ETS when prompted.

Test plan

  • End-to-end with ETS 6 — secure tunnel, device diagnostics, bus access
  • End-to-end with XKNX Python library
  • Hardcoded passwords with ETS commissioning password prompt
  • Session lifecycle: handshake, authentication, tunnel, keepalive, close
  • Both plain and secure connections on same port
  • Builds with --enable-ipsecure (default) and --disable-ipsecure
  • Cross-compiled for arm64 (Wirenboard) via devcontainer

🤖 Generated with Claude Code

@evgeny-boger evgeny-boger changed the base branch from main to fix/max-apdu-length-v2 March 22, 2026 21:59
evgeny-boger and others added 3 commits March 23, 2026 02:40
Implement KNX IP Secure (03_08_09) for the TCP tunnel server, allowing
encrypted and authenticated KNXnet/IP connections from ETS and other
KNX IP Secure clients.

Protocol implementation:
- SESSION_REQUEST/RESPONSE: X25519 ECDH key exchange with device
  authentication MAC (PBKDF2-derived key)
- SESSION_AUTHENTICATE: User password verification via CCM MAC
- SECURE_WRAPPER: AES-128-CCM encryption/authentication of all
  subsequent KNXnet/IP frames (16-byte MAC)
- Session state machine: IDLE → UNAUTHENTICATED → AUTHENTICATED
- Sequence number tracking for replay protection
- Session keepalive and close handling

Configuration:
  [mytcp]
  server = tcptunsrv
  device-auth = <device authentication password>
  user-password = <user password>

Or load from .knxkeys keyring file:
  keyring = /path/to/project.knxkeys
  keyring-password = <keyring password>

Tested end-to-end with XKNX Python library as KNX IP Secure client.
Full session lifecycle verified: handshake, authentication, tunnel
establishment, data exchange, and clean disconnect.

Also fixes tools/version.sh to work in git worktrees.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Clear session_key and xor_client_server in SecureSession destructor
- Clear xor_client_server after successful authentication
- Remove dead code: aes_ctr_stream(), unused includes (openssl/rand.h,
  openssl/x509.h)
- Fix sequence number: initialize recv_seq to UINT64_MAX so first
  frame (seq=0) is accepted, replays properly rejected
- Add session cleanup in TcpTunConn::stop() to prevent session leaks
- Limit concurrent sessions to IPSEC_MAX_SESSIONS (16)
- Fix user_id logging: read after handleSessionAuthenticate() succeeds
- Move service type constants from header to .cpp (only used internally)
- Filter keyring Interface elements by Type="Tunneling" or "Backbone"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add Security service family (0x09) to DESCRIPTION_RESPONSE on TCP
tunnel server when IP Secure is enabled, so ETS discovers the secure
tunnelling capability.

Add `secure` config option to ets_router (UDP server) to include
SF_SECURITY in SEARCH_RESPONSE for multicast discovery.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@evgeny-boger evgeny-boger force-pushed the tmp/ipsecure branch 2 times, most recently from f25d994 to 9d345ad Compare March 23, 2026 01:17
Advertise SF_SECURITY in SEARCH_RESPONSE_EXTENDED when IP Secure is
enabled, matching DESCRIPTION_RESPONSE behavior.

Do not reject plain CONNECT_REQUESTs — ETS decides whether to use
IP Secure based on its project configuration, not server rejection.
Both plain and secure connections are accepted on the same port.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
evgeny-boger and others added 2 commits March 23, 2026 13:42
- Replace hand-rolled XML parsing with pugixml DOM parser
- Fix keyring password extraction: skip 8-byte random prefix,
  remove PKCS7 padding, PBKDF2-derive keys from plaintext passwords
- Load management password (user 1) from Device elements
- Add Secure Service Families DIB (type 0x06) to all responses
  (DESCRIPTION_RESPONSE, SEARCH_RESPONSE, SEARCH_RESPONSE_EXTENDED)
- Add Tunnelling Info DIB (type 0x07) with tunnel slot addresses
- Advertise service families v2 when secure is enabled
- Add pugixml link dependency

Tested end-to-end with ETS 6 connecting via KNX IP Secure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add detailed comments in tcptunserver.cpp explaining password roles:
  user-password (ETS "Commissioning Password") is mandatory,
  device-auth is optional (client MITM prevention only)
- Document that ETS initiates IP Secure from Secure Service Families
  DIB without requiring a KNX IP Router in the project
- Add BUILD.md with cross-compile and deploy instructions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
evgeny-boger and others added 2 commits March 23, 2026 20:51
Remove all keyring XML parsing code (pugixml dependency, base64 decode,
AES-CBC decrypt, password extraction). The keyring loading was unreliable
because ETS prompts the user for the "Commissioning Password" regardless
of whether the keyring is loaded — ETS matches credentials by device
serial number, not by individual address.

The simplest and most reliable approach:
- Set user-password in knxd config
- Enter the same password in ETS when prompted
- Optionally set device-auth for MITM prevention

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add --enable-ipsecure / --disable-ipsecure configure option.
When disabled, knxd builds without libcrypto dependency and all
IP Secure code is compiled out via #ifdef HAVE_IPSECURE.

Also adds serial-number config option for discovery DIBs.

Co-Authored-By: Claude Opus 4.6 (1M context) <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