Compare commits

...

43 Commits

Author SHA1 Message Date
J. Nick Koston
6d2e1f8658 Merge branch 'dev' into socket-lwip-raw-udp 2026-05-13 01:36:28 -05:00
J. Nick Koston
e855ddb1f1 Merge branch 'dev' into socket-lwip-raw-udp 2026-04-22 08:22:37 +02:00
J. Nick Koston
e191f5fb4b Merge branch 'dev' into socket-lwip-raw-udp 2026-04-21 15:08:29 +02:00
J. Nick Koston
e7c126d3dc [socket] Rename UDP socket types so UDPSocket is the full send+recv type
Swap the UDP type naming so the unsuffixed UDPSocket is the full
send+recv socket and the send-only variant becomes UDPSendSocket. The
previous naming inverted reader expectations (UDPSocket sounded complete
but was send-only, UDPRecvSocket sounded limited but was the full one).

Public:
  UDPSocket       (was UDPRecvSocket)
  UDPSendSocket   (was UDPSocket)
  socket_udp                     (was socket_udp_recv)
  socket_ip_udp                  (was socket_ip_udp_recv)
  socket_udp_loop_monitored      (was socket_udp_recv_loop_monitored)
  socket_ip_udp_loop_monitored   (was socket_ip_udp_recv_loop_monitored)
  socket_udp_send                (was socket_udp)
  socket_ip_udp_send             (was socket_ip_udp)

Internal:
  LWIPRawUDPImpl       (was LWIPRawUDPRecvImpl)
  LWIPRawUDPSendImpl   (was LWIPRawUDPImpl)

No consumers exist yet, so this is a clean rename with no migration.
2026-04-10 18:40:14 -10:00
J. Nick Koston
af7b3821b8 Merge branch 'dev' into socket-lwip-raw-udp 2026-04-10 18:31:45 -10:00
J. Nick Koston
d6c48e2d64 [socket] Validate source address before copying data in recvfrom
Move pbuf_copy_partial after ip2sockaddr_ validation so the caller
buffer is not modified when the address conversion fails.
2026-04-09 09:21:48 -10:00
J. Nick Koston
c01699f2a4 [socket] Move socket_loop_monitored declaration before UDP factories
Fix forward declaration ordering: socket_udp_recv_loop_monitored
calls socket_loop_monitored, so the latter must be declared first.
2026-04-09 09:20:36 -10:00
J. Nick Koston
abc4069657 [socket] Restore LWIP thread safety documentation to common header 2026-04-09 09:09:38 -10:00
J. Nick Koston
c3827423ba [socket] Move thin UDP and listen factory wrappers inline into socket.h
All the _ip_ variants and non-LWIP_TCP delegators are one-liners
that just forward to other factory functions. Move them inline to
reduce socket.cpp and keep the dispatch logic visible in one place.
2026-04-09 09:09:17 -10:00
J. Nick Koston
41a8e7f61b [socket] Split lwip raw impl into tcp, udp, and common files
Extract UDP classes and shared helpers from lwip_raw_tcp_impl into
separate files for better organization:

- lwip_raw_common_impl.{h,cpp}: shared helpers (lwip_ip_to_sockaddr,
  sockaddr_to_lwip, lwip_bind_err)
- lwip_raw_udp_impl.{h,cpp}: LWIPRawUDPImpl, LWIPRawUDPRecvImpl,
  and UDP factory functions
- lwip_raw_tcp_impl.{h,cpp}: TCP-only code (LWIPRawCommon,
  LWIPRawImpl, LWIPRawListenImpl, TCP factories)

Update __init__.py FILTER_SOURCE_FILES to exclude all three lwip
raw files when not using the lwip_tcp implementation.
2026-04-09 09:06:04 -10:00
J. Nick Koston
0a3f8c6d67 [socket] Add unified UDP recv loop-monitored factory functions
Add socket_udp_recv_loop_monitored() and socket_ip_udp_recv_loop_monitored()
so consumers can create UDP recv sockets with loop wake support using a single
API across all platforms, without #ifdef guards.
2026-04-09 08:56:11 -10:00
J. Nick Koston
2375faee88 fixes, update 2026-04-09 08:54:08 -10:00
J. Nick Koston
3da3a66d09 Merge branch 'dev' into socket-lwip-raw-udp 2026-04-09 07:55:07 -10:00
J. Nick Koston
cf22559af0 Merge branch 'dev' into socket-lwip-raw-udp 2026-04-02 09:56:54 -10:00
J. Nick Koston
17c2557cca Merge branch 'dev' into socket-lwip-raw-udp 2026-04-01 18:47:13 -10:00
J. Nick Koston
012dadbf77 Merge branch 'dev' into socket-lwip-raw-udp 2026-03-22 21:27:55 -10:00
J. Nick Koston
97e4bb71c3 Merge branch 'dev' into socket-lwip-raw-udp 2026-03-18 19:31:21 -10:00
J. Nick Koston
b333bb76e4 Merge branch 'dev' into socket-lwip-raw-udp 2026-03-16 16:36:46 -10:00
J. Nick Koston
273637b6d7 tweak 2026-03-16 16:35:32 -10:00
J. Nick Koston
75efdd8662 tweaks 2026-03-16 11:01:21 -10:00
J. Nick Koston
86e4341a52 tweaks 2026-03-16 11:00:57 -10:00
J. Nick Koston
402398b389 tweaks 2026-03-16 10:55:33 -10:00
J. Nick Koston
dde81d3f63 tweaks 2026-03-16 10:51:54 -10:00
J. Nick Koston
eef806c806 Merge branch 'dev' into socket-lwip-raw-udp 2026-03-16 10:44:13 -10:00
J. Nick Koston
cdbbcfb87d [socket] Extract lwip_bind_err() to deduplicate bind error handling
TCP bind and UDP bind_internal_ had identical ERR_USE/ERR_VAL/ERR_OK
to errno mapping. Extract into a shared helper.
2026-03-14 16:12:39 -10:00
J. Nick Koston
71da3dc2de [socket] Fix RP2040 socket_delay race: don't clear wake flag before sleep
Restore the comment explaining why s_socket_woke must not be cleared
between the early-return check and the __wfe() loop, and restore the
s_socket_woke = false after the loop to consume the wake for the next
call. Both were lost during conflict resolution.
2026-03-14 16:09:24 -10:00
J. Nick Koston
0176305d24 [socket] Restore read_locked_, SO_RCVTIMEO, and wait_for_data_ lost in merge
These features from upstream/dev were dropped when resolving conflicts
with the PR's remote branch: read_locked_/wait_for_data_ (blocking read
with SO_RCVTIMEO timeout support), recv_timeout_cs_ field, SO_RCVTIMEO
and SO_SNDTIMEO setsockopt/getsockopt handling, and the setblocking()
implementation that accepts blocking mode for SO_RCVTIMEO.
2026-03-14 16:07:15 -10:00
J. Nick Koston
c81e9fd154 [socket] Deduplicate sockaddr parsing between TCP and UDP bind
Extract sockaddr_to_lwip() from LWIPRawUDPImpl static method to a
shared file-level function. Refactor LWIPRawCommon::bind() to use it
instead of inline address parsing, removing ~35 lines of duplicated
sockaddr-to-ip_addr_t conversion code.
2026-03-14 16:03:24 -10:00
J. Nick Koston
556ef1894f Merge branch 'socket-lwip-raw-udp' of https://github.com/esphome/esphome into socket-lwip-raw-udp
# Conflicts:
#	esphome/components/socket/lwip_raw_tcp_impl.cpp
#	esphome/components/socket/lwip_raw_tcp_impl.h
2026-03-14 16:00:31 -10:00
J. Nick Koston
519be06e73 [socket] Add LWIP_LOCK to UDP socket methods for RP2040 safety
On RP2040, lwip callbacks run from a low-priority IRQ context and can
preempt main-loop code. All lwip API calls from the main loop must
hold the async_context lock (LWIP_LOCK) to prevent races on shared
lwip state (PCB lists, pbuf pools, IGMP groups).

