feat(gateway): implement KNX security features including secure session handling and factory certificate management

Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
Tony
2026-05-12 21:29:40 +08:00
parent 888d021343
commit df1dd472cc
7 changed files with 509 additions and 3 deletions
@@ -35,6 +35,12 @@ constexpr uint16_t kServiceDisconnectResponse = 0x020a;
constexpr uint16_t kServiceTunnellingRequest = 0x0420;
constexpr uint16_t kServiceTunnellingAck = 0x0421;
constexpr uint16_t kServiceRoutingIndication = 0x0530;
constexpr uint16_t kServiceSecureWrapper = 0x0950;
constexpr uint16_t kServiceSecureSessionRequest = 0x0951;
constexpr uint16_t kServiceSecureSessionResponse = 0x0952;
constexpr uint16_t kServiceSecureSessionAuth = 0x0953;
constexpr uint16_t kServiceSecureSessionStatus = 0x0954;
constexpr uint16_t kServiceSecureGroupSync = 0x0955;
constexpr uint8_t kKnxNetIpHeaderSize = 0x06;
constexpr uint8_t kKnxNetIpVersion10 = 0x10;
constexpr uint8_t kKnxNoError = 0x00;
@@ -42,6 +48,8 @@ constexpr uint8_t kKnxErrorConnectionId = 0x21;
constexpr uint8_t kKnxErrorConnectionType = 0x22;
constexpr uint8_t kKnxErrorNoMoreConnections = 0x24;
constexpr uint8_t kKnxErrorSequenceNumber = 0x04;
constexpr uint8_t kKnxSecureStatusAuthFailed = 0x01;
constexpr uint8_t kKnxSecureStatusUnauthenticated = 0x02;
constexpr uint8_t kKnxConnectionTypeTunnel = 0x04;
constexpr uint8_t kKnxTunnelLayerLink = 0x02;
constexpr uint8_t kTpUartResetRequest = 0x01;
@@ -507,6 +515,20 @@ bool ParseKnxNetIpHeader(const uint8_t* data, size_t len, uint16_t* service,
return *total_len >= 6 && *total_len <= len;
}
bool IsKnxNetIpSecureService(uint16_t service) {
switch (service) {
case kServiceSecureWrapper:
case kServiceSecureSessionRequest:
case kServiceSecureSessionResponse:
case kServiceSecureSessionAuth:
case kServiceSecureSessionStatus:
case kServiceSecureGroupSync:
return true;
default:
return false;
}
}
bool IsExtendedTpFrame(const uint8_t* data, size_t len) {
return len > 0 && (data[0] & 0xD3) == 0x10;
}
@@ -1875,6 +1897,10 @@ void GatewayKnxTpIpRouter::handleUdpDatagram(const uint8_t* data, size_t len,
}
const uint8_t* body = data + 6;
const size_t body_len = total_len - 6;
if (IsKnxNetIpSecureService(service)) {
handleSecureService(service, body, body_len, remote);
return;
}
switch (service) {
case kServiceRoutingIndication:
if (config_.multicast_enabled) {
@@ -2001,6 +2027,34 @@ void GatewayKnxTpIpRouter::handleDisconnectRequest(const uint8_t* body, size_t l
sendDisconnectResponse(channel_id, status, remote);
}
void GatewayKnxTpIpRouter::handleSecureService(uint16_t service, const uint8_t* body,
size_t len, const sockaddr_in& remote) {
#if defined(CONFIG_GATEWAY_KNX_IP_SECURE_SUPPORTED)
switch (service) {
case kServiceSecureSessionRequest:
case kServiceSecureSessionAuth:
ESP_LOGW(kTag, "KNXnet/IP Secure service 0x%04x rejected: secure sessions are not provisioned", service);
sendSecureSessionStatus(kKnxSecureStatusAuthFailed, remote);
break;
case kServiceSecureWrapper:
ESP_LOGW(kTag, "KNXnet/IP Secure wrapper rejected: no authenticated secure session");
sendSecureSessionStatus(kKnxSecureStatusUnauthenticated, remote);
break;
case kServiceSecureGroupSync:
ESP_LOGD(kTag, "KNXnet/IP Secure group sync ignored until secure routing is provisioned");
break;
default:
ESP_LOGD(kTag, "KNXnet/IP Secure service 0x%04x ignored", service);
break;
}
#else
(void)service;
(void)body;
(void)len;
(void)remote;
#endif
}
void GatewayKnxTpIpRouter::sendTunnellingAck(uint8_t channel_id, uint8_t sequence,
uint8_t status, const sockaddr_in& remote) {
const std::vector<uint8_t> body{0x04, channel_id, sequence, status};
@@ -2008,6 +2062,12 @@ void GatewayKnxTpIpRouter::sendTunnellingAck(uint8_t channel_id, uint8_t sequenc
SendAll(udp_sock_, packet.data(), packet.size(), remote);
}
void GatewayKnxTpIpRouter::sendSecureSessionStatus(uint8_t status, const sockaddr_in& remote) {
const std::vector<uint8_t> body{status, 0x00};
const auto packet = KnxNetIpPacket(kServiceSecureSessionStatus, body);
SendAll(udp_sock_, packet.data(), packet.size(), remote);
}
void GatewayKnxTpIpRouter::sendTunnelIndication(const uint8_t* data, size_t len) {
if (!tunnel_connected_ || udp_sock_ < 0 || data == nullptr || len == 0) {
return;