Skip to content

Commit

Permalink
[knx] add initial support for KNX secure [WIP], openhab#8872
Browse files Browse the repository at this point in the history
* add support for KNX IP Secure, new options SECURETUNNEL and SECUREROUTER
* add config options for keyring file and password, and credentials for
  secure connections
* add passive (listening only) access for KNX data secure frames
* add tests for security functions
* add useCEMI option for newer serial devices like KNX RF sticks,
  kBerry, etc.
* update user documentation

Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
  • Loading branch information
holgerfriedrich committed Feb 16, 2022
1 parent f70290a commit b389477
Show file tree
Hide file tree
Showing 21 changed files with 931 additions and 49 deletions.
44 changes: 44 additions & 0 deletions .github/workflows/build-knx-addon.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven

name: Build knx addon with Maven

on:
push:
branches: [ pr-knx-data-secure ]

jobs:
build320:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up Java 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Build with Maven
run: mvn -B package --file pom.xml -pl :org.openhab.binding.knx -Dohc.version=3.2.0
- uses: actions/upload-artifact@v2
with:
name: org.openhab.binding.knx.320
path: bundles/org.openhab.binding.knx/target/org.openhab.binding.knx-*.jar

build31x:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up Java 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Build with Maven
run: mvn -B package --file pom.xml -pl :org.openhab.binding.knx
- uses: actions/upload-artifact@v2
with:
name: org.openhab.binding.knx.33x
path: bundles/org.openhab.binding.knx/target/org.openhab.binding.knx-*.jar

33 changes: 32 additions & 1 deletion bundles/org.openhab.binding.knx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The IP Gateway is the most commonly used way to connect to the KNX bus. At its b

| Name | Required | Description | Default value |
|---------------------|--------------|--------------------------------------------------------------------------------------------------------------|------------------------------------------------------|
| type | Yes | The IP connection type for connecting to the KNX bus (`TUNNEL` or `ROUTER`) | - |
| type | Yes | The IP connection type for connecting to the KNX bus (`TUNNEL`, `ROUTER`, `SECURETUNNEL` or `SECUREROUTER`) | - |
| ipAddress | for `TUNNEL` | Network address of the KNX/IP gateway. If type `ROUTER` is set, the IPv4 Multicast Address can be set. | for `TUNNEL`: \<nothing\>, for `ROUTER`: 224.0.23.12 |
| portNumber | for `TUNNEL` | Port number of the KNX/IP gateway | 3671 |
| localIp | No | Network address of the local host to be used to set up the connection to the KNX/IP gateway | the system-wide configured primary interface address |
Expand All @@ -52,6 +52,7 @@ The *serial* bridge accepts the following configuration parameters:
| responseTimeout | N | Timeout in seconds to wait for a response from the KNX bus | 10 |
| readRetriesLimit | N | Limits the read retries while initialization from the KNX bus | 3 |
| autoReconnectPeriod | N | Seconds between connect retries when KNX link has been lost, 0 means never retry | 0 |
| useCemi | N | Use newer CEMI message format, could be useful for newer devices like KNX RF sticks, kBerry, etc. | false |

## Things

Expand Down Expand Up @@ -199,6 +200,36 @@ Each configuration parameter has a `mainGA` where commands are written to and op
The `dpt` element is optional. If ommitted, the corresponding default value will be used (see the channel descriptions above).


## KNX Secure

> Note: Support for KNX Secure is partly implemented for openHAB and should be considered as experimental.
### KNX IP Secure

KNX IP Secure protects the traffic between openHAB and your KNX installation. It requires either a KNX Secure Router or a Secure IP Interface with security features enabled in ETS tool.

For *Secure routing* mode, the so called `backbone key` needs to be configured in openHAB. It is created by the ETS tool and cannot be changed via the ETS user interface. There are two possible ways to provide the key to openHAB:

- The backbone key can be extracted Security report (ETS, Reports, Security, look for a 32-digit key) and specified in parameter `backboneKey`.
- The backbone key is included in ETS keyring export (ETS, project settings, export keyring). Keyring file is configured using `keyringFile` (put it in `config\misc` folder of the openHAB installation) and also requires `keyringPasswort`.

