Skip to content

Data Secure comissioning in ETS compatibility: extended frames, Max APDU length, and KNXnet/IP Tunnelling v2#4

Draft
evgeny-boger wants to merge 9 commits intomainfrom
fix/max-apdu-length-v2
Draft

Data Secure comissioning in ETS compatibility: extended frames, Max APDU length, and KNXnet/IP Tunnelling v2#4
evgeny-boger wants to merge 9 commits intomainfrom
fix/max-apdu-length-v2

Conversation

@evgeny-boger
Copy link
Copy Markdown
Member

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

Summary

This PR fixes ETS compatibility issues when connecting to knxd via KNXnet/IP TCP tunnelling to talk to KNX Data Secure devices. Without these changes, ETS cannot discover the correct APDU length, fails to handle extended KNX frames, and gets no responses to mandatory v2 feature queries.

Changes by area

Max APDU length reporting and auto-detect:

  • Report PID_MAX_APDULENGTH (PID 56, 03_05_01 §4.3.7) in DEVICE_CONFIGURATION_REQUEST responses
  • Report PID_MAX_APDULENGTH in TCP tunnel config service
  • Auto-detect from hardware via Router::maxFrameLength() (minimum across all bus drivers, minus 8 for frame overhead)
  • Validate range 15..254 per spec §4.3.7.1 — clamp with error log if out of range
  • Configurable via max-apdu-length in knxd.ini; manufacturer-code for KNX manufacturer ID

KNXnet/IP TCP Tunnelling v2 (required for ETS 6.3 TCP connections):

  • Add KNXnet/IP v2 service type constants (03_08_02 Core v01.06.02, 03_08_04 Tunnelling v01.07.01)
  • Handle DESCRIPTION_REQUEST on TCP connections with v2 service families
  • Silently ignore SEARCH_REQUEST_EXTENDED (ETS falls back gracefully)
  • Handle TUNNEL_FEATURE_GET for all 8 interface features (§3.6)
  • Handle TUNNEL_FEATURE_SET with proper access control
  • BusConnectionStatus reports actual router link state via Router::isIdle()

TPUART/NCN5120 extended frame support:

  • Extract encode_frame() virtual for chip-specific frame encoding
  • NCN5120: use U_L_DataOffset.req for frames >64 bytes (critical bug fix)
  • NCN5120: report maxFrameLength = 263; TPUART: 63; FT12/USB: 23

Enhanced TPUART logging:

  • Decode state indication bits (sc, re, te, pe, tw) in error messages
  • Log retry counts, state names, and timing in timeout/error paths

Test plan

Automated: tools/test_tunnel_features.py

python3 tools/test_tunnel_features.py <host> <port>

Tests TUNNEL_FEATURE_GET for all 8 features, FEATURE_SET (writable, read-only, unknown), and readback verification. Expected: 13/13 passed.

Manual: config validation

max-apdu-length explicit value:

[tcptun]
server = tcptunsrv
port = 3672
tunnel = tcptun-tunnel
max-apdu-length = 200
  • TUNNEL_FEATURE_GET for IF_MAX_APDU_LENGTH returns 00 c8 (200)
  • PID_MAX_APDULENGTH config request returns 00 c8 (200)

max-apdu-length auto-detect (not configured):

  • With NCN5120 driver: auto-detects to 254 (263 - 8 = 255, clamped)
  • With TPUART driver: auto-detects to 55 (63 - 8)
  • With dummy driver (or no bus driver): falls back to 15 (23 - 8)

max-apdu-length out of range:

  • max-apdu-length = 300 → clamped to 254, error log max-apdu-length 300 exceeds 254, clamping
  • max-apdu-length = 5 → clamped to 15, error log max-apdu-length 5 below minimum 15, clamping
  • max-apdu-length = 0 → treated as "not configured", auto-detects from hardware

manufacturer-code:

[tcptun]
manufacturer-code = 0xABCD
  • TUNNEL_FEATURE_GET for IF_KNX_MANUFACTURER_CODE returns ab cd
  • Not configured → returns 00 00

