UDP Component¶
The purpose of this component is to allow ESPHome nodes to directly communicate with each over an IP network. It permits the state of sensors and binary sensors to be broadcast via UDP packets to other nodes on the same LAN, or to specific IP addresses (which may be in remote, but reachable networks).
Nodes may be providers which 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 entry
udp:
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: some-device-name
encryption: "REPLACEME with some key"
sensor:
- platform: udp
provider: some-device-name
id: local_sensor_id
remote_id: some_sensor_id
binary_sensor:
- platform: udp
provider: unencrypted-device
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.
port (Optional, int): The destination UDP port number to use. Defaults to
18511
.addresses (Optional, list of IPv4 addresses): One or more IP addresses to broadcast data to. Defaults to
255.255.255.255
which is the local network broadcast address.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.
When receiving data in such a configuration, sensors need an udp_id
configuration item to know where to expect data to come from.
Reliability¶
UDP, like any other network protocol, does not provide a guarantee that data will be delivered, but unlike TCP it does not
even provide any indication whether data has been successfully delivered or not. When any of the configured sensors changes state,
the component will broadcast that sensor’s state, but since this may not be delivered to a consumer, the UDP component
also broadcasts all sensor data on a timed schedule, set by update_interval
. Even this does not guarantee
delivery, but in practice unless the network has failed, updates will eventually be delivered, albeit possibly after
some delay.
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
udp:
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: udp
provider: device-2
id: relay2_sensor
on_press:
switch.turn_on: relay1
on_release:
switch.turn_off: relay1
# Device 2
esphome:
name: device-2
udp:
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: udp
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.
udp:
update_interval: 60s
addresses: ["10.87.135.110"]
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: udp
provider: st7735s
id: tick_tock
- platform: udp
provider: room-lights
id: relay1_sensor
sensor:
- platform: udp
provider: st7735s
id: wifi_signal_sensor
The example below shows a provider device separating data sent to different consumers. There are two provider confgurations, with different IDs.
The udp_internal
provider broadcasts the selected sensor states in plain every 10 seconds to all the network members, while the udp_external
provider sends other sensors data to an external IP address and port, with encryption. The node also listens to data from a remote-node
through
the port specified in the udp_external
configuration:
udp:
- id: udp_internal
update_interval: 10s
sensors:
- temp_outdoor
- temp_rooma
- temp_roomb
- temp_roomc
- temp_garage
- temp_water
- humi_rooma
- humi_roomb
- humi_roomc
- id: udp_external
update_interval: 60s
encryption: "Muddy Waters"
ping_pong_enable: true
rolling_code_enable: true
port: 38512
addresses:
- 10.87.135.110
binary_sensors:
- binary_sensor_door
sensors:
- temp_outdoor
binary_sensor:
- platform: udp
id: binary_sensor_unlock
udp_id: udp_external
provider: remote-node
remote_id: binary_sensor_unlock_me
on_press:
- lambda: |-
ESP_LOGI("main", "d command to binary_sensor_unlock");