Packet Transport Component¶
The purpose of this component is to allow ESPHome nodes to directly communicate with each over a communication channel. It permits the state of sensors and binary sensors to be transmitted from one node to another, without the need for a central server or broker. The actual transport channel is provided by another component. Currently the supported transports are UDP Component and UART Bus.
Nodes may be providers which transmit or broadcast sensor data, or consumers which receive sensor data from one or more providers. A node may be both a provider and a consumer. Optional security is provided by one or more of:
encryption using a shared secret key
a rolling code
a challenge-response (ping-pong) key
Example Configuration¶
# Example configuration entry
packet_transport:
platform: ...
update_interval: 5s
encryption: "REPLACEME"
rolling_code_enable: true
binary_sensors:
- binary_sensor_id1
sensors:
- sensor_id1
- id: sensor_id2
broadcast_id: different_id
providers:
- name: device1-name
encryption: "REPLACEME"
sensor:
- platform: packet_transport
provider: device1-name
id: local_sensor_id
remote_id: some_sensor_id
binary_sensor:
- platform: packet_transport
provider: device2-name
id: other_binary_sensor_id # also used as remote_id
Configuration variables:¶
id (Optional, ID): Manually specify the ID used for code generation.
update_interval (Optional, Time): Interval between full broadcasts. Defaults to 15s.
sensors (Optional, list): A list of sensor IDs to be broadcast. Each entry may be just the sensor id, or may set a different id to be broadcast.
id (Required, ID): The id of the sensor to be used
broadcast_id (Optional, string): The id to be used for this sensor in the broadcast. Defaults to the same as the internal id.
binary_sensors (Optional, list): A list of binary sensor IDs to be broadcast.
id (Required, ID): The id of the binary sensor to be used
broadcast_id (Optional, string): The id to be used for this binary sensor in the broadcast. Defaults to the same as the internal id.
encryption (Optional, string): The encryption key to use when broadcasting. Default is no encryption. This may be any string, and will be hashed to form a 256 bit key.
rolling_code_enable (Optional, boolean): Enables a rolling code to be included in all broadcasts. Requires
encryption
to be set. Defaults tofalse
. Can be set only on the provider side.ping_pong_enable (Optional, boolean): When set, requires encrypted providers to include a nonce generated by this device in broadcasts. Defaults to
false
. Can be set only on the consumer side.ping_pong_recycle_time (Optional, Time): Controls how often the ping-pong key is regenerated. Requires
ping_pong_enable
to be set. Defaults to 10 minutes. Can be set only on the consumer side.providers (Optional, list): A list of provider device names and optionally their secret encryption keys.
name (Required, string): The device name of the provider.
encryption (Optional, string): The provider’s encryption key.
Wherever a provider name is required, this should be the node name configured in the esphome:
block.
This component supports multiple configurations, making it possible to differentiate between consumers when providing data to them, or providers if they are multiple.
When receiving data in such a configuration, sensors need a transport_id
configuration item to know where to expect data to come from.
Reliability¶
The reliability of the transmission is dependent on the underlying transport.
Security¶
By default there is no security - all data is transmitted in clear text on the network. This would be appropriate for non-sensitive sensor data or perhaps on a fully secured wired network. For other cases the data can be encrypted by providing an encryption key, which is shared between the provider and consumer.
Encryption alone ensures that data cannot be read in transit and protects against spoofing of data, but does not protect against replay attacks (where a threat actor records a transmission and replays it later, e.g. to repeat an action.)
A rolling code can be enabled which mitigates replay attacks - each transmission contains a 64 bit value which is guaranteed to monotonically increase, so the consumer will reject any data which contains a rolling code already seen. The rolling code also ensures that the data in every packet is different, which makes brute-force attacks on the encryption much more difficult. This is enabled in the provider configuration and adds minor overhead.
Note
The rolling code’s upper 32 bit field is incremented and written to flash once at reboot on the provider node. It’s also incremented and written to flash when the lower 32 bit field overflows, which can only happen after a very long time. The consumer side does not store the d rolling codes in flash.
For further protection a ping-pong
(or challenge-response) facility is available, which can be enabled in the
consumer configuration. The consumer periodically generates a 32 bit random number (a nonce aka “Number used Once”)
and broadcasts it as a ping. Any provider receiving this nonce will include it in any future encrypted broadcasts as
pong. The consumer expects to get back its most recently transmitted ping in any packets it receives, and will reject
any that do not contain it.
Use of the ping-pong feature will add to network traffic and the size of the transmitted packets (a single packet may include up to 4 nonces from different devices) but provides a high level of protection against replay attacks. It does require a 2-way network connection, and it only works on local networks because the consumer can only broadcast the nonce to the providers.
Note
Occasionally a Ping key not seen
warning message may appear in the device log. This is expected, because it may
happen that while the consumer has regenerated the ping key, it subsequently received a pong with the previous key,
most likely because the messages crossed in transit. In such a case, the message will be rejected, but the next message
will contain the correct pong.
Because of this, ping-pong
is only recommended to be used for state transmissions, which are updated periodically
at update_interval
.
Security considerations
The encryption used is XXTEA which is fast and compact. Although XXTEA is known to be susceptible to a chosen-plaintext attack, such an attack is not possible with this application, and it otherwise has no published weaknesses [1]. The implementation used here has been modified slightly to use a 256 bit key which will strengthen security compared to the original 128 bit key.
When encryption is used, all data is encrypted except the sender node name, and the initial request for a ping-pong key. Broadcasting names does not compromise security, since this information would already be available via mDNS. Requesting a key in clear text does not reduce the security of the key, since it is the ability to encrypt this key with the shared secret key that provides the security assurance.
This does mean however that there is a possible Denial of Service attack by a malicious node overwriting a valid ping-pong key, which will result in packets being rejected by the legitimate consumer.
Configuration examples¶
This example couples two light switches in two different devices, so that switching either one on or off will cause the other to follow suit. In each case a template binary_sensor is used to mirror the switch state.
# Device 1
esphome:
name: device-1
packet_transport:
binary_sensors:
- relay1_sensor
switch:
- platform: gpio
pin: GPIO6
id: relay1
name: "Device 1 switch"
binary_sensor:
- platform: template
id: relay1_sensor
lambda: "return id(relay1).state;"
- platform: packet_transport
provider: device-2
id: relay2_sensor
on_press:
switch.turn_on: relay1
on_release:
switch.turn_off: relay1
# Device 2
esphome:
name: device-2
packet_transport:
binary_sensors:
- relay2_sensor
switch:
- platform: gpio
pin: GPIO6
id: relay2
name: "Device 2 switch"
binary_sensor:
- platform: template
id: relay2_sensor
lambda: "return id(relay2).state;"
- platform: packet_transport
provider: device-1
id: relay1_sensor
on_press:
switch.turn_on: relay2
on_release:
switch.turn_off: relay2
The following example shows a device using encryption to read a sensor and two binary sensors from two different devices, one with encryption and ping-pong and one without. It also rebroadcasts one of those binary sensors with its own encryption and a rolling code to a remote host.
packet_transport:
update_interval: 60s
ping_pong_enable: true
rolling_code_enable: true
encryption: "Muddy Waters"
binary_sensors:
- tick_tock
providers:
- name: st7735s
encryption: "Blind Willie Johnson"
# - name: room-lights # Not required here since no encryption
binary_sensor:
- platform: packet_transport
provider: st7735s
id: tick_tock
- platform: packet_transport
provider: room-lights
id: relay1_sensor
sensor:
- platform: packet_transport
provider: st7735s
id: wifi_signal_sensor
In the example below we differentiate channels we establish over UDP, by providing multiple configuration keys.
We provide the value of sensor_to_provide
encrypted, to a node with a specific IP address, and we receive the
state of relay1_sensor
from tne node named device-1
without any encryption.
udp:
- id: udp_output
addresses:
- 192.168.1.78 # the IP of the specific node to provide the sensor value to
- id: udp_input
packet_transport:
- platform: udp
id: transport_output
udp_id: udp_output
encryption: !secret your_encryption_key
sensors:
- sensor_to_provide
- platform: udp
id: transport_input
udp_id: udp_input
providers:
- name: device-1
sensor:
- platform: ...
id: sensor_to_provide
binary_sensor:
- platform: packet_transport
transport_id: transport_input
provider: device-1
remote_id: relay1_sensor