Manual: ETS 6 TCP tunnel connection

  • ETS 6.4 discovers knxd via TCP and connects without errors
  • ETS can program/download secure devices through knxd tunnel (this implicitly validates that ETS received the correct max APDU length — if too large, oversized frames would fail on the bus)
  • knxd log shows TUNNEL_FEATURE_GET requests from ETS being answered (with trace-mask = 0xff)
  • No "Unexpected service type" errors in knxd log

Manual: TPUART/NCN5120

  • knxd starts with NCN5120 driver, no errors in log
  • Extended frames (>64 bytes) sent correctly via NCN5120 (KNX Secure test if available)
  • TPUART state transitions logged with state names on timeout/error
  • No regressions in standard frame transmission

Manual: regression

  • UDP tunnelling still works (eibnetserver path)
  • Bus monitor mode still works
  • knxd starts and stops cleanly with standard config

Comment thread src/libserver/eibnetserver.cpp Outdated
interface = cfg->value("interface","");
servername = cfg->value("name", dynamic_cast<Router *>(&router)->servername);
keepalive = cfg->value("heartbeat-timeout", CONNECTION_ALIVE_TIME);
maxAPDULength = cfg->value("max-apdu-length", 249);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you said it defaults to 15, yet in the code it is 249. Keep 15

