ESPHome 2026.10.2
Loading...
Searching...
No Matches
socket.cpp
Go to the documentation of this file.
1#include "socket.h"
2#if defined(USE_SOCKET_IMPL_LWIP_TCP) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS)
3#include <cerrno>
4#include <cstring>
5#ifdef USE_SOCKET_IMPL_BSD_SOCKETS
6#include <sys/select.h>
7#endif
8#include <string>
9#include "esphome/core/log.h"
11#ifdef USE_HOST
12#include "esphome/core/wake.h"
13#endif
14
15namespace esphome::socket {
16
17#ifdef USE_HOST
18// Host: ready when the wake select() loop has flagged this fd (or it isn't monitored).
19bool socket_ready_fd(int fd, bool loop_monitored) { return !loop_monitored || wake_fd_ready(fd); }
20#elif defined(USE_ZEPHYR)
21// Zephyr (nRF52): fd monitoring isn't wired into the esphome select loop
22// (wake_register_fd is USE_HOST-only), so loop_monitored is always false. Always
23// return true — the caller handles EAGAIN/EWOULDBLOCK on read.
24//
25// Cost (known trade-off, not an oversight): loop-monitored sockets (API, web_server)
26// are read every loop() iteration and bail on EAGAIN; there is no event-driven wake,
27// so the main loop busy-polls at loop frequency and cannot idle between packets.
28// TODO: wire Zephyr fds into an event-driven wake source (e.g. zsock_poll/k_poll) so
29// the loop can sleep between packets on battery/OpenThread targets.
30bool socket_ready_fd(int /*fd*/, bool /*loop_monitored*/) { return true; }
31#endif
32
33// Platform-specific inet_ntop wrappers
34#if defined(USE_SOCKET_IMPL_LWIP_TCP)
35// LWIP raw TCP (ESP8266) uses inet_ntoa_r which takes struct by value
36static inline const char *esphome_inet_ntop4(const void *addr, char *buf, size_t size) {
37 inet_ntoa_r(*reinterpret_cast<const struct in_addr *>(addr), buf, size);
38 return buf;
39}
40#if USE_NETWORK_IPV6
41static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) {
42 inet6_ntoa_r(*reinterpret_cast<const ip6_addr_t *>(addr), buf, size);
43 return buf;
44}
45#endif
46#elif defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
47// LWIP sockets (LibreTiny, ESP32 Arduino)
48static inline const char *esphome_inet_ntop4(const void *addr, char *buf, size_t size) {
49 return lwip_inet_ntop(AF_INET, addr, buf, size);
50}
51#if USE_NETWORK_IPV6
52static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) {
53 return lwip_inet_ntop(AF_INET6, addr, buf, size);
54}
55#endif
56#elif defined(USE_ZEPHYR)
57// Zephyr BSD sockets — use Zephyr native address formatting via POSIX-subset wrappers.
58// <zephyr/net/socket.h> is already included transitively through <sys/socket.h>.
59static inline const char *esphome_inet_ntop4(const void *addr, char *buf, size_t size) {
60 return zsock_inet_ntop(AF_INET, addr, buf, size);
61}
62// IPv6 is always enabled on nRF52 (config validation enforces enable_ipv6=True),
63// but the guard is retained for consistency with other platform blocks.
64#if USE_NETWORK_IPV6
65static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) {
66 return zsock_inet_ntop(AF_INET6, addr, buf, size);
67}
68#endif
69#else
70// BSD sockets (host, ESP32-IDF)
71static inline const char *esphome_inet_ntop4(const void *addr, char *buf, size_t size) {
72 return inet_ntop(AF_INET, addr, buf, size);
73}
74#if USE_NETWORK_IPV6
75static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) {
76 return inet_ntop(AF_INET6, addr, buf, size);
77}
78#endif
79#endif
80
81bool sockaddr_to_ipv4(const struct sockaddr *addr, uint32_t *out) {
82 if (addr->sa_family == AF_INET) {
83 *out = reinterpret_cast<const struct sockaddr_in *>(addr)->sin_addr.s_addr;
84 return true;
85 }
86#if USE_NETWORK_IPV6
87 if (addr->sa_family == AF_INET6) {
88 // ::ffff:a.b.c.d; s6_addr is the portable byte view on every stack.
89 static constexpr uint8_t V4_MAPPED_PREFIX[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF};
90 const uint8_t *bytes = reinterpret_cast<const struct sockaddr_in6 *>(addr)->sin6_addr.s6_addr;
91 if (memcmp(bytes, V4_MAPPED_PREFIX, sizeof(V4_MAPPED_PREFIX)) == 0) {
92 memcpy(out, bytes + sizeof(V4_MAPPED_PREFIX), sizeof(*out));
93 return true;
94 }
95 }
96#endif
97 return false;
98}
99
100// Format sockaddr into caller-provided buffer, returns length written (excluding null)
101size_t format_sockaddr_to(const struct sockaddr *addr_ptr, socklen_t len, std::span<char, SOCKADDR_STR_LEN> buf) {
102 if (addr_ptr->sa_family == AF_INET && len >= sizeof(const struct sockaddr_in)) {
103 const auto *addr = reinterpret_cast<const struct sockaddr_in *>(addr_ptr);
104 if (esphome_inet_ntop4(&addr->sin_addr, buf.data(), buf.size()) != nullptr)
105 return strlen(buf.data());
106 }
107#if USE_NETWORK_IPV6
108 else if (addr_ptr->sa_family == AF_INET6 && len >= sizeof(sockaddr_in6)) {
109 const auto *addr = reinterpret_cast<const struct sockaddr_in6 *>(addr_ptr);
110 uint32_t v4;
111 // Format a v4 mapped peer as plain IPv4.
112 if (sockaddr_to_ipv4(addr_ptr, &v4) && esphome_inet_ntop4(&v4, buf.data(), buf.size()) != nullptr)
113 return strlen(buf.data());
114 if (esphome_inet_ntop6(&addr->sin6_addr, buf.data(), buf.size()) != nullptr)
115 return strlen(buf.data());
116 }
117#endif
118 buf[0] = '\0';
119 return 0;
120}
121
122std::unique_ptr<Socket> socket_ip(int type, int protocol) {
123#if USE_NETWORK_IPV6
124 return socket(AF_INET6, type, protocol);
125#else
126 return socket(AF_INET, type, protocol);
127#endif /* USE_NETWORK_IPV6 */
128}
129
130#ifdef USE_SOCKET_IMPL_LWIP_TCP
131// LWIP_TCP has separate Socket/ListenSocket types — needs out-of-line factory.
132// BSD and LWIP_SOCKETS define this inline in socket.h.
133std::unique_ptr<ListenSocket> socket_ip_loop_monitored(int type, int protocol) {
134#if USE_NETWORK_IPV6
135 return socket_listen_loop_monitored(AF_INET6, type, protocol);
136#else
137 return socket_listen_loop_monitored(AF_INET, type, protocol);
138#endif /* USE_NETWORK_IPV6 */
139}
140#endif
141
142socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_address, uint16_t port) {
143#if USE_NETWORK_IPV6
144 if (strchr(ip_address, ':') != nullptr) {
145 if (addrlen < sizeof(sockaddr_in6)) {
146 errno = EINVAL;
147 return 0;
148 }
149 auto *server = reinterpret_cast<sockaddr_in6 *>(addr);
150 memset(server, 0, sizeof(sockaddr_in6));
151 server->sin6_family = AF_INET6;
152 server->sin6_port = htons(port);
153
154#ifdef USE_SOCKET_IMPL_BSD_SOCKETS
155#if defined(USE_ZEPHYR)
156 // Zephyr BSD sockets: use native address conversion
157 if (zsock_inet_pton(AF_INET6, ip_address, &server->sin6_addr) != 1) {
158 errno = EINVAL;
159 return 0;
160 }
161#else
162 // Use standard inet_pton for BSD sockets
163 if (inet_pton(AF_INET6, ip_address, &server->sin6_addr) != 1) {
164 errno = EINVAL;
165 return 0;
166 }
167#endif
168#else
169 // Use LWIP-specific functions
170 ip6_addr_t ip6;
171 if (inet6_aton(ip_address, &ip6) == 0) {
172 errno = EINVAL;
173 return 0;
174 }
175 memcpy(server->sin6_addr.un.u32_addr, ip6.addr, sizeof(ip6.addr));
176#endif
177 return sizeof(sockaddr_in6);
178 }
179#endif /* USE_NETWORK_IPV6 */
180 if (addrlen < sizeof(sockaddr_in)) {
181 errno = EINVAL;
182 return 0;
183 }
184 auto *server = reinterpret_cast<sockaddr_in *>(addr);
185 memset(server, 0, sizeof(sockaddr_in));
186 server->sin_family = AF_INET;
187#if defined(USE_ZEPHYR)
188 // Zephyr BSD sockets: use native address conversion
189 if (zsock_inet_pton(AF_INET, ip_address, &server->sin_addr) != 1) {
190 errno = EINVAL;
191 return 0;
192 }
193#else
194 // inet_aton() reports failure separately from the address, so 255.255.255.255 needs no special case
195 if (inet_aton(ip_address, &server->sin_addr) == 0) {
196 errno = EINVAL;
197 return 0;
198 }
199#endif
200 server->sin_port = htons(port);
201 return sizeof(sockaddr_in);
202}
203
204#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
206 int fd = sock.get_fd();
207 if (fd < 0 || fd >= FD_SETSIZE) {
208 // FD_SET on either is undefined behavior
209 err_out = EBADF;
211 }
212 // Connect completion is a write event; the main loop only selects on reads
213 fd_set writefds;
214 FD_ZERO(&writefds);
215 FD_SET(fd, &writefds);
216 struct timeval tv = {0, 0};
217#ifdef USE_SOCKET_IMPL_LWIP_SOCKETS
218 // LWIP_COMPAT_SOCKETS may be off (LibreTiny), so use the lwip symbol directly
219 int ret = lwip_select(fd + 1, nullptr, &writefds, nullptr, &tv);
220#else
221 // Global-scope select: the entity namespace esphome::select shadows it here
222 int ret = ::select(fd + 1, nullptr, &writefds, nullptr, &tv);
223#endif
224 if (ret < 0) {
225 err_out = errno;
227 }
228 if (ret == 0) {
230 }
231 int error = 0;
232 socklen_t len = sizeof(error);
233 if (sock.getsockopt(SOL_SOCKET, SO_ERROR, &error, &len) != 0) {
234 err_out = errno;
236 }
237 if (error != 0) {
238 err_out = error;
240 }
242}
243#endif
244
245socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port) {
246#if USE_NETWORK_IPV6
247 if (addrlen < sizeof(sockaddr_in6)) {
248 errno = EINVAL;
249 return 0;
250 }
251 auto *server = reinterpret_cast<sockaddr_in6 *>(addr);
252 memset(server, 0, sizeof(sockaddr_in6));
253 server->sin6_family = AF_INET6;
254 server->sin6_port = htons(port);
255 server->sin6_addr = IN6ADDR_ANY_INIT;
256 return sizeof(sockaddr_in6);
257#else
258 if (addrlen < sizeof(sockaddr_in)) {
259 errno = EINVAL;
260 return 0;
261 }
262 auto *server = reinterpret_cast<sockaddr_in *>(addr);
263 memset(server, 0, sizeof(sockaddr_in));
264 server->sin_family = AF_INET;
265 server->sin_addr.s_addr = ESPHOME_INADDR_ANY;
266 server->sin_port = htons(port);
267 return sizeof(sockaddr_in);
268#endif /* USE_NETWORK_IPV6 */
269}
270} // namespace esphome::socket
271#endif
int getsockopt(int level, int optname, void *optval, socklen_t *optlen)
uint16_t type
std::vector< uint8_t > & out
Definition gif.cpp:49
uint32_t socklen_t
Definition headers.h:99
struct in6_addr ip6_addr_t
Definition ip_address.h:24
int ret
ConnectPollResult poll_connect(Socket &sock, int &err_out)
Poll a connect() that returned EINPROGRESS.
Definition socket.cpp:205
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().
Definition socket.cpp:142
std::unique_ptr< Socket > socket_ip(int type, int protocol)
Create a socket in the newest available IP domain (IPv6 or IPv4) of the given type and protocol.
Definition socket.cpp:122
size_t format_sockaddr_to(const struct sockaddr *addr_ptr, socklen_t len, std::span< char, SOCKADDR_STR_LEN > buf)
Format sockaddr into caller-provided buffer, returns length written (excluding null)
Definition socket.cpp:101
ConnectPollResult
Outcome of polling a non-blocking connect(); see socket::poll_connect().
Definition headers.h:209
std::unique_ptr< ListenSocket > socket_listen_loop_monitored(int domain, int type, int protocol)
std::unique_ptr< Socket > socket(int domain, int type, int protocol)
Create a socket of the given domain, type and protocol.
bool sockaddr_to_ipv4(const struct sockaddr *addr, uint32_t *out)
Network order IPv4 of an AF_INET or v4 mapped AF_INET6 peer; false for anything else.
Definition socket.cpp:81
bool socket_ready_fd(int fd, bool loop_monitored)
Shared ready() helper for fd-based socket implementations.
Definition socket.cpp:19
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().
Definition socket.cpp:245
std::unique_ptr< ListenSocket > socket_ip_loop_monitored(int type, int protocol)
Definition socket.cpp:133
bool ESPHOME_ALWAYS_INLINE wake_fd_ready(int fd)
Definition wake_host.h:42
const void size_t len
Definition hal.h:64
uint16_t size
Definition helpers.cpp:33
static void uint32_t
sa_family_t sa_family
Definition headers.h:87
Platform-specific main loop wake primitives.