For *Secure tunneling* with a Secure IP Interface (or a router in tunneling mode), more parameters are required. A unique device authentication key, and a specific tunnel identifier and password need to be available. It can be provided to openHAB in two different ways:

- All information can be looked up in ETS and provided separately: `tunnelDevAuth`, `tunnelPasswort`. `tunnelUser` is a number which is not directly visible in ETS, but can be looked up in keyring export or deduced (typically 2 for the first tunnel of a device, 3 for the second one, ...)
- All necessary information is included in ETS keyring export (ETS, project settings, export keyring). Keyring file is configured using `keyringFile` (put it in `config\misc` folder of the openHAB installation) and `keyringPasswort`. In addition, `tunnelSourceAddr` needs to be set to uniquely identify the tunnel in use.


### KNX Data Secure

Data secure protects the content of messages on the KNX bus. In a KNX installation, both classic and secure group addresses can coexist.

openHAB typically ignores messages with secure group addresses, unless data secure is configured.

> NOTE: openHAB currently does fully support passive (listening) access to secure group addresses. Write access to secured GAs is done via the "GO diagnostics" feature described in KNX AN170 and is currently limited. Expect a timeout if a data value is written too often. Initial/periodic read will fail, avoid automatic read (< in thing definition).
All necessary information to decode secure group addresses is included in ETS keyring export (ETS, project settings, export keyring). Keyring file is configured using `keyringFile` (put it in `config\misc` folder of the openHAB installation) and also requires `keyringPasswort`.


## Examples