Comment thread src/backend/tpuart.cpp Outdated
switch(state)
{
case T_wait_keepalive:
TRACEPRINTF (t, 8, "State indication in wait_keepalive, going to wait");
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this traceprintf

Comment thread src/backend/tpuart.cpp Outdated
setstate(T_wait);
break;
case T_in_reset:
TRACEPRINTF (t, 8, "State indication in reset, staying");
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this traceprintf

Comment thread src/backend/tpuart.cpp Outdated
// ERRORPRINTF (t, E_ERROR | 62, "TPUART detected. Hardware ACK not supported.");
// my_addr = 0;
// }
TRACEPRINTF (t, 8, "State indication in setaddr, going to getstate");
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this traceprintf

Comment thread src/backend/tpuart.cpp Outdated
setstate(T_in_getstate);
break;
case T_in_getstate:
TRACEPRINTF (t, 8, "State indication in getstate, going online");
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this traceprintf

Comment thread src/libserver/eibnetip.h Outdated
SEARCH_REQUEST_EXTENDED = 0x020B,
SEARCH_RESPONSE_EXTENDED = 0x020C,

/* Tunnelling (0x0420 .. 0x042F) */
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add reference to the KNX standard: version, section, paragraph and so on.

Comment thread src/libserver/tcptunserver.cpp Outdated
r2.installid = 0;
inet_pton(AF_INET, "224.0.23.12", &r2.multicastaddr);
strncpy((char *) r2.name, router.servername.c_str(), sizeof(r2.name) - 1);
d.version = 2; // v2 = TCP support (ISO 22510)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add reference to the KNX standard: version, section, paragraph and so on, instead of ISO 22510

Comment thread src/libserver/tcptunserver.h Outdated
protected:
/** config */
ev::tstamp keepalive;
uint16_t maxAPDULength = 249;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hardcoded value should be conservative, not 249

Comment thread src/libserver/tcptunserver.cpp Outdated

if (p1.service == TUNNEL_FEATURE_GET)
{
// ISO 22510: TUNNELLING_FEATURE_GET
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add reference to the KNX standard: version, section, paragraph and so on.

Comment thread src/libserver/eibnetserver.h Outdated
int no;
bool nat;
uint8_t maxAPDULength;
uint16_t maxAPDULength;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't need to widen the type. But do check the configuration value though, not silently corrupt data

@evgeny-boger evgeny-boger force-pushed the fix/max-apdu-length-v2 branch 3 times, most recently from 0681d4c to 4fced1e Compare March 22, 2026 16:07
Comment thread src/libserver/eibnetserver.cpp
Comment thread src/libserver/tcptunserver.cpp Outdated
EIBnet_ConnectResponse r2;
// For TCP, the "Route Back" HPAI must be used.
// See ISO 22510:2019 section 5.2.8.6.2
// KNX Std v3.0.4, 03_08_02 Core v01.06.02, §5.2.8.6.2
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't change that

Comment thread src/libserver/tcptunserver.cpp Outdated
r2.installid = 0;
inet_pton(AF_INET, "224.0.23.12", &r2.multicastaddr);
strncpy((char *) r2.name, router.servername.c_str(), sizeof(r2.name) - 1);
d.version = 2; // v2 = TCP support, KNX Std v3.0.4, 03_08_02 Core v01.06.02
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

find exact paragraph

Comment thread src/libserver/tcptunserver.cpp Outdated
d.version = 2; // v2 = TCP support, KNX Std v3.0.4, 03_08_02 Core v01.06.02
d.family = 2; // Core
r2.services.push_back(d);
d.family = 3; // Device Management
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make constants for these numbers. It's table 3 in 7.5.4.3 of , KNX Std v3.0.4, 03_08_02 pdf I suppose?

Comment thread src/libserver/tcptunserver.cpp
Comment thread src/libserver/tcptunserver.cpp
Comment thread src/libserver/tcptunserver.cpp
Comment thread src/libserver/tcptunserver.cpp Outdated

if (p1.service == TUNNEL_FEATURE_SET)
{
// KNX Std v3.0.4, 03_08_04 Tunnelling v01.07.01, §3.3: TUNNELLING_FEATURE_SET
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reference a format table

Comment thread src/libserver/tcptunserver.cpp Outdated

switch (featureID)
{
case 0x01: // SupportedEMIType — read-only
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, should be constants or enums

Comment thread tools/test_tunnel_features.py
@evgeny-boger evgeny-boger force-pushed the fix/max-apdu-length-v2 branch from 4fced1e to e32d468 Compare March 22, 2026 16:31
Comment thread src/libserver/eibnetserver.cpp Outdated
else if (prop == PID_MAX_APDULENGTH)
{
res.resize (2);
res[1] = maxAPDULength;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

res[1] is 8-bit, doesn't it?

Comment thread src/libserver/eibnetserver.h Outdated

enum PropertyID : uint8_t
{
PID_MAX_APDULENGTH = 0x38,
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are other property ids?
What is the reference for this 0x38 in a standard?

Comment thread src/libserver/tunchannel.cpp Outdated
else if (prop == PID_MAX_APDULENGTH)
{
res.resize (2);
res[1] = 0xF9;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this magic number?

Comment thread src/libserver/tcptunserver.cpp Outdated
TRACEPRINTF (t, 8, "DESCRIBE");

Router& router = static_cast<Router &>(parent->router);
r2.KNXmedium = 2;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make an enum for medium types (if it's not exists yet)

Comment thread src/libserver/tcptunserver.cpp
@evgeny-boger evgeny-boger force-pushed the fix/max-apdu-length-v2 branch 7 times, most recently from a48d716 to 7d7f873 Compare March 22, 2026 18:09
evgeny-boger and others added 5 commits March 22, 2026 21:14
Respond to property PID_MAX_APDULENGTH (0x38) queries in KNXnet/IP
tunneling config requests instead of returning count=0 (unsupported).

Auto-detect max APDU length from hardware driver capabilities via
Router::maxFrameLength(), which returns the minimum frame length
across all bus drivers. Configurable via 'max-apdu-length' in
knxd.ini (range 15..254 per 03_05_01 §4.3.7.1).

Based on voxior-inc/knxd fork by Zan Pevec.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add state name to timeout, retry and error messages for easier
debugging. Decode TPUART state indication register bits (SC, RE,
TE, PE, TW) in warning messages. Log state transitions during
reset, setaddr and getstate sequences.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ISO 22510 service type enums: SEARCH_REQUEST_EXTENDED,
SEARCH_RESPONSE_EXTENDED, TUNNEL_FEATURE_GET/RESPONSE/SET/INFO.
Required for KNXnet/IP Secure and ETS6 TCP tunneling support.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ETS6 sends DESCRIPTION_REQUEST over TCP to discover device
capabilities. Respond with device info and v2 service families
(Core, Device Management, Tunnelling) per ISO 22510.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pass maxAPDULength from server config to TunServiceConfig so that
DEVICE_CONFIGURATION_REQUEST for PID_MAX_APDULENGTH returns the
configured value instead of hardcoded 249. Configurable via
'max-apdu-length' in the tcptunsrv section of knxd.ini.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@evgeny-boger evgeny-boger force-pushed the fix/max-apdu-length-v2 branch from 7d7f873 to cc3f64b Compare March 22, 2026 18:14
evgeny-boger and others added 3 commits March 22, 2026 21:20
Extract frame-to-UART encoding from send_again() into virtual
encode_frame() on TPUARTwrap. Base implementation handles standard
6-bit TPUART indexing (max 63 bytes). NCN5120wrap overrides it to
insert U_L_DataOffset.req commands at 64-byte boundaries for
extended frames up to 263 bytes.

The virtual method approach cleanly separates chip-specific UART
command encoding from the shared transmission logic in send_again().
Future TPUART-family chips (e.g. Elmos E981.03 with its
U_L_LongDataContinue 3-byte encoding) can override encode_frame()
without touching the shared code.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Complete the TUNNEL_FEATURE_GET handler for all interface features
defined in KNX Standard v3.0.4 §03.08.04 (Tunnelling v01.07.01),
and add a TUNNEL_FEATURE_SET handler.

FEATURE_GET additions (features 0x01-0x03, 0x07 were added earlier):
- 0x01: Fix SupportedEMIType to return 2 bytes per spec (was 1 byte,
  rejected by Calimero's value length validation)
- 0x03: BusConnectionStatus now reports actual router link state
  via Router::isIdle() instead of hardcoded "connected"
- 0x04: KNXManufacturerCode (configurable via manufacturer-code)
- 0x05: ActiveEMIType (cEMI)
- 0x06: InterfaceIndividualAddress (per-channel, from link layer)
- 0x08: InterfaceFeatureInfoEnable (per-channel state)

FEATURE_SET handler:
- Feature 0x08 (EnableFeatureInfoService): writable, validates
  value is 0x00 or 0x01, rejects others with FR_DATA_VOID
- All other known features: responds FR_ACCESS_READ_ONLY with
  value echoed back (capped to 2 bytes)
- Unknown features: responds FR_ADDRESS_VOID, no value

Also adds FeatureReturnCode enum to eibnetip.h with standard
cEMI error codes used by TUNNEL_FEATURE_RESPONSE.

Tested with raw protocol test (13/13) and Calimero KNX library
(11/11), which validates per-feature value lengths per spec.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Python script that tests all TUNNEL_FEATURE_GET/SET operations
against a knxd TCP tunnel server. Covers all 8 interface features,
unknown feature handling, read-only rejection, writable SET with
readback verification, and proper disconnect.

Usage: python3 test_tunnel_features.py [host] [port]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@evgeny-boger evgeny-boger force-pushed the fix/max-apdu-length-v2 branch from cc3f64b to 0d6cfd0 Compare March 22, 2026 18:21
@evgeny-boger evgeny-boger changed the title ETS 6.3 compatibility: extended frames, Max APDU length, and KNXnet/IP Tunnelling v2 Data Secure comissioning in ETS compatibility: extended frames, Max APDU length, and KNXnet/IP Tunnelling v2 Mar 22, 2026
Implement SEARCH_RESPONSE_EXTENDED (03_08_02 Core v01.06.02, §7.6.4)
instead of silently ignoring SEARCH_REQUEST_EXTENDED. Returns the same
device info and service family DIBs as DESCRIPTION_RESPONSE.

SRP (Search Request Parameter) filtering is not implemented — all DIBs
are returned unconditionally, which is a valid superset per spec.

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