ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
udp_component.cpp
Go to the documentation of this file.
2#ifdef USE_NETWORK
3#include "esphome/core/log.h"
6#include "udp_component.h"
7
8namespace esphome::udp {
9
10ESPHOME_LOG_TAG(TAG, "udp");
11static constexpr uint32_t NETWORK_POLL_ID = 0;
12static constexpr uint32_t NETWORK_POLL_INTERVAL_MS = 1000;
13
15 size_t address_count = 0;
16 for (const char *const *it = this->addresses_; it != nullptr && *it != nullptr; it++)
17 address_count++;
18 this->sockaddrs_.init(address_count);
19 for (const char *const *it = this->addresses_; it != nullptr && *it != nullptr; it++) {
20 const char *address = *it;
21 SockaddrEntry entry{};
22 if (socket::set_sockaddr(&entry.sa, sizeof(entry), address, this->broadcast_port_) == 0) {
23 ESP_LOGW(TAG, "Invalid address %s", address);
24 // A dropped address silently receives nothing; surface the misconfiguration
25 this->status_set_warning(LOG_STR("invalid address"));
26 continue;
27 }
28 this->sockaddrs_.push_back(entry);
29 }
30 // set up broadcast socket
31 if (this->should_broadcast_) {
32#ifdef USE_UDP_IPV6_SEND
33 if (this->ipv4_send_)
34#endif
35 {
36 this->broadcast_socket_ = socket::socket_udp_send(AF_INET, IPPROTO_IP);
37 if (this->broadcast_socket_ == nullptr) {
38 this->status_set_error(LOG_STR("Could not create socket"));
39 this->mark_failed();
40 return;
41 }
42 int enable = 1;
43 auto err = this->broadcast_socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
44 if (err != 0) {
45 this->status_set_warning(LOG_STR("Socket unable to set reuseaddr"));
46 // we can still continue
47 }
48 err = this->broadcast_socket_->setsockopt(SOL_SOCKET, SO_BROADCAST, &enable, sizeof(int));
49 if (err != 0) {
50 this->status_set_warning(LOG_STR("Socket unable to set broadcast"));
51 }
52 }
53#ifdef USE_UDP_IPV6_SEND
54 if (this->ipv6_send_) {
55 this->send_socket_v6_ = socket::socket_udp_send(AF_INET6, IPPROTO_IP);
56 if (this->send_socket_v6_ == nullptr) {
57 this->status_set_error(LOG_STR("Could not create IPv6 socket"));
58 this->mark_failed();
59 return;
60 }
61 // Unicast needs no egress interface; multicast does
62 if (this->ipv6_multicast_)
64 }
65#endif
66 }
67 // create listening socket if we either want to subscribe to providers, or need to listen
68 // for ping key broadcasts.
69 if (this->should_listen_) {
70 struct sockaddr_storage server{};
71 socklen_t server_len;
72 if (this->listen_address_ != nullptr) {
73 server_len =
74 socket::set_sockaddr((struct sockaddr *) &server, sizeof(server), this->listen_address_, this->listen_port_);
75 // A multicast join needs a socket of the group's address family
76 this->listen_socket_ = socket::socket_udp_loop_monitored(server.ss_family, IPPROTO_IP);
77 this->poll_pending_ |= POLL_JOIN;
78#ifdef USE_SOCKET_IPV6
79 // An IPv6 group binds after the join, which supplies the scope id link-local groups need
80 if (server.ss_family == AF_INET6)
81 this->poll_pending_ |= POLL_BIND;
82#endif
83 } else {
84#ifdef USE_SOCKET_IPV6
85 server_len = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->listen_port_);
86 this->listen_socket_ = socket::socket_udp_loop_monitored(AF_INET6, IPPROTO_IP);
87 // Clear IPV6_V6ONLY so IPv4 datagrams also arrive, as mapped addresses
88 int v6only = 0;
89 if (this->listen_socket_ != nullptr &&
90 this->listen_socket_->setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only)) != 0) {
91 ESP_LOGW(TAG, "Failed to clear IPV6_V6ONLY: errno %d", errno);
92 this->status_set_warning(LOG_STR("IPv4 datagrams may not be received"));
93 }
94#else
95 // Must stay IPv4: raw lwIP can have USE_NETWORK_IPV6 without IPv6 sockets
96 server_len = socket::set_sockaddr((struct sockaddr *) &server, sizeof(server), "0.0.0.0", this->listen_port_);
97 this->listen_socket_ = socket::socket_udp_loop_monitored(AF_INET, IPPROTO_IP);
98#endif
99 }
100 if (this->listen_socket_ == nullptr) {
101 this->status_set_error(LOG_STR("Could not create socket"));
102 this->mark_failed();
103 return;
104 }
105 auto err = this->listen_socket_->setblocking(false);
106 if (err < 0) {
107 ESP_LOGE(TAG, "Unable to set nonblocking: errno %d", errno);
108 this->status_set_error(LOG_STR("Unable to set nonblocking"));
109 this->mark_failed();
110 return;
111 }
112 int enable = 1;
113 err = this->listen_socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
114 if (err != 0) {
115 this->status_set_warning(LOG_STR("Socket unable to set reuseaddr"));
116 // we can still continue
117 }
118
119 if ((this->poll_pending_ & POLL_BIND) == 0) {
120 err = this->listen_socket_->bind((struct sockaddr *) &server, server_len);
121 if (err != 0) {
122 ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
123 this->status_set_error(LOG_STR("Unable to bind socket"));
124 this->mark_failed();
125 return;
126 }
127 }
128 }
129 if (this->poll_pending_ != 0) {
130 // Joins and the IPv6 egress interface need the network up; RP2040 has no interface before then
131 this->set_interval(NETWORK_POLL_ID, NETWORK_POLL_INTERVAL_MS, [this]() {
132 if (network::is_connected() && this->network_poll_())
133 this->cancel_interval(NETWORK_POLL_ID);
134 });
135 }
136}
137
139 bool ok = true;
140 if (this->poll_pending_ & POLL_JOIN) {
141#ifdef USE_SOCKET_IPV6
142 uint32_t *if_index = &this->mcast_ifindex_;
143#else
144 uint32_t *if_index = nullptr;
145#endif
146 if (socket::join_multicast_group(this->listen_socket_.get(), this->listen_address_, if_index)) {
147 ESP_LOGD(TAG, "Joined multicast %s", this->listen_address_);
148 this->poll_pending_ &= ~POLL_JOIN;
149 this->poll_failed_ &= ~POLL_JOIN;
150 } else {
151 this->set_poll_warning_(POLL_JOIN, LOG_STR("Failed to join multicast group"));
152 ok = false;
153 }
154 }
155#ifdef USE_SOCKET_IPV6
156 if (ok && (this->poll_pending_ & POLL_BIND)) {
157 struct sockaddr_storage server{};
158 socklen_t server_len =
159 socket::set_sockaddr((struct sockaddr *) &server, sizeof(server), this->listen_address_, this->listen_port_);
160 // POSIX bind() on a link-local group needs it; lwIP ignores it
161 reinterpret_cast<sockaddr_in6 *>(&server)->sin6_scope_id = this->mcast_ifindex_;
162 if (this->listen_socket_->bind((struct sockaddr *) &server, server_len) == 0) {
163 this->poll_pending_ &= ~POLL_BIND;
164 this->poll_failed_ &= ~POLL_BIND;
165 } else {
166 this->set_poll_warning_(POLL_BIND, LOG_STR("Unable to bind socket"));
167 ok = false;
168 }
169 }
170#endif
171#ifdef USE_UDP_IPV6_SEND
172 // Only an IPv6 group's join supplies the index, so a failing IPv4 join must not hold back sends
173 const bool index_pending = (this->poll_pending_ & (POLL_JOIN | POLL_BIND)) == (POLL_JOIN | POLL_BIND);
174 if ((this->poll_pending_ & POLL_MCAST_IF) && !index_pending) {
175 // Index 0 picks the first eligible interface
176 if (socket::set_ipv6_multicast_if(this->send_socket_v6_.get(), this->mcast_ifindex_)) {
177 this->poll_pending_ &= ~POLL_MCAST_IF;
178 this->poll_failed_ &= ~POLL_MCAST_IF;
179 } else {
180 this->set_poll_warning_(POLL_MCAST_IF, LOG_STR("Failed to set IPv6 multicast interface"));
181 ok = false;
182 }
183 }
184#endif
185 if (!ok)
186 return false;
187 if (this->poll_warning_) {
188 this->poll_warning_ = false;
189 this->status_clear_warning();
190 }
191 return true;
192}
193
194void UDPComponent::set_poll_warning_(PollStep step, const LogString *what) {
195 // Log the first failure of each step only; the poll keeps retrying every second
196 if (this->poll_failed_ & step)
197 return;
198 this->poll_failed_ |= step;
199 ESP_LOGW(TAG, "%s. Error %d", LOG_STR_ARG(what), errno);
200 // One warning bit: only a warning raised here is cleared on success, one from setup() stays
201 if (!this->status_has_warning()) {
202 this->status_set_warning(what);
203 this->poll_warning_ = true;
204 }
205}
206
208 if (!this->should_listen_) {
209 this->disable_loop();
210 return;
211 }
212 // One byte more than the largest packet, as a read cuts a longer datagram to fit and so cannot report it
213 std::array<uint8_t, MAX_PACKET_SIZE + 1> buf;
214 for (;;) {
215 if (!this->listen_socket_->ready())
216 break;
217 auto len = this->listen_socket_->read(buf.data(), buf.size());
218 if (len <= 0)
219 break;
220 size_t packet_len = static_cast<size_t>(len);
221 if (packet_len > MAX_PACKET_SIZE) {
222 ESP_LOGV(TAG, "Packet longer than %zu bytes dropped", MAX_PACKET_SIZE);
223 continue;
224 }
225 ESP_LOGV(TAG, "Received packet of length %zu", packet_len);
226 this->packet_listeners_.call(std::span<const uint8_t>(buf.data(), packet_len));
227 }
228}
229
231 ESP_LOGCONFIG(TAG,
232 "UDP:\n"
233 " Listen Port: %u\n"
234 " Broadcast Port: %u",
235 this->listen_port_, this->broadcast_port_);
236 for (const char *const *it = this->addresses_; it != nullptr && *it != nullptr; it++) {
237 const char *address = *it;
238 ESP_LOGCONFIG(TAG, " Address: %s", address);
239 }
240 if (this->listen_address_ != nullptr) {
241 ESP_LOGCONFIG(TAG, " Listen address: %s", this->listen_address_);
242 }
243 ESP_LOGCONFIG(TAG,
244 " Broadcasting: %s\n"
245 " Listening: %s",
246 YESNO(this->should_broadcast_), YESNO(this->should_listen_));
247}
248
249bool UDPComponent::send_packet(const uint8_t *data, size_t size) {
250 if (this->is_failed())
251 return false; // a send socket may be missing
252 bool sent = true;
253 for (const auto &entry : this->sockaddrs_) {
254#ifdef USE_UDP_IPV6_SEND
255 const bool v6 = entry.sa.sa_family == AF_INET6;
256 // Until the egress interface is set, a multicast packet would leave on the wrong one
257 if (v6 && (this->poll_pending_ & POLL_MCAST_IF) && entry.in6.sin6_addr.s6_addr[0] == 0xff) {
258 ESP_LOGV(TAG, "Egress interface not set yet, multicast packet dropped");
259 continue;
260 }
261 socket::UDPSendSocket *sock = v6 ? this->send_socket_v6_.get() : this->broadcast_socket_.get();
262 socklen_t sockaddr_size = v6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in);
263#else
264 socket::UDPSendSocket *sock = this->broadcast_socket_.get();
265 socklen_t sockaddr_size = sizeof(sockaddr_in);
266#endif
267 if (sock == nullptr) {
268 ESP_LOGW(TAG, "Broadcast socket not initialized");
269 return false;
270 }
271 auto result = sock->sendto(data, size, 0, &entry.sa, sockaddr_size);
272 if (result < 0) {
273 // lwIP refuses a packet with ENOMEM while the destination's ARP lookup is pending and its queue is full;
274 // the packet can be sent again later
275 if (errno == ENOMEM) {
276 ESP_LOGV(TAG, "sendto() out of memory");
277 } else {
278 ESP_LOGW(TAG, "sendto() error %d", errno);
279 }
280 sent = false;
281 }
282 }
283 return sent;
284}
285} // namespace esphome::udp
286
287#endif
uint8_t address
Definition bl0906.h:4
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:274
bool cancel_interval(const char *name)
Cancel an interval function.
Definition component.cpp:92
bool status_has_warning() const
Definition component.h:280
void disable_loop()
Disable this component's loop.
void set_interval(const char *name, uint32_t interval, std::function< void()> &&f)
Set an interval function with a const char* name.
Definition component.cpp:88
void status_clear_warning()
Definition component.h:296
ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen)
void set_poll_warning_(PollStep step, const LogString *what)
std::unique_ptr< socket::UDPSendSocket > send_socket_v6_
FixedVector< SockaddrEntry > sockaddrs_
LazyCallbackManager< void(std::span< const uint8_t >)> packet_listeners_
bool network_poll_()
One pass of the network poll; true once nothing is left to do.
std::unique_ptr< socket::UDPSendSocket > broadcast_socket_
const char *const * addresses_
std::unique_ptr< socket::UDPSocket > listen_socket_
bool send_packet(const uint8_t *data, size_t size)
Returns false if the packet could not be sent to every destination.
uint32_t socklen_t
Definition headers.h:109
ESPHOME_ALWAYS_INLINE bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
Definition util.h:36
std::unique_ptr< UDPSendSocket > socket_udp_send(int domain, int protocol)
Create a send-only UDP socket (socket_udp_send), a UDP socket with receive support (socket_udp),...
socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_address, uint16_t port)
Set a sockaddr to the specified address and port for the IP version used by socket_ip().
bool set_ipv6_multicast_if(Socket *sock, uint32_t if_index_in=0)
Configure the outgoing IPv6 multicast interface on the given socket.
bool join_multicast_group(UDPSocket *sock, const char *ip_address, uint32_t *if_index_out=nullptr)
Join a multicast group on the given socket.
socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port)
Set a sockaddr to the any address and specified port for the IP version used by socket_ip().
std::unique_ptr< UDPSocket > socket_udp_loop_monitored(int domain, int protocol)
Definition socket.h:136
ESPHOME_LOG_TAG(TAG, "udp.packet_interface")
const void size_t len
Definition hal.h:68
uint16_t size
Definition helpers.cpp:34
static void uint32_t