The TCP implementation was already correct; the UDP methods were
missing the lock.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 15:57:57 -10:00
J. Nick Koston
1e935c128a Merge remote-tracking branch 'upstream/dev' into socket-lwip-raw-udp
# Conflicts:
#	esphome/components/socket/lwip_raw_tcp_impl.cpp
2026-03-14 15:53:39 -10:00
J. Nick Koston
a0e162912c safety 2026-03-11 00:00:52 -10:00
J. Nick Koston
1131af1690 safety 2026-03-10 23:59:25 -10:00
J. Nick Koston
6fb00baa29 Merge remote-tracking branch 'upstream/dev' into socket-lwip-raw-udp 2026-03-10 23:57:51 -10:00
J. Nick Koston
fccfab8083 [socket] Switch UDP rx queue from lock-free SPSC to count-based with LWIP_LOCK
With the lwip lock infrastructure in place from the TCP race fix,
the lock-free SPSC ring buffer's wasted slot is no longer needed.
Switch to a simple rx_count_ approach that uses all 4 queue slots.

Also add LWIP_LOCK() to all UDP methods that call lwip APIs
(bind, close, sendto, setsockopt, getsockopt, recvfrom, factories).
2026-03-10 01:15:57 -10:00
J. Nick Koston
f54756ae2d [socket] Address review comments: doc comments and (void) flags
- Document port_host byte order convention in lwip_ip_to_sockaddr
- Note intentional method hiding in LWIPRawUDPRecvImpl
- Note recvfrom truncation differs from POSIX MSG_TRUNC
- Add (void) flags in sendto to clarify flags are ignored

Co-Authored-By: J. Nick Koston <nick@koston.org>
2026-03-10 01:12:15 -10:00
J. Nick Koston
49ba08cec9 [socket] Add lwip raw UDP socket implementation
Add native UDP support to the lwip raw TCP socket layer used by
ESP8266 and RP2040, eliminating the need for Arduino WiFiUDP fallback.

Two new classes:
- LWIPRawUDPImpl: send-only UDP (8 bytes overhead)
- LWIPRawUDPRecvImpl: send+recv with fixed-size ring buffer (no heap
  allocation in recv callback)

Factory functions: socket_udp(), socket_udp_recv(), socket_ip_udp(),
socket_ip_udp_recv() with UDPSocket/UDPRecvSocket type aliases.

Additive only — no consumer migration in this PR.
2026-03-10 01:12:15 -10:00
J. Nick Koston
81d12fd14a [socket] Hold lwip lock for entire write() operation
Same pattern as writev — write() calls internal_write_() then
internal_output_(), each acquiring the lock separately. Hold
the lock at the outer scope so inner calls just bump the
recursion counter.
2026-03-10 00:57:36 -10:00
J. Nick Koston
cc05bf3ed2 [socket] Add LWIP_LOCK to socket factory functions
tcp_new() is an lwip core API call that must be bracketed with
the lwip lock on RP2040 per pico-sdk docs. Add LWIP_LOCK() to
socket() and socket_listen() factory functions.
2026-03-10 00:55:34 -10:00
J. Nick Koston
c182c0c74f [socket] Hold lwip lock for entire readv/writev scatter-gather operation
Avoid repeated lock acquire/release cycles per iovec element.
The recursive mutex re-entry in inner calls is nearly free (counter
bump), while the outer lock prevents the expensive IRQ disable/enable
on each iteration.
2026-03-10 00:53:01 -10:00
J. Nick Koston
a88e9b8146 [socket] Fix RP2040 TCP race condition between lwip callbacks and main loop
On RP2040 (Pico W), arduino-pico sets PICO_CYW43_ARCH_THREADSAFE_BACKGROUND=1,
which means lwip callbacks (recv_fn, accept_fn, err_fn) run from a PendSV
interrupt — not the main loop. This allows them to preempt read(), write(),
close(), and accept() at any point, causing race conditions on shared state
like the rx_buf_ pbuf chain.

The most critical race: recv_fn calls pbuf_cat(rx_buf_, pb) while read() is
freeing nodes in the same chain, leading to use-after-free and lwip's
"Creating an infinite loop" assertion panic. This is the root cause of #10681.

Fix: implement RP2040's LwIPLock (previously a no-op) to call
cyw43_arch_lwip_begin/end, which acquires the pico-sdk async_context recursive
mutex. Add LWIP_LOCK() guards to all main-loop lwip API call sites in the
socket layer.

On ESP8266, lwip callbacks run cooperatively from the main loop, so
LwIPLock remains a no-op.

Closes #10681
2026-03-10 00:34:20 -10:00
J. Nick Koston
7dea3756e9 [socket] Address review comments: doc comments and (void) flags
- Document port_host byte order convention in lwip_ip_to_sockaddr
- Note intentional method hiding in LWIPRawUDPRecvImpl
- Note recvfrom truncation differs from POSIX MSG_TRUNC
- Add (void) flags in sendto to clarify flags are ignored

Co-Authored-By: J. Nick Koston <nick@koston.org>
2026-03-09 23:56:37 -10:00
J. Nick Koston
fa0bff3374 [socket] Add lwip raw UDP socket implementation
Add native UDP support to the lwip raw TCP socket layer used by
ESP8266 and RP2040, eliminating the need for Arduino WiFiUDP fallback.

Two new classes:
- LWIPRawUDPImpl: send-only UDP (8 bytes overhead)
- LWIPRawUDPRecvImpl: send+recv with fixed-size ring buffer (no heap
  allocation in recv callback)

