ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
ipv4_resolve.cpp
Go to the documentation of this file.
1#include "ipv4_resolve.h"
2
3#if defined(USE_SOCKET_IMPL_LWIP_TCP) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS)
4
5#include "socket.h"
7#include "esphome/core/log.h"
8
9#include <cstring>
10
11#if !defined(USE_HOST) && !defined(USE_ZEPHYR)
12#include "lwip/dns.h"
13#else
14#include <netdb.h>
15#endif
16
17namespace esphome::socket {
18
19ESPHOME_LOG_TAG(TAG, "socket");
20
22#if defined(IPV4_RESOLVE_ATOMIC_STATE)
23 uint8_t expected = STATE_FAILED;
24 return this->state_word_.compare_exchange_strong(expected, STATE_IDLE);
25#else
26 if (this->state_word_ != STATE_FAILED) {
27 return false;
28 }
29 this->state_word_ = STATE_IDLE;
30 return true;
31#endif
32}
33
35#if defined(IPV4_RESOLVE_VOLATILE_EPOCH)
36 // The generation moves first; RESOLVING stays set until the callback
37 // drops the stale result, so start() cannot replace that lookup.
38 this->epoch_ = this->epoch_ + 1;
39 this->addr_word_ = 0;
40 if (this->state_word_ != STATE_RESOLVING) {
41 this->state_word_ = STATE_IDLE;
42 }
43#elif defined(IPV4_RESOLVE_ATOMIC_STATE)
44 // PUBLISHING stays, so start() cannot queue a second callback
45 // while this one is storing the address.
46 uint8_t expected = STATE_RESOLVING;
47 if (this->state_word_.compare_exchange_strong(expected, STATE_IDLE)) {
48 this->set_addr_(0);
49 return;
50 }
51 if (this->state_() == STATE_PUBLISHING) {
52 return;
53 }
54 this->set_state_(STATE_IDLE);
55 this->set_addr_(0);
56#else
57 this->set_state_(STATE_IDLE);
58 this->set_addr_(0);
59#endif
60}
61
62socklen_t Ipv4Resolve::to_sockaddr(struct sockaddr *dest, socklen_t destlen, uint16_t port) const {
63 if (this->state_() != STATE_RESOLVED || destlen < sizeof(sockaddr_in)) {
64 return 0;
65 }
66 auto *in = reinterpret_cast<sockaddr_in *>(dest);
67 memset(in, 0, sizeof(sockaddr_in));
68 in->sin_family = AF_INET;
69 in->sin_port = htons(port);
70 in->sin_addr.s_addr = this->addr_();
71 return sizeof(sockaddr_in);
72}
73
74#if !defined(USE_HOST) && !defined(USE_ZEPHYR)
75#if defined(IPV4_RESOLVE_VOLATILE_EPOCH)
76bool Ipv4Resolve::drop_stale_(uint32_t expected) {
77 if (this->epoch_ == expected) {
78 return false;
79 }
80 this->state_word_ = STATE_IDLE;
81 this->addr_word_ = 0;
82 return true;
83}
84#endif
85
86void Ipv4Resolve::dns_found(const char *name, const ip_addr_t *addr, void *arg) {
87 auto *self = static_cast<Ipv4Resolve *>(arg);
88#if defined(IPV4_RESOLVE_VOLATILE_EPOCH)
89 const uint32_t expected = self->pending_epoch_;
90 if (self->drop_stale_(expected)) {
91 return;
92 }
93#endif
94 if (addr != nullptr && IP_IS_V4(addr)) {
95#if defined(IPV4_RESOLVE_ATOMIC_STATE)
96 // Only the callback that wins the exchange owns the lookup.
97 uint8_t expected = STATE_RESOLVING;
98 if (!self->state_word_.compare_exchange_strong(expected, STATE_PUBLISHING)) {
99 return;
100 }
101 self->set_addr_(ip4_addr_get_u32(ip_2_ip4(addr)));
102 expected = STATE_PUBLISHING;
103 // Lost ownership; the leftover address is gated by to_sockaddr()'s state check.
104 if (!self->state_word_.compare_exchange_strong(expected, STATE_RESOLVED)) {
105 return;
106 }
107#elif defined(IPV4_RESOLVE_VOLATILE_EPOCH)
108 self->addr_word_ = ip4_addr_get_u32(ip_2_ip4(addr));
109 self->state_word_ = STATE_RESOLVED;
110 if (self->drop_stale_(expected)) {
111 return;
112 }
113#else
114 // Do not publish over a newer start().
115 if (self->state_() != STATE_RESOLVING) {
116 return;
117 }
118 self->set_addr_(ip4_addr_get_u32(ip_2_ip4(addr)));
119 self->set_state_(STATE_RESOLVED);
120#endif
121 } else {
122 ESP_LOGW(self->tag_ != nullptr ? self->tag_ : TAG, "DNS failed for %s", name);
123#if defined(IPV4_RESOLVE_ATOMIC_STATE)
124 uint8_t expected = STATE_RESOLVING;
125 self->state_word_.compare_exchange_strong(expected, STATE_FAILED);
126#elif defined(IPV4_RESOLVE_VOLATILE_EPOCH)
127 self->state_word_ = STATE_FAILED;
128 if (self->drop_stale_(expected)) {
129 return;
130 }
131#else
132 if (self->state_() != STATE_RESOLVING) {
133 return;
134 }
135 self->set_state_(STATE_FAILED);
136#endif
137 }
138}
139#endif
140
141void Ipv4Resolve::start(const char *host, uint16_t port, const char *tag) {
142 const uint8_t state = this->state_();
144 return;
145 }
146 this->set_state_(STATE_IDLE);
147 this->tag_ = tag;
148 struct sockaddr_storage literal;
149 if (set_sockaddr(reinterpret_cast<struct sockaddr *>(&literal), sizeof(literal), host, port) != 0) {
150 if (literal.ss_family == AF_INET) {
151 auto *in = reinterpret_cast<sockaddr_in *>(&literal);
152 this->set_addr_(in->sin_addr.s_addr);
153 this->set_state_(STATE_RESOLVED);
154 return;
155 }
156 this->set_state_(STATE_FAILED);
157 ESP_LOGW(tag, "Not an IPv4 address: %s", host);
158 return;
159 }
160#if !defined(USE_HOST) && !defined(USE_ZEPHYR)
161 ip_addr_t cached;
162 err_t err;
163 {
165#if defined(IPV4_RESOLVE_VOLATILE_EPOCH)
166 this->pending_epoch_ = this->epoch_;
167#endif
168 this->set_state_(STATE_RESOLVING);
169 err = dns_gethostbyname_addrtype(host, &cached, &Ipv4Resolve::dns_found, this, LWIP_DNS_ADDRTYPE_IPV4);
170 if (err != ERR_INPROGRESS && this->state_() == STATE_RESOLVING) {
171 this->set_state_(STATE_IDLE);
172 }
173 }
174 if (err == ERR_OK && IP_IS_V4(&cached)) {
175 this->set_addr_(ip4_addr_get_u32(ip_2_ip4(&cached)));
176 this->set_state_(STATE_RESOLVED);
177 return;
178 }
179 if (err == ERR_INPROGRESS || this->state_() == STATE_RESOLVED || this->state_() == STATE_PUBLISHING) {
180 return;
181 }
182#else
183 struct addrinfo hints {};
184 hints.ai_family = AF_INET;
185 hints.ai_socktype = SOCK_STREAM;
186 struct addrinfo *res = nullptr;
187 if (getaddrinfo(host, nullptr, &hints, &res) == 0 && res != nullptr) {
188 auto *in = reinterpret_cast<struct sockaddr_in *>(res->ai_addr);
189 if (res->ai_family == AF_INET) {
190 this->set_addr_(in->sin_addr.s_addr);
191 this->set_state_(STATE_RESOLVED);
192 }
193 freeaddrinfo(res);
194 if (this->ready()) {
195 return;
196 }
197 }
198#endif
199 this->set_state_(STATE_FAILED);
200 ESP_LOGW(tag, "Could not resolve %s", host);
201}
202
203} // namespace esphome::socket
204
205#endif
Helper class to lock the lwIP TCPIP core when making lwIP API calls from non-TCPIP threads.
Definition helpers.h:2107
void start(const char *host, uint16_t port, const char *tag)
Resolve host; tag names the failure log.
bool consume_failure()
Drop a failed lookup so the next start() tries again.
static constexpr uint8_t STATE_RESOLVED
static constexpr uint8_t STATE_RESOLVING
socklen_t to_sockaddr(struct sockaddr *dest, socklen_t destlen, uint16_t port) const
Write the stored address into dest. Returns 0 until ready() is true.
static constexpr uint8_t STATE_IDLE
static constexpr uint8_t STATE_FAILED
static constexpr uint8_t STATE_PUBLISHING
void forget()
Drop the stored address so the next start() resolves again.
bool state
Definition fan.h:2
uint32_t socklen_t
Definition headers.h:109
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().
ESPHOME_LOG_TAG(TAG, "socket")
static void uint32_t
sa_family_t ss_family
Definition headers.h:104
SemaphoreHandle_t lock