The following two templates are sufficient for almost all purposes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ public class KNXBindingConstants {
public static final String LOCAL_SOURCE_ADDRESS = "localSourceAddr";
public static final String PORT_NUMBER = "portNumber";
public static final String SERIAL_PORT = "serialPort";
public static final String USE_CEMI = "useCemi";
public static final String KEYRING_FILE = "keyringFile";
public static final String KEYRING_PASSWORD = "keyringPassword";
public static final String ROUTER_BACKBONE_GROUP_KEY = "routerBackboneGroupKey";
public static final String TUNNEL_USER_ID = "tunnelUserId";
public static final String TUNNEL_USER_PASSWORD = "tunnelUserPassword";
public static final String TUNNEL_DEVICE_AUTHENTICATION = "tunnelDeviceAuthentication";
public static final String TUNNEL_SOURCE_ADDRESS = "tunnelSourceAddress";

// The default multicast ip address (see <a
// href="http://www.iana.org/assignments/multicast-addresses/multicast-addresses.xml">iana</a> EIBnet/IP
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import org.openhab.core.types.Type;

import tuwien.auto.calimero.datapoint.Datapoint;
import tuwien.auto.calimero.process.ProcessEvent;

/**
* This interface must be implemented by classes that provide a type mapping
Expand Down Expand Up @@ -45,7 +44,8 @@ public interface KNXTypeMapper {
* maps a datapoint value to an openHAB command or state
*
* @param datapoint the source datapoint
* @param data the datapoint value as an ASDU byte array (see <code>{@link ProcessEvent}.getASDU()</code>)
* @param data the datapoint value as an ASDU byte array (see
* <code>{@link tuwien.auto.calimero.process.ProcessEvent}.getASDU()</code>)
* @return a command or state of openHAB
*/
@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.knx.internal.client.InboundSpec;
import org.openhab.binding.knx.internal.client.OutboundSpec;

import tuwien.auto.calimero.GroupAddress;
import tuwien.auto.calimero.KNXFormatException;
Expand Down Expand Up @@ -57,7 +55,8 @@ protected final GroupAddress toGroupAddress(GroupAddressConfiguration ga) {
/**
* Return the data point type.
* <p>
* See {@link InboundSpec#getDPT()} and {@link OutboundSpec#getDPT()}.
* See {@link org.openhab.binding.knx.internal.client.InboundSpec#getDPT()} and
* {@link org.openhab.binding.knx.internal.client.OutboundSpec#getDPT()}.
*
* @return the data point type.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import tuwien.auto.calimero.process.ProcessCommunicatorImpl;
import tuwien.auto.calimero.process.ProcessEvent;
import tuwien.auto.calimero.process.ProcessListener;
import tuwien.auto.calimero.secure.KnxSecureException;
import tuwien.auto.calimero.secure.SecureApplicationLayer;
import tuwien.auto.calimero.secure.Security;

Expand Down Expand Up @@ -91,6 +92,9 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
private final Set<GroupAddressListener> groupAddressListeners = new CopyOnWriteArraySet<>();
private final LinkedBlockingQueue<ReadDatapoint> readDatapoints = new LinkedBlockingQueue<>();

private boolean firstConnect;
private long lastDisconnectSysMillis;

@FunctionalInterface
private interface ListenerNotification {
void apply(BusMessageListener listener, IndividualAddress source, GroupAddress destination, byte[] asdu);
Expand All @@ -101,6 +105,7 @@ private interface ListenerNotification {

@Override
public void detached(DetachEvent e) {
lastDisconnectSysMillis = System.currentTimeMillis();
logger.debug("The KNX network link was detached from the process communicator");
}

Expand Down Expand Up @@ -135,6 +140,8 @@ public AbstractKNXClient(int autoReconnectPeriod, ThingUID thingUID, int respons
this.readRetriesLimit = readRetriesLimit;
this.knxScheduler = knxScheduler;
this.statusUpdateCallback = statusUpdateCallback;
firstConnect = true;
lastDisconnectSysMillis = System.currentTimeMillis();
}

public void initialize() {
Expand All @@ -145,7 +152,9 @@ public void initialize() {

private boolean scheduleReconnectJob() {
if (autoReconnectPeriod > 0) {
connectJob = knxScheduler.schedule(this::connect, autoReconnectPeriod, TimeUnit.SECONDS);
// schedule connect job, for the first connection ignore autoReconnectPeriod and use 1 sec
connectJob = knxScheduler.schedule(this::connect, firstConnect ? 1 : autoReconnectPeriod, TimeUnit.SECONDS);
firstConnect = false;
return true;
} else {
return false;
Expand Down Expand Up @@ -173,6 +182,10 @@ private synchronized boolean connect() {
if (isConnected()) {
return true;
}
long now = System.currentTimeMillis();
if ((now - lastDisconnectSysMillis) < 1000) {
logger.debug("fast reconnect");
}
try {
releaseConnection();

Expand Down Expand Up @@ -206,7 +219,7 @@ private synchronized boolean connect() {
statusUpdateCallback.updateStatus(ThingStatus.ONLINE);
connectJob = null;
return true;
} catch (KNXException | InterruptedException e) {
} catch (KNXException | InterruptedException | KnxSecureException e) {
logger.debug("Error connecting to the bus: {}", e.getMessage(), e);
disconnect(e);
scheduleReconnectJob();
Expand All @@ -215,11 +228,12 @@ private synchronized boolean connect() {
}

private void disconnect(@Nullable Exception e) {
lastDisconnectSysMillis = System.currentTimeMillis();

releaseConnection();
if (e != null) {
String message = e.getLocalizedMessage();
statusUpdateCallback.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
message != null ? message : "");
"" + e.getLocalizedMessage());
} else {
statusUpdateCallback.updateStatus(ThingStatus.OFFLINE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
public class CustomKNXNetworkLinkIP extends KNXNetworkLinkIP {

public static final int TUNNELING = KNXNetworkLinkIP.TUNNELING;
public static final int TUNNELINGV2 = KNXNetworkLinkIP.TunnelingV2;
public static final int ROUTING = KNXNetworkLinkIP.ROUTING;

CustomKNXNetworkLinkIP(final int serviceMode, KNXnetIPConnection conn, KNXMediumSettings settings)
Expand Down
Loading

0 comments on commit b389477

Please sign in to comment.