Factory functions: socket_udp(), socket_udp_recv(), socket_ip_udp(),
socket_ip_udp_recv() with UDPSocket/UDPRecvSocket type aliases.

Additive only — no consumer migration in this PR.
2026-03-09 23:48:07 -10:00
10 changed files with 741 additions and 122 deletions

View File

@@ -182,7 +182,9 @@ def FILTER_SOURCE_FILES() -> list[str]:
# Build list of files to exclude based on selected implementation
excluded = []
if impl != IMPLEMENTATION_LWIP_TCP:
excluded.append("lwip_raw_common_impl.cpp")
excluded.append("lwip_raw_tcp_impl.cpp")
excluded.append("lwip_raw_udp_impl.cpp")
if impl != IMPLEMENTATION_BSD_SOCKETS:
excluded.append("bsd_sockets_impl.cpp")
if impl != IMPLEMENTATION_LWIP_SOCKETS:

View File

@@ -20,6 +20,16 @@
#define IPPROTO_IP 0
#define IPPROTO_TCP 6
#define IPPROTO_UDP 17
#define IP_ADD_MEMBERSHIP 3
#define IP_DROP_MEMBERSHIP 4
// NOLINTNEXTLINE(readability-identifier-naming)
struct ip_mreq {
struct in_addr imr_multiaddr;
struct in_addr imr_interface;
};
#if LWIP_IPV6
#define AF_INET6 10

View File

@@ -0,0 +1,99 @@
#include "lwip_raw_common_impl.h"
#include "esphome/core/defines.h"
#ifdef USE_SOCKET_IMPL_LWIP_TCP
#include <cerrno>
#include <cstring>
namespace esphome::socket {
int lwip_ip_to_sockaddr(sa_family_t family, const ip_addr_t *ip, uint16_t port_host, struct sockaddr *name,
socklen_t *addrlen) {
if (family == AF_INET) {
if (*addrlen < sizeof(struct sockaddr_in)) {
errno = EINVAL;
return -1;
}
auto *addr = reinterpret_cast<struct sockaddr_in *>(name);
addr->sin_family = AF_INET;
*addrlen = addr->sin_len = sizeof(struct sockaddr_in);
addr->sin_port = htons(port_host);
inet_addr_from_ip4addr(&addr->sin_addr, ip_2_ip4(ip));
return 0;
}
#if LWIP_IPV6
if (family == AF_INET6) {
if (*addrlen < sizeof(struct sockaddr_in6)) {
errno = EINVAL;
return -1;
}
auto *addr = reinterpret_cast<struct sockaddr_in6 *>(name);
addr->sin6_family = AF_INET6;
*addrlen = addr->sin6_len = sizeof(struct sockaddr_in6);
addr->sin6_port = htons(port_host);
// AF_INET6 sockets may receive IPv4 packets; convert to IPv4-mapped IPv6.
if (IP_IS_V4(ip)) {
ip_addr_t mapped;
ip4_2_ipv4_mapped_ipv6(ip_2_ip6(&mapped), ip_2_ip4(ip));
inet6_addr_from_ip6addr(&addr->sin6_addr, ip_2_ip6(&mapped));
} else {
inet6_addr_from_ip6addr(&addr->sin6_addr, ip_2_ip6(ip));
}
return 0;
}
#endif
return -1;
}
bool sockaddr_to_lwip(const struct sockaddr *addr, socklen_t addrlen, ip_addr_t *ip, uint16_t *port) {
if (addrlen < sizeof(struct sockaddr))
return false;
#if LWIP_IPV6
if (addr->sa_family == AF_INET) {
if (addrlen < sizeof(sockaddr_in))
return false;
auto *addr4 = reinterpret_cast<const sockaddr_in *>(addr);
*port = ntohs(addr4->sin_port);
ip->type = IPADDR_TYPE_V4;
ip->u_addr.ip4.addr = addr4->sin_addr.s_addr;
return true;
}
if (addr->sa_family == AF_INET6) {
if (addrlen < sizeof(sockaddr_in6))
return false;
auto *addr6 = reinterpret_cast<const sockaddr_in6 *>(addr);
*port = ntohs(addr6->sin6_port);
ip->type = IPADDR_TYPE_V6;
memcpy(&ip->u_addr.ip6.addr, &addr6->sin6_addr.un.u8_addr, 16);
return true;
}
#else
if (addr->sa_family == AF_INET) {
if (addrlen < sizeof(sockaddr_in))
return false;
auto *addr4 = reinterpret_cast<const sockaddr_in *>(addr);
*port = ntohs(addr4->sin_port);
ip->addr = addr4->sin_addr.s_addr;
return true;
}
#endif
return false;
}
int lwip_bind_err(err_t err) {
if (err == ERR_OK)
return 0;
if (err == ERR_USE) {
errno = EADDRINUSE;
} else if (err == ERR_VAL) {
errno = EINVAL;
} else {
errno = EIO;
}
return -1;
}
} // namespace esphome::socket
#endif // USE_SOCKET_IMPL_LWIP_TCP

View File

@@ -0,0 +1,55 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_SOCKET_IMPL_LWIP_TCP
#include <cerrno>
#include <cstring>
#include "headers.h"
#include "lwip/ip.h"
namespace esphome::socket {
// ---- LWIP thread safety ----
//
// On RP2040 (Pico W), arduino-pico sets PICO_CYW43_ARCH_THREADSAFE_BACKGROUND=1.
// This means lwip callbacks (recv_fn, accept_fn, err_fn) run from a low-priority
// user IRQ context, not the main loop (see low_priority_irq_handler() in pico-sdk
// async_context_threadsafe_background.c). They can preempt main-loop code at any point.
//
// Without locking, this causes race conditions between recv_fn and read() on the
// shared rx_buf_ pbuf chain — recv_fn calls pbuf_cat() while read() is freeing
// nodes, leading to use-after-free and infinite-loop crashes. See esphome#10681.
//
// On ESP8266, lwip callbacks run from the SYS context which cooperates with user
// code (CONT context) — they never preempt each other, so no locking is needed.
//
// esphome::LwIPLock is the platform-provided RAII guard (see helpers.h/helpers.cpp).
// On RP2040, it acquires cyw43_arch_lwip_begin/end (WiFi) or ethernet_arch_lwip_begin/end
// (Ethernet). On ESP8266, it's a no-op.
//
// Each .cpp file that needs locking defines its own LWIP_LOCK() macro:
// #define LWIP_LOCK() esphome::LwIPLock lwip_lock_guard
// This is a per-TU convenience macro, not defined here to avoid macro leaking.
/// Convert lwip ip_addr_t + host-order port to sockaddr, based on the socket's address family.
/// @param port_host Port in host byte order. TCP callers must convert from network order first
/// (tcp_pcb stores ports in network byte order); UDP callers can pass directly
/// (lwip udp_recv callback provides port in host byte order).
/// Shared by both TCP (LWIPRawCommon) and UDP (LWIPRawUDPImpl) implementations.
int lwip_ip_to_sockaddr(sa_family_t family, const ip_addr_t *ip, uint16_t port_host, struct sockaddr *name,
socklen_t *addrlen);
/// Convert sockaddr to lwip ip_addr_t and host-order port.
/// For IPv6, sets type to IPADDR_TYPE_V6 (callers that need dual-stack should
/// override to IPADDR_TYPE_ANY after calling).
/// Shared by both TCP (LWIPRawCommon) and UDP (LWIPRawUDPImpl) bind/sendto paths.
bool sockaddr_to_lwip(const struct sockaddr *addr, socklen_t addrlen, ip_addr_t *ip, uint16_t *port);
/// Map lwip bind error to errno. Returns 0 on success, -1 on error with errno set.
int lwip_bind_err(err_t err);
} // namespace esphome::socket
#endif // USE_SOCKET_IMPL_LWIP_TCP

