Add secure transport and OAM router runtime implementations

- Implement secure transport mechanisms in `gateway_knx_secure_transport.cpp` for handling secure sessions, including AES encryption, session key generation, and secure packet wrapping and unwrapping.
- Introduce `OamRouterRuntime` in `oam_router_runtime.cpp` to manage OAM router identity, individual addresses, and tunnel frame handling.
- Enhance secure session management with functions for session allocation, authentication, and secure packet processing.
- Ensure compatibility with existing KNXnet/IP protocols while adding support for secure communications.

Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
Tony
2026-05-25 08:18:01 +08:00
parent 0467179f70
commit 2b779d5532
22 changed files with 2665 additions and 77 deletions
+103 -2
View File
@@ -27,6 +27,7 @@ namespace gateway {
namespace openknx {
class EtsDeviceRuntime;
class OamRouterRuntime;
class TpuartUartInterface;
}
@@ -59,6 +60,40 @@ struct GatewayKnxEtsAssociation {
uint16_t group_object_number{0};
};
struct GatewayKnxCloudRemoteConfig {
bool enabled{false};
std::string mode{"mqtt"};
std::string relay_endpoint;
std::string mqtt_topic_prefix;
std::string auth_token_ref;
bool require_secure_tunnel{true};
bool udp_punch_enabled{false};
};
struct GatewayKnxOamRouterConfig {
bool enabled{false};
bool ets_database_enabled{true};
bool secure_tunnel_enabled{true};
bool secure_routing_enabled{true};
uint16_t individual_address{0xff02};
uint16_t tunnel_address_base{0xff10};
int programming_button_gpio{-1};
bool programming_button_active_low{true};
int programming_led_gpio{-1};
bool programming_led_active_high{true};
GatewayKnxCloudRemoteConfig cloud_remote;
};
struct GatewayKnxIpSecureCredentialMaterial {
bool activated{false};
bool backbone_key_available{false};
std::array<uint8_t, 16> backbone_key{};
bool device_authentication_key_available{false};
std::array<uint8_t, 16> device_authentication_key{};
std::vector<std::array<uint8_t, 16>> tunnel_user_keys;
uint64_t routing_sequence{0};
};
struct GatewayKnxConfig {
bool dali_router_enabled{true};
bool ip_router_enabled{false};
@@ -76,6 +111,7 @@ struct GatewayKnxConfig {
bool programming_button_active_low{true};
int programming_led_gpio{-1};
bool programming_led_active_high{true};
GatewayKnxOamRouterConfig oam_router;
std::vector<GatewayKnxEtsAssociation> ets_associations;
GatewayKnxTpUartConfig tp_uart;
};
@@ -134,6 +170,9 @@ struct GatewayKnxReg1ScanOptions {
std::optional<GatewayKnxConfig> GatewayKnxConfigFromValue(const DaliValue* value);
DaliValue GatewayKnxConfigToValue(const GatewayKnxConfig& config);
std::optional<GatewayKnxOamRouterConfig> GatewayKnxOamRouterConfigFromValue(
const DaliValue* value);
DaliValue GatewayKnxOamRouterConfigToValue(const GatewayKnxOamRouterConfig& config);
bool GatewayKnxConfigUsesTpUart(const GatewayKnxConfig& config);
const char* GatewayKnxMappingModeToString(GatewayKnxMappingMode mode);
@@ -228,6 +267,7 @@ class GatewayKnxTpIpRouter {
using GroupObjectWriteHandler = std::function<DaliBridgeResult(uint16_t group_object_number,
const uint8_t* data,
size_t len)>;
using RoutingSequenceStoreHandler = std::function<void(uint64_t sequence)>;
GatewayKnxTpIpRouter(GatewayKnxBridge& bridge,
std::string openknx_namespace = "openknx");
@@ -237,11 +277,16 @@ class GatewayKnxTpIpRouter {
void setCommissioningOnly(bool enabled);
void setGroupWriteHandler(GroupWriteHandler handler);
void setGroupObjectWriteHandler(GroupObjectWriteHandler handler);
void setOamIpSecureCredentials(const GatewayKnxIpSecureCredentialMaterial& credentials);
void setOamIpSecureRoutingSequenceStoreHandler(RoutingSequenceStoreHandler handler);
const GatewayKnxConfig& config() const;
bool tpUartOnline() const;
bool programmingMode();
esp_err_t setProgrammingMode(bool enabled);
esp_err_t toggleProgrammingMode();
bool oamProgrammingMode();
esp_err_t setOamProgrammingMode(bool enabled);
esp_err_t toggleOamProgrammingMode();
esp_err_t start(uint32_t task_stack_size, UBaseType_t task_priority);
esp_err_t stop();
@@ -285,6 +330,24 @@ class GatewayKnxTpIpRouter {
::sockaddr_in data_remote{};
std::vector<uint8_t> last_received_cemi;
std::vector<uint8_t> last_tunnel_confirmation_packet;
uint16_t secure_session_id{0};
bool oam_router_persona{false};
};
struct SecureSession {
bool active{false};
bool authenticated{false};
uint16_t session_id{0};
int tcp_sock{-1};
::sockaddr_in remote{};
std::array<uint8_t, 32> client_public_key{};
std::array<uint8_t, 32> server_public_key{};
std::array<uint8_t, 32> shared_secret{};
std::array<uint8_t, 16> session_key{};
uint64_t send_sequence{0};
uint64_t receive_sequence{0};
uint8_t user_id{0};
TickType_t last_activity_tick{0};
};
static void TaskEntry(void* arg);
@@ -297,6 +360,7 @@ class GatewayKnxTpIpRouter {
void handleTcpAccept();
void handleTcpClient(TcpClient& client);
void closeTcpClient(TcpClient& client);
void closeSecureSessionsForTcp(int sock);
std::unique_ptr<openknx::TpuartUartInterface> createOpenKnxTpUartInterface();
bool configureTpUart();
bool configureProgrammingGpio();
@@ -316,6 +380,12 @@ class GatewayKnxTpIpRouter {
void handleDisconnectRequest(const uint8_t* packet_data, size_t len, const ::sockaddr_in& remote);
void handleSecureService(uint16_t service, const uint8_t* body, size_t len,
const ::sockaddr_in& remote);
void handleSecureSessionRequest(const uint8_t* body, size_t len,
const ::sockaddr_in& remote);
void handleSecureWrapper(const uint8_t* body, size_t len,
const ::sockaddr_in& remote);
void handleSecureGroupSync(const uint8_t* body, size_t len,
const ::sockaddr_in& remote);
void sendTunnellingAck(uint8_t channel_id, uint8_t sequence, uint8_t status,
const ::sockaddr_in& remote);
void sendDeviceConfigurationAck(uint8_t channel_id, uint8_t sequence, uint8_t status,
@@ -335,9 +405,9 @@ class GatewayKnxTpIpRouter {
const ::sockaddr_in& remote, uint8_t connection_type,
uint16_t tunnel_address);
void sendRoutingIndication(const uint8_t* data, size_t len);
bool sendPacket(const std::vector<uint8_t>& packet, const ::sockaddr_in& remote) const;
bool sendPacket(const std::vector<uint8_t>& packet, const ::sockaddr_in& remote);
bool sendPacketToTunnelClient(const TunnelClient& client,
const std::vector<uint8_t>& packet) const;
const std::vector<uint8_t>& packet);
bool currentTransportAllowsTcpHpai() const;
std::optional<std::array<uint8_t, 8>> localHpaiForRemote(const ::sockaddr_in& remote,
bool tcp = false) const;
@@ -359,6 +429,22 @@ class GatewayKnxTpIpRouter {
const ::sockaddr_in& data_remote,
uint8_t connection_type);
void resetTunnelClient(TunnelClient& client);
SecureSession* findSecureSession(uint16_t session_id, const ::sockaddr_in& remote);
const SecureSession* findSecureSession(uint16_t session_id,
const ::sockaddr_in& remote) const;
SecureSession* allocateSecureSession(const ::sockaddr_in& remote);
SecureSession* activeSecureSession();
bool wrapSecurePacket(SecureSession& session, const std::vector<uint8_t>& inner,
std::vector<uint8_t>* wrapped);
bool wrapSecureRoutingPacket(const std::vector<uint8_t>& inner,
std::vector<uint8_t>* wrapped);
bool decryptSecureWrapper(SecureSession& session, const uint8_t* body, size_t len,
std::vector<uint8_t>* inner);
bool decryptSecureRoutingWrapper(const uint8_t* body, size_t len,
std::vector<uint8_t>* inner);
bool verifySecureSessionAuth(SecureSession& session, const uint8_t* packet,
size_t len, uint8_t* status);
bool secureCredentialsReady() const;
uint8_t nextTunnelChannelId() const;
uint16_t effectiveTunnelAddressForSlot(size_t slot) const;
void pruneStaleTunnelClients();
@@ -366,6 +452,10 @@ class GatewayKnxTpIpRouter {
TunnelClient* response_client, uint16_t response_service,
const uint8_t* suppress_routing_echo = nullptr,
size_t suppress_routing_echo_len = 0);
bool handleOamRouterTunnelFrame(const uint8_t* data, size_t len,
TunnelClient* response_client, uint16_t response_service,
const uint8_t* suppress_routing_echo = nullptr,
size_t suppress_routing_echo_len = 0);
bool handleOpenKnxBusFrame(const uint8_t* data, size_t len);
bool transmitOpenKnxTpFrame(const uint8_t* data, size_t len);
void selectOpenKnxNetworkInterface(const ::sockaddr_in& remote);
@@ -380,13 +470,16 @@ class GatewayKnxTpIpRouter {
void pollProgrammingButton();
void updateProgrammingLed();
void setProgrammingLed(bool on);
void setOamProgrammingLed(bool on);
GatewayKnxBridge& bridge_;
GroupWriteHandler group_write_handler_;
GroupObjectWriteHandler group_object_write_handler_;
RoutingSequenceStoreHandler routing_sequence_store_handler_;
std::string openknx_namespace_;
GatewayKnxConfig config_;
std::unique_ptr<openknx::EtsDeviceRuntime> ets_device_;
std::unique_ptr<openknx::OamRouterRuntime> oam_router_;
TaskHandle_t task_handle_{nullptr};
SemaphoreHandle_t openknx_lock_{nullptr};
SemaphoreHandle_t startup_semaphore_{nullptr};
@@ -403,14 +496,22 @@ class GatewayKnxTpIpRouter {
TickType_t network_refresh_tick_{0};
std::array<TcpClient, kMaxTcpClients> tcp_clients_{};
std::array<TunnelClient, kMaxTunnelClients> tunnel_clients_{};
std::array<SecureSession, kMaxTcpClients> secure_sessions_{};
std::unique_ptr<IpParameterObject> knx_ip_parameters_;
uint8_t last_tunnel_channel_id_{0};
uint16_t last_secure_session_id_{0};
uint16_t active_secure_session_id_{0};
GatewayKnxIpSecureCredentialMaterial oam_ip_secure_credentials_{};
bool tp_uart_online_{false};
bool commissioning_only_{false};
std::atomic_bool openknx_configured_{false};
bool programming_button_last_pressed_{false};
bool programming_led_state_{false};
TickType_t programming_button_last_toggle_tick_{0};
bool oam_programming_mode_{false};
bool oam_programming_button_last_pressed_{false};
bool oam_programming_led_state_{false};
TickType_t oam_programming_button_last_toggle_tick_{0};
std::string last_error_;
};
@@ -32,6 +32,22 @@ constexpr const char* kTag = "gateway_knx";
#define CONFIG_GATEWAY_KNX_OEM_HARDWARE_ID 0xA401
#endif
#ifndef CONFIG_GATEWAY_KNX_OAM_ROUTER_OEM_MANUFACTURER_ID
#define CONFIG_GATEWAY_KNX_OAM_ROUTER_OEM_MANUFACTURER_ID 0x00FA
#endif
#ifndef CONFIG_GATEWAY_KNX_OAM_ROUTER_APPLICATION_NUMBER
#define CONFIG_GATEWAY_KNX_OAM_ROUTER_APPLICATION_NUMBER 0xA11F
#endif
#ifndef CONFIG_GATEWAY_KNX_OAM_ROUTER_APPLICATION_VERSION
#define CONFIG_GATEWAY_KNX_OAM_ROUTER_APPLICATION_VERSION 0x07
#endif
#ifndef CONFIG_GATEWAY_KNX_OAM_ROUTER_HARDWARE_ID
#define CONFIG_GATEWAY_KNX_OAM_ROUTER_HARDWARE_ID 0x0001
#endif
inline constexpr uint16_t kReg1DaliManufacturerId =
static_cast<uint16_t>(CONFIG_GATEWAY_KNX_OEM_MANUFACTURER_ID);
inline constexpr uint16_t kReg1DaliHardwareId =
@@ -56,6 +72,33 @@ inline constexpr uint8_t kReg1DaliProgramVersion[5] = {
static_cast<uint8_t>(kReg1DaliApplicationNumber & 0xff),
kReg1DaliApplicationVersion};
inline constexpr uint32_t kReg1DaliSerialMacIncrement = 0;
inline constexpr uint32_t kOamRouterSerialMacIncrement = 1;
inline constexpr uint16_t kOamRouterDeviceDescriptor = 0x091A;
inline constexpr uint16_t kOamRouterManufacturerId =
static_cast<uint16_t>(CONFIG_GATEWAY_KNX_OAM_ROUTER_OEM_MANUFACTURER_ID);
inline constexpr uint16_t kOamRouterHardwareId =
static_cast<uint16_t>(CONFIG_GATEWAY_KNX_OAM_ROUTER_HARDWARE_ID);
inline constexpr uint16_t kOamRouterApplicationNumber =
static_cast<uint16_t>(CONFIG_GATEWAY_KNX_OAM_ROUTER_APPLICATION_NUMBER);
inline constexpr uint8_t kOamRouterApplicationVersion =
static_cast<uint8_t>(CONFIG_GATEWAY_KNX_OAM_ROUTER_APPLICATION_VERSION);
inline constexpr uint8_t kOamRouterHardwareType[6] = {
0x00,
0x00,
static_cast<uint8_t>((kOamRouterHardwareId >> 8) & 0xff),
static_cast<uint8_t>(kOamRouterHardwareId & 0xff),
kOamRouterApplicationVersion,
0x00};
inline constexpr uint8_t kOamRouterOrderNumber[10] = {
'I', 'P', '-', 'R', 'o', 'u', 't', 'e', 'r', 0};
inline constexpr uint8_t kOamRouterProgramVersion[5] = {
static_cast<uint8_t>((kOamRouterManufacturerId >> 8) & 0xff),
static_cast<uint8_t>(kOamRouterManufacturerId & 0xff),
static_cast<uint8_t>((kOamRouterApplicationNumber >> 8) & 0xff),
static_cast<uint8_t>(kOamRouterApplicationNumber & 0xff),
kOamRouterApplicationVersion};
// RAII semaphore guard.
class SemaphoreGuard {
public:
@@ -0,0 +1,63 @@
#pragma once
#include "esp_idf_platform.h"
#include "ets_memory_loader.h"
#include "esp_netif.h"
#include "freertos/FreeRTOS.h"
#include "knx/cemi_frame.h"
#include "knx/device_object.h"
#include "knx/platform.h"
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#if defined(ENABLE_BAU091A_PERSONA)
#include "knx/bau091A.h"
#endif
namespace gateway::openknx {
class OamRouterRuntime {
public:
using CemiFrameSender = std::function<void(const uint8_t* data, size_t len)>;
OamRouterRuntime(std::string nvs_namespace,
uint16_t fallback_individual_address,
uint16_t tunnel_client_address = 0);
~OamRouterRuntime();
bool available() const;
uint16_t individualAddress() const;
uint16_t tunnelClientAddress() const;
bool configured() const;
bool programmingMode() const;
void setProgrammingMode(bool enabled);
void toggleProgrammingMode();
EtsMemorySnapshot snapshot() const;
DeviceObject* deviceObject();
Platform* platform();
void setNetworkInterface(esp_netif_t* netif);
bool handleTunnelFrame(const uint8_t* data, size_t len, CemiFrameSender sender);
void loop();
private:
static bool HandleOutboundCemiFrame(CemiFrame& frame, void* context);
static void EmitTunnelFrame(CemiFrame& frame, void* context);
static uint16_t DefaultTunnelClientAddress(uint16_t individual_address);
bool shouldConsumeTunnelFrame(CemiFrame& frame) const;
std::string nvs_namespace_;
CemiFrameSender sender_;
#if defined(ENABLE_BAU091A_PERSONA)
EspIdfPlatform platform_;
Bau091A device_;
#endif
};
} // namespace gateway::openknx