View File

@@ -10,6 +10,7 @@
#include "esphome/core/helpers.h"
#include "esphome/core/wake.h"
#include "esphome/core/log.h"
#include "lwip_raw_common_impl.h"
#ifdef USE_OTA_PLATFORM_ESPHOME
extern "C" void esphome_wake_ota_component_any_context();
@@ -24,23 +25,9 @@ extern "C" void esphome_wake_ota_component_any_context();
namespace esphome::socket {
// ---- LWIP thread safety ----
//
// On RP2040 (Pico W), arduino-pico sets PICO_CYW43_ARCH_THREADSAFE_BACKGROUND=1.
// This means lwip callbacks (recv_fn, accept_fn, err_fn) run from a low-priority
// user IRQ context, not the main loop (see low_priority_irq_handler() in pico-sdk
// async_context_threadsafe_background.c). They can preempt main-loop code at any point.
//
// Without locking, this causes race conditions between recv_fn and read() on the
// shared rx_buf_ pbuf chain — recv_fn calls pbuf_cat() while read() is freeing
// nodes, leading to use-after-free and infinite-loop crashes. See esphome#10681.
//
// On ESP8266, lwip callbacks run from the SYS context which cooperates with user
// code (CONT context) — they never preempt each other, so no locking is needed.
//
// esphome::LwIPLock is the platform-provided RAII guard (see helpers.h/helpers.cpp).
// On RP2040, it acquires cyw43_arch_lwip_begin/end (WiFi) or ethernet_arch_lwip_begin/end
// (Ethernet). On ESP8266, it's a no-op.
// LWIP thread safety — see lwip_raw_common_impl.h for full explanation.
// esphome::LwIPLock is the platform-provided RAII guard.
// On RP2040, it acquires cyw43_arch_lwip_begin/end. On ESP8266, it's a no-op.
#define LWIP_LOCK() esphome::LwIPLock lwip_lock_guard // NOLINT
static const char *const TAG = "socket.lwip";
@@ -107,59 +94,20 @@ int LWIPRawCommon::bind(const struct sockaddr *name, socklen_t addrlen) {
return -1;
}
ip_addr_t ip;
in_port_t port;
uint16_t port;
if (!sockaddr_to_lwip(name, addrlen, &ip, &port)) {
errno = EINVAL;
return -1;
}
#if LWIP_IPV6
if (this->family_ == AF_INET) {
if (addrlen < sizeof(sockaddr_in)) {
errno = EINVAL;
return -1;
}
auto *addr4 = reinterpret_cast<const sockaddr_in *>(name);
port = ntohs(addr4->sin_port);
ip.type = IPADDR_TYPE_V4;
ip.u_addr.ip4.addr = addr4->sin_addr.s_addr;
LWIP_LOG("tcp_bind(%p ip=%s port=%u)", this->pcb_, ip4addr_ntoa(&ip.u_addr.ip4), port);
} else if (this->family_ == AF_INET6) {
if (addrlen < sizeof(sockaddr_in6)) {
errno = EINVAL;
return -1;
}
auto *addr6 = reinterpret_cast<const sockaddr_in6 *>(name);
port = ntohs(addr6->sin6_port);
// Use IPADDR_TYPE_ANY for dual-stack (accept both IPv4 and IPv6)
if (this->family_ == AF_INET6) {
ip.type = IPADDR_TYPE_ANY;
memcpy(&ip.u_addr.ip6.addr, &addr6->sin6_addr.un.u8_addr, 16);
LWIP_LOG("tcp_bind(%p ip=%s port=%u)", this->pcb_, ip6addr_ntoa(&ip.u_addr.ip6), port);
} else {
errno = EINVAL;
return -1;
}
#else
if (this->family_ != AF_INET) {
errno = EINVAL;
return -1;
}
auto *addr4 = reinterpret_cast<const sockaddr_in *>(name);
port = ntohs(addr4->sin_port);
ip.addr = addr4->sin_addr.s_addr;
LWIP_LOG("tcp_bind(%p ip=%u port=%u)", this->pcb_, ip.addr, port);
#endif
err_t err = tcp_bind(this->pcb_, &ip, port);
if (err == ERR_USE) {
LWIP_LOG(" -> err ERR_USE");
errno = EADDRINUSE;
return -1;
}
if (err == ERR_VAL) {
LWIP_LOG(" -> err ERR_VAL");
errno = EINVAL;
return -1;
}
if (err != ERR_OK) {
LWIP_LOG(" -> err %d", err);
errno = EIO;
return -1;
}
return 0;
LWIP_LOG(" -> err %d", err);
return lwip_bind_err(err);
}
int LWIPRawCommon::close() {
@@ -344,43 +292,8 @@ int LWIPRawCommon::setsockopt(int level, int optname, const void *optval, sockle
}
int LWIPRawCommon::ip2sockaddr_(ip_addr_t *ip, uint16_t port, struct sockaddr *name, socklen_t *addrlen) {
if (this->family_ == AF_INET) {
if (*addrlen < sizeof(struct sockaddr_in)) {
errno = EINVAL;
return -1;
}
struct sockaddr_in *addr = reinterpret_cast<struct sockaddr_in *>(name);
addr->sin_family = AF_INET;
*addrlen = addr->sin_len = sizeof(struct sockaddr_in);
addr->sin_port = port;
inet_addr_from_ip4addr(&addr->sin_addr, ip_2_ip4(ip));
return 0;
}
#if LWIP_IPV6
else if (this->family_ == AF_INET6) {
if (*addrlen < sizeof(struct sockaddr_in6)) {
errno = EINVAL;
return -1;
}
struct sockaddr_in6 *addr = reinterpret_cast<struct sockaddr_in6 *>(name);
addr->sin6_family = AF_INET6;
*addrlen = addr->sin6_len = sizeof(struct sockaddr_in6);
addr->sin6_port = port;
// AF_INET6 sockets are bound to IPv4 as well, so we may encounter IPv4 addresses that must be converted to IPv6.
if (IP_IS_V4(ip)) {
ip_addr_t mapped;
ip4_2_ipv4_mapped_ipv6(ip_2_ip6(&mapped), ip_2_ip4(ip));
inet6_addr_from_ip6addr(&addr->sin6_addr, ip_2_ip6(&mapped));
} else {
inet6_addr_from_ip6addr(&addr->sin6_addr, ip_2_ip6(ip));
}
return 0;
}
#endif
return -1;
// TCP pcb stores port in network byte order; convert to host order for the shared helper
return lwip_ip_to_sockaddr(this->family_, ip, ntohs(port), name, addrlen);
}
// ---- LWIPRawImpl methods ----
@@ -867,11 +780,11 @@ err_t LWIPRawListenImpl::accept_fn_(struct tcp_pcb *newpcb, err_t err) {
return ERR_OK;
}
// ---- Factory functions ----
// ---- TCP Factory functions ----
std::unique_ptr<Socket> socket(int domain, int type, int protocol) {
if (type != SOCK_STREAM) {
ESP_LOGE(TAG, "UDP sockets not supported on this platform, use WiFiUDP");
ESP_LOGE(TAG, "Use socket_udp() for UDP sockets on this platform");
errno = EPROTOTYPE;
return nullptr;
}
@@ -891,7 +804,7 @@ std::unique_ptr<Socket> socket_loop_monitored(int domain, int type, int protocol
std::unique_ptr<ListenSocket> socket_listen(int domain, int type, int protocol) {
if (type != SOCK_STREAM) {
ESP_LOGE(TAG, "UDP sockets not supported on this platform, use WiFiUDP");
ESP_LOGE(TAG, "Use socket_udp() for UDP sockets on this platform");
errno = EPROTOTYPE;
return nullptr;
}

View File

@@ -16,6 +16,8 @@
#include "lwip/opt.h"
#include "lwip/tcp.h"
#include "lwip_raw_udp_impl.h"
namespace esphome::socket {
// Forward declaration

View File

@@ -0,0 +1,379 @@
#include "socket.h"
#include "esphome/core/defines.h"
#ifdef USE_SOCKET_IMPL_LWIP_TCP
#include <cerrno>
#include <cstring>
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/wake.h"
#include "lwip_raw_common_impl.h"
#include "lwip/igmp.h"
#include "lwip/pbuf.h"
#include "lwip/udp.h"
namespace esphome::socket {
// LWIP thread safety — see lwip_raw_common_impl.h for full explanation.
// esphome::LwIPLock is the platform-provided RAII guard.
// On RP2040, it acquires cyw43_arch_lwip_begin/end. On ESP8266, it's a no-op.
#define LWIP_LOCK() esphome::LwIPLock lwip_lock_guard // NOLINT
static const char *const TAG = "socket.lwip_udp";
// ---- LWIPRawUDPSendImpl (send-only) methods ----
LWIPRawUDPSendImpl::LWIPRawUDPSendImpl(sa_family_t family) : family_(family) {
LWIP_LOCK();
#if LWIP_IPV6
this->pcb_ = udp_new_ip_type(family == AF_INET6 ? IPADDR_TYPE_ANY : IPADDR_TYPE_V4);
#else
this->pcb_ = udp_new();
#endif
}
LWIPRawUDPSendImpl::~LWIPRawUDPSendImpl() {
// Early return avoids acquiring the lwip lock when pcb_ is already null
// (e.g., after LWIPRawUDPImpl::close() already cleaned up).
if (this->pcb_ == nullptr)
return;
LWIP_LOCK();
udp_remove(this->pcb_);
this->pcb_ = nullptr;
}
int LWIPRawUDPSendImpl::bind_internal_locked_(const struct sockaddr *name, socklen_t addrlen) {
// Caller must hold LWIP_LOCK
if (this->pcb_ == nullptr) {
errno = EBADF;
return -1;
}
if (name == nullptr) {
errno = EINVAL;
return -1;
}
ip_addr_t ip;
uint16_t port;
if (!sockaddr_to_lwip(name, addrlen, &ip, &port)) {
errno = EINVAL;
return -1;
}
#if LWIP_IPV6
// For bind, use IPADDR_TYPE_ANY on IPv6 sockets to accept both IPv4 and IPv6
// packets (dual-stack). sockaddr_to_lwip uses IPADDR_TYPE_V6 which is correct
// for sendto destinations but too restrictive for bind.
if (this->family_ == AF_INET6) {
ip.type = IPADDR_TYPE_ANY;
}
#endif
return lwip_bind_err(udp_bind(this->pcb_, &ip, port));
}
int LWIPRawUDPSendImpl::bind(const struct sockaddr *name, socklen_t addrlen) {
LWIP_LOCK();
return this->bind_internal_locked_(name, addrlen);
}
int LWIPRawUDPSendImpl::close() {
LWIP_LOCK();
return this->close_internal_locked_();
}
int LWIPRawUDPSendImpl::close_internal_locked_() {
// Caller must hold LWIP_LOCK
if (this->pcb_ == nullptr) {
errno = EBADF;
return -1;
}
udp_remove(this->pcb_);
this->pcb_ = nullptr;
return 0;
}
int LWIPRawUDPSendImpl::ip2sockaddr_(const ip_addr_t *ip, uint16_t port, struct sockaddr *name, socklen_t *addrlen) {
// UDP recv callback provides port in host byte order
return lwip_ip_to_sockaddr(this->family_, ip, port, name, addrlen);
}
ssize_t LWIPRawUDPSendImpl::sendto(const void *buf, size_t len, int flags, const struct sockaddr *dest_addr,
socklen_t addrlen) {
(void) flags; // Flags (MSG_DONTWAIT, etc.) are ignored; raw lwip is always non-blocking
LWIP_LOCK();
if (this->pcb_ == nullptr) {
errno = EBADF;
return -1;
}
if (buf == nullptr || dest_addr == nullptr) {
errno = EINVAL;
return -1;
}
// pbuf_alloc takes u16_t length; reject oversized packets
if (len > UINT16_MAX) {
errno = EMSGSIZE;
return -1;
}
ip_addr_t dst_ip;
uint16_t dst_port;
if (!sockaddr_to_lwip(dest_addr, addrlen, &dst_ip, &dst_port)) {
errno = EINVAL;
return -1;
}
// Allocate pbuf and copy data
struct pbuf *pb = pbuf_alloc(PBUF_TRANSPORT, (uint16_t) len, PBUF_RAM);
if (pb == nullptr) {
errno = ENOMEM;
return -1;
}
memcpy(pb->payload, buf, len);
err_t err = udp_sendto(this->pcb_, pb, &dst_ip, dst_port);
pbuf_free(pb);
if (err != ERR_OK) {
errno = err == ERR_MEM ? ENOMEM : EIO;
return -1;
}
return (ssize_t) len;
}
int LWIPRawUDPSendImpl::setsockopt(int level, int optname, const void *optval, socklen_t optlen) {
LWIP_LOCK();
if (this->pcb_ == nullptr) {
errno = EBADF;
return -1;
}
if (level == SOL_SOCKET && optname == SO_REUSEADDR) {
// lwip raw UDP doesn't enforce port exclusivity the same way,
// but we accept this silently for compatibility
return 0;
}
if (level == SOL_SOCKET && optname == SO_BROADCAST) {
if (optval == nullptr || optlen < sizeof(int)) {
errno = EINVAL;
return -1;
}
int val = *reinterpret_cast<const int *>(optval);
if (val) {
ip_set_option(this->pcb_, SOF_BROADCAST);
} else {
ip_reset_option(this->pcb_, SOF_BROADCAST);
}
return 0;
}
if (level == IPPROTO_IP && optname == IP_ADD_MEMBERSHIP) {
if (optval == nullptr || optlen < sizeof(struct ip_mreq)) {
errno = EINVAL;
return -1;
}
auto *mreq = reinterpret_cast<const struct ip_mreq *>(optval);
ip4_addr_t multiaddr;
multiaddr.addr = mreq->imr_multiaddr.s_addr;
ip4_addr_t ifaddr;
ifaddr.addr = mreq->imr_interface.s_addr;
err_t err = igmp_joingroup(&ifaddr, &multiaddr);
if (err != ERR_OK) {
errno = EIO;
return -1;
}
return 0;
}
if (level == IPPROTO_IP && optname == IP_DROP_MEMBERSHIP) {
if (optval == nullptr || optlen < sizeof(struct ip_mreq)) {
errno = EINVAL;
return -1;
}
auto *mreq = reinterpret_cast<const struct ip_mreq *>(optval);
ip4_addr_t multiaddr;
multiaddr.addr = mreq->imr_multiaddr.s_addr;
ip4_addr_t ifaddr;
ifaddr.addr = mreq->imr_interface.s_addr;
err_t err = igmp_leavegroup(&ifaddr, &multiaddr);
if (err != ERR_OK) {
errno = EIO;
return -1;
}
return 0;
}
errno = ENOPROTOOPT;
return -1;
}
int LWIPRawUDPSendImpl::getsockopt(int level, int optname, void *optval, socklen_t *optlen) {
LWIP_LOCK();
if (this->pcb_ == nullptr) {
errno = EBADF;
return -1;
}
if (level == SOL_SOCKET && optname == SO_REUSEADDR) {
if (optval == nullptr || optlen == nullptr || *optlen < sizeof(int)) {
errno = EINVAL;
return -1;
}
*reinterpret_cast<int *>(optval) = 1;
*optlen = sizeof(int);
return 0;
}
errno = ENOPROTOOPT;
return -1;
}
int LWIPRawUDPSendImpl::setblocking(bool blocking) {
if (blocking) {
// blocking operation not supported on raw lwip
errno = EINVAL;
return -1;
}
return 0;
}
// ---- LWIPRawUDPImpl methods ----
LWIPRawUDPImpl::~LWIPRawUDPImpl() {
// Flush rx queue and unregister callback before base destructor removes pcb
if (this->pcb_ != nullptr)
this->close();
}
int LWIPRawUDPImpl::close() {
LWIP_LOCK();
// Unregister recv callback before removing pcb
if (this->pcb_ != nullptr) {
udp_recv(this->pcb_, nullptr, nullptr);
}
// Flush any queued rx packets
while (this->rx_count_ > 0) {
auto &pkt = this->rx_queue_[this->rx_read_idx_];
if (pkt.pb != nullptr) {
pbuf_free(pkt.pb);
pkt.pb = nullptr;
}
this->rx_read_idx_ = (this->rx_read_idx_ + 1) & UDP_RX_MASK;
this->rx_count_--;
}
// close_internal_locked_() returns EBADF if already closed, which is fine from destructor
return this->close_internal_locked_();
}
int LWIPRawUDPImpl::bind(const struct sockaddr *name, socklen_t addrlen) {
LWIP_LOCK();
int ret = this->bind_internal_locked_(name, addrlen);
if (ret != 0)
return ret;
// Register recv callback now that we're bound and ready to receive
udp_recv(this->pcb_, LWIPRawUDPImpl::s_recv_fn, this);
return 0;
}
ssize_t LWIPRawUDPImpl::read(void *buf, size_t len) { return this->recvfrom(buf, len, nullptr, nullptr); }
ssize_t LWIPRawUDPImpl::recvfrom(void *buf, size_t len, struct sockaddr *src_addr, socklen_t *addrlen) {
if (buf == nullptr && len > 0) {
errno = EINVAL;
return -1;
}
LWIP_LOCK();
if (this->pcb_ == nullptr) {
errno = EBADF;
return -1;
}
if (this->rx_count_ == 0) {
errno = EWOULDBLOCK;
return -1;
}
auto &pkt = this->rx_queue_[this->rx_read_idx_];
size_t pkt_len = pkt.pb->tot_len;
size_t copy_len = std::min(len, pkt_len);
// Fill in source address if requested.
// If ip2sockaddr_ fails (e.g., addrlen too small), fail the entire recvfrom
// rather than silently returning data without a source address.
if (src_addr != nullptr && addrlen != nullptr &&
this->ip2sockaddr_(&pkt.src_addr, pkt.src_port, src_addr, addrlen) != 0) {
// Don't consume the packet or modify the caller buffer on address conversion failure
return -1;
}
// Copy data from pbuf chain — done after validation so caller buffer is
// not modified on error paths.
pbuf_copy_partial(pkt.pb, buf, copy_len, 0);
// Free the pbuf and advance the read pointer
pbuf_free(pkt.pb);
pkt.pb = nullptr;
this->rx_read_idx_ = (this->rx_read_idx_ + 1) & UDP_RX_MASK;
this->rx_count_--;
return (ssize_t) copy_len;
}
void LWIPRawUDPImpl::s_recv_fn(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port) {
auto *self = reinterpret_cast<LWIPRawUDPImpl *>(arg);
self->recv_fn_(p, addr, port);
}
// LWIP CALLBACK — runs from IRQ context on RP2040 (low-priority user IRQ).
// No heap allocation allowed — malloc is not IRQ-safe (see #14687).
// No LWIP_LOCK() needed — lwip core already holds the async_context lock.
void LWIPRawUDPImpl::recv_fn_(struct pbuf *p, const ip_addr_t *addr, u16_t port) {
if (p == nullptr)
return;
// Check if queue is full
if (this->rx_count_ >= UDP_RX_QUEUE_SIZE) {
// Drop packet — queue full
pbuf_free(p);
return;
}
// Enqueue the packet
uint8_t write_idx = (this->rx_read_idx_ + this->rx_count_) & UDP_RX_MASK;
auto &slot = this->rx_queue_[write_idx];
slot.pb = p;
slot.src_addr = *addr;
slot.src_port = port;
this->rx_count_++;
#if defined(USE_ESP8266) || defined(USE_RP2040)
esphome::wake_loop_any_context();
#endif
}
// ---- UDP Factory functions ----
std::unique_ptr<UDPSendSocket> socket_udp_send(int domain, int protocol) {
(void) protocol; // Raw lwip UDP ignores protocol; kept for API compatibility
auto sock = make_unique<LWIPRawUDPSendImpl>((sa_family_t) domain);
if (!sock->is_valid()) {
errno = ENOMEM;
return nullptr;
}
return sock;
}
std::unique_ptr<UDPSocket> socket_udp(int domain, int protocol) {
(void) protocol; // Raw lwip UDP ignores protocol; kept for API compatibility
auto sock = make_unique<LWIPRawUDPImpl>((sa_family_t) domain);
if (!sock->is_valid()) {
errno = ENOMEM;
return nullptr;
}
return sock;
}
std::unique_ptr<UDPSocket> socket_udp_loop_monitored(int domain, int protocol) {
// LWIPRawUDPImpl has wake built into the recv callback, so no extra monitoring needed
return socket_udp(domain, protocol);
}
#undef LWIP_LOCK
} // namespace esphome::socket
#endif // USE_SOCKET_IMPL_LWIP_TCP

View File

@@ -0,0 +1,112 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_SOCKET_IMPL_LWIP_TCP
#include <array>
#include <cerrno>
#include <cstring>
#include <memory>
#include "headers.h"
#include "lwip/ip.h"
#include "lwip/udp.h"
namespace esphome::socket {
/// Send-only UDP socket implementation for LWIP raw API.
/// Non-virtual, concrete type. Uses lwip/udp.h raw API.
/// No receive capability — use LWIPRawUDPImpl for sockets that need to receive.
class LWIPRawUDPSendImpl {
public:
LWIPRawUDPSendImpl(sa_family_t family);
~LWIPRawUDPSendImpl();
LWIPRawUDPSendImpl(const LWIPRawUDPSendImpl &) = delete;
LWIPRawUDPSendImpl &operator=(const LWIPRawUDPSendImpl &) = delete;
int bind(const struct sockaddr *name, socklen_t addrlen);
int close();
/// Send a UDP packet to the specified destination.
ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
int setsockopt(int level, int optname, const void *optval, socklen_t optlen);
int getsockopt(int level, int optname, void *optval, socklen_t *optlen);
int setblocking(bool blocking);
bool is_valid() const { return this->pcb_ != nullptr; }
bool ready() const { return false; }
int get_fd() const { return -1; }
protected:
/// Convert lwip ip_addr_t and port to sockaddr.
int ip2sockaddr_(const ip_addr_t *ip, uint16_t port, struct sockaddr *name, socklen_t *addrlen);
/// Shared bind logic — parses sockaddr and calls udp_bind. Caller must hold LWIP_LOCK.
int bind_internal_locked_(const struct sockaddr *name, socklen_t addrlen);
/// Shared close logic — unregisters and removes udp pcb. Caller must hold LWIP_LOCK.
int close_internal_locked_();
struct udp_pcb *pcb_{nullptr};
sa_family_t family_{0};
};
/// UDP socket with receive support for LWIP raw API.
/// Extends LWIPRawUDPSendImpl with a fixed-size ring buffer for incoming packets.
/// The recv callback is registered on bind().
///
/// Note: close() and bind() intentionally hide the base class methods to add
/// recv callback registration/cleanup. This is safe because these classes are
/// never used polymorphically (no virtual dispatch) — callers always use the
/// concrete LWIPRawUDPImpl type via the UDPSocket alias.
class LWIPRawUDPImpl : public LWIPRawUDPSendImpl {
public:
using LWIPRawUDPSendImpl::LWIPRawUDPSendImpl;
~LWIPRawUDPImpl();
/// Close the socket, flushing any queued rx packets first.
int close();
/// Bind and register the recv callback for incoming packets.
int bind(const struct sockaddr *name, socklen_t addrlen);
/// Read the next queued packet, discarding source address info.
/// If buf is smaller than the packet, data is silently truncated (returns bytes copied).
/// Note: unlike POSIX MSG_TRUNC, this does not return the original packet length on truncation.
ssize_t read(void *buf, size_t len);
/// Read the next queued packet and return the source address.
/// If buf is smaller than the packet, data is silently truncated (returns bytes copied).
/// Note: unlike POSIX MSG_TRUNC, this does not return the original packet length on truncation.
ssize_t recvfrom(void *buf, size_t len, struct sockaddr *src_addr, socklen_t *addrlen);
/// Returns true if there are packets available to read.
/// Intentionally unlocked — same rationale as LWIPRawImpl::ready().
bool ready() const { return this->rx_count_ > 0; }
protected:
static void s_recv_fn(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port);
void recv_fn_(struct pbuf *p, const ip_addr_t *addr, u16_t port);
/// Ring buffer for received UDP packets.
/// Both producer (recv callback) and consumer (main loop) are serialized by the
/// lwip lock — the callback runs under lwip core lock, and consumer methods hold
/// LWIP_LOCK(). All 4 slots are usable (no wasted slot for full/empty distinction).
/// No heap allocation in the recv callback — packets are dropped if the queue is full.
static constexpr uint8_t UDP_RX_QUEUE_SIZE = 4;
static constexpr uint8_t UDP_RX_MASK = UDP_RX_QUEUE_SIZE - 1;
static_assert((UDP_RX_QUEUE_SIZE & UDP_RX_MASK) == 0, "UDP_RX_QUEUE_SIZE must be power of 2");
struct UDPRxPacket {
struct pbuf *pb{nullptr};
ip_addr_t src_addr{};
uint16_t src_port{0};
};
std::array<UDPRxPacket, UDP_RX_QUEUE_SIZE> rx_queue_{};
uint8_t rx_read_idx_{0};
uint8_t rx_count_{0};
};
} // namespace esphome::socket
#endif // USE_SOCKET_IMPL_LWIP_TCP

View File

@@ -92,18 +92,6 @@ std::unique_ptr<Socket> socket_ip(int type, int protocol) {
#endif /* USE_NETWORK_IPV6 */
}
#ifdef USE_SOCKET_IMPL_LWIP_TCP
// LWIP_TCP has separate Socket/ListenSocket types — needs out-of-line factory.
// BSD and LWIP_SOCKETS define this inline in socket.h.
std::unique_ptr<ListenSocket> socket_ip_loop_monitored(int type, int protocol) {
#if USE_NETWORK_IPV6
return socket_listen_loop_monitored(AF_INET6, type, protocol);
#else
return socket_listen_loop_monitored(AF_INET, type, protocol);
#endif /* USE_NETWORK_IPV6 */
}
#endif
socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_address, uint16_t port) {
#if USE_NETWORK_IPV6
if (strchr(ip_address, ':') != nullptr) {

View File

@@ -27,17 +27,25 @@ namespace esphome::socket {
// Type aliases — only one implementation is active per build.
// Socket is the concrete type for connected sockets.
// ListenSocket is the concrete type for listening/server sockets.
// On BSD and LWIP_SOCKETS, both aliases resolve to the same type.
// UDPSocket is the concrete type for UDP sockets (send + receive).
// UDPSendSocket is the concrete type for send-only UDP sockets.
// On BSD and LWIP_SOCKETS, all aliases resolve to the same type.
// On LWIP_TCP, they are different types (no virtual dispatch between them).
#ifdef USE_SOCKET_IMPL_BSD_SOCKETS
using Socket = BSDSocketImpl;
using ListenSocket = BSDSocketImpl;
using UDPSendSocket = BSDSocketImpl;
using UDPSocket = BSDSocketImpl;
#elif defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
using Socket = LwIPSocketImpl;
using ListenSocket = LwIPSocketImpl;
using UDPSendSocket = LwIPSocketImpl;
using UDPSocket = LwIPSocketImpl;
#elif defined(USE_SOCKET_IMPL_LWIP_TCP)
using Socket = LWIPRawImpl;
using ListenSocket = LWIPRawListenImpl;
using UDPSendSocket = LWIPRawUDPSendImpl;
using UDPSocket = LWIPRawUDPImpl;
#endif
#ifdef USE_LWIP_FAST_SELECT
@@ -104,6 +112,58 @@ std::unique_ptr<Socket> socket_ip(int type, int protocol);
/// File descriptors >= FD_SETSIZE will not be monitored and will log an error.
std::unique_ptr<Socket> socket_loop_monitored(int domain, int type, int protocol);
/// Create a send-only UDP socket of the given domain and protocol.
#ifdef USE_SOCKET_IMPL_LWIP_TCP
std::unique_ptr<UDPSendSocket> socket_udp_send(int domain, int protocol);
#else
inline std::unique_ptr<UDPSendSocket> socket_udp_send(int domain, int protocol) {
return esphome::socket::socket(domain, SOCK_DGRAM, protocol);
}
#endif
/// Create a send-only UDP socket in the newest available IP domain.
inline std::unique_ptr<UDPSendSocket> socket_ip_udp_send(int protocol) {
#if USE_NETWORK_IPV6
return socket_udp_send(AF_INET6, protocol);
#else
return socket_udp_send(AF_INET, protocol);
#endif
}
/// Create a UDP socket (send + receive) of the given domain and protocol.
#ifdef USE_SOCKET_IMPL_LWIP_TCP
std::unique_ptr<UDPSocket> socket_udp(int domain, int protocol);
#else
inline std::unique_ptr<UDPSocket> socket_udp(int domain, int protocol) {
return esphome::socket::socket(domain, SOCK_DGRAM, protocol);
}
#endif
/// Create a UDP socket (send + receive) in the newest available IP domain.
inline std::unique_ptr<UDPSocket> socket_ip_udp(int protocol) {
#if USE_NETWORK_IPV6
return socket_udp(AF_INET6, protocol);
#else
return socket_udp(AF_INET, protocol);
#endif
}
/// Create a UDP socket and monitor it for data in the main loop.
/// On LWIP_TCP platforms, wake is built into the recv callback so this just delegates to socket_udp().
/// On BSD/LWIP_SOCKETS platforms, this registers the socket with the Application's select() loop.
#ifdef USE_SOCKET_IMPL_LWIP_TCP
std::unique_ptr<UDPSocket> socket_udp_loop_monitored(int domain, int protocol);
#else
inline std::unique_ptr<UDPSocket> socket_udp_loop_monitored(int domain, int protocol) {
return socket_loop_monitored(domain, SOCK_DGRAM, protocol);
}
#endif
inline std::unique_ptr<UDPSocket> socket_ip_udp_loop_monitored(int protocol) {
#if USE_NETWORK_IPV6
return socket_udp_loop_monitored(AF_INET6, protocol);
#else
return socket_udp_loop_monitored(AF_INET, protocol);
#endif
}
/// Create a listening socket of the given domain, type and protocol.
/// Create a listening socket and monitor it for data in the main loop.
/// Create a listening socket in the newest available IP domain and monitor it.
@@ -111,7 +171,6 @@ std::unique_ptr<Socket> socket_loop_monitored(int domain, int type, int protocol
// LWIP_TCP has separate Socket/ListenSocket types — needs distinct factory functions.
std::unique_ptr<ListenSocket> socket_listen(int domain, int type, int protocol);
std::unique_ptr<ListenSocket> socket_listen_loop_monitored(int domain, int type, int protocol);
std::unique_ptr<ListenSocket> socket_ip_loop_monitored(int type, int protocol);
#else
// BSD and LWIP_SOCKETS: Socket == ListenSocket, so listen variants just delegate.
inline std::unique_ptr<ListenSocket> socket_listen(int domain, int type, int protocol) {
@@ -120,14 +179,14 @@ inline std::unique_ptr<ListenSocket> socket_listen(int domain, int type, int pro
inline std::unique_ptr<ListenSocket> socket_listen_loop_monitored(int domain, int type, int protocol) {
return socket_loop_monitored(domain, type, protocol);
}
#endif
inline std::unique_ptr<ListenSocket> socket_ip_loop_monitored(int type, int protocol) {
#if USE_NETWORK_IPV6
return socket_loop_monitored(AF_INET6, type, protocol);
return socket_listen_loop_monitored(AF_INET6, type, protocol);
#else
return socket_loop_monitored(AF_INET, type, protocol);
return socket_listen_loop_monitored(AF_INET, type, protocol);
#endif
}
#endif
/// Set a sockaddr to the specified address and port for the IP version used by socket_ip().
/// @param addr Destination sockaddr structure