Skip to content

Commit 26c202a

Browse files
committed
Fix safety issues with AsyncDNS
1 parent 73ae3de commit 26c202a

2 files changed

Lines changed: 74 additions & 34 deletions

File tree

wled00/asyncDNS.h

Lines changed: 66 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,50 +16,88 @@ class AsyncDNS {
1616
// with the IDF V4 bug external error handling is required anyway or dns can just stay stuck
1717
enum class result { Idle, Busy, Success, Error };
1818

19+
private:
20+
struct state_t {
21+
ip_addr_t raw_addr;
22+
std::atomic<result> status { result::Idle };
23+
uint16_t errorcount = 0;
24+
};
25+
std::shared_ptr<state_t> _state;
26+
27+
// callback for dns_gethostbyname(), called when lookup is complete or timed out
28+
static void _dns_callback(const char *name, const ip_addr_t *ipaddr, void *arg) {
29+
std::shared_ptr<state_t>* state_ptr = reinterpret_cast<std::shared_ptr<state_t>*>(arg);
30+
state_t& state = **state_ptr;
31+
32+
if (ipaddr) {
33+
state.raw_addr = *ipaddr;
34+
state.status = result::Success;
35+
} else {
36+
state.status = result::Error; // note: if query timed out (~5s), DNS lookup is broken until WiFi connection is reset (IDF V4 bug)
37+
state.errorcount++;
38+
}
39+
40+
delete state_ptr;
41+
}
42+
43+
public:
44+
AsyncDNS() : _state(std::make_shared<state_t>()) {};
45+
AsyncDNS(const AsyncDNS&) = delete; // noncopyable
46+
AsyncDNS& operator=(const AsyncDNS&) = delete; // noncopyable
47+
1948
// non-blocking query function to start DNS lookup
2049
result query(const char* hostname) {
21-
if (_status == result::Busy) return result::Busy; // in progress, waiting for callback
50+
if (_state->status == result::Busy) return result::Busy; // in progress, waiting for callback
51+
52+
std::shared_ptr<state_t>* callback_context = new std::shared_ptr<state_t>(_state);
53+
if (!callback_context) {
54+
_state->status = result::Error;
55+
_state->errorcount++;
56+
return result::Error;
57+
}
2258

23-
_status = result::Busy;
24-
err_t err = dns_gethostbyname(hostname, &_raw_addr, _dns_callback, this);
59+
_state->status = result::Busy;
60+
err_t err = dns_gethostbyname(hostname, &_state->raw_addr, _dns_callback, callback_context);
2561
if (err == ERR_OK) {
26-
_status = result::Success; // result already in cache
62+
_state->status = result::Success; // result already in cache
2763
} else if (err != ERR_INPROGRESS) {
28-
_status = result::Error;
29-
_errorcount++;
64+
_state->status = result::Error;
65+
_state->errorcount++;
3066
}
31-
return _status;
67+
return _state->status;
3268
}
3369

3470
// get the IP once Success is returned
35-
const IPAddress getIP() {
36-
if (_status != result::Success) return IPAddress(0,0,0,0);
71+
IPAddress getIP() const {
72+
if (_state->status != result::Success) return IPAddress(0,0,0,0);
3773
#ifdef ARDUINO_ARCH_ESP32
38-
return IPAddress(_raw_addr.u_addr.ip4.addr);
74+
return IPAddress(_state->raw_addr.u_addr.ip4.addr);
3975
#else
40-
return IPAddress(_raw_addr.addr);
76+
return IPAddress(_state->raw_addr.addr);
4177
#endif
4278
}
4379

44-
void renew() { _status = result::Idle; } // reset status to allow re-query
45-
void reset() { _status = result::Idle; _errorcount = 0; } // reset status and error count
46-
const result status() { return _status; }
47-
const uint16_t getErrorCount() { return _errorcount; }
48-
49-
private:
50-
ip_addr_t _raw_addr;
51-
std::atomic<result> _status { result::Idle };
52-
uint16_t _errorcount = 0;
80+
void renew() { // reset status to allow re-query
81+
if (_state->status == result::Busy) {
82+
// Abandon old state, but keep error count
83+
uint16_t ec = _state->errorcount;
84+
_state = std::make_shared<state_t>();
85+
_state->errorcount = ec;
86+
} else {
87+
_state->status = result::Idle;
88+
}
89+
}
5390

54-
// callback for dns_gethostbyname(), called when lookup is complete or timed out
55-
static void _dns_callback(const char *name, const ip_addr_t *ipaddr, void *arg) {
56-
AsyncDNS* instance = reinterpret_cast<AsyncDNS*>(arg);
57-
if (ipaddr) {
58-
instance->_raw_addr = *ipaddr;
59-
instance->_status = result::Success;
91+
void reset() { // reset status and error count
92+
if (_state->status == result::Busy) {
93+
// Abandon old state
94+
_state = std::make_shared<state_t>();
6095
} else {
61-
instance->_status = result::Error; // note: if query timed out (~5s), DNS lookup is broken until WiFi connection is reset (IDF V4 bug)
62-
instance->_errorcount++;
96+
_state->status = result::Idle;
97+
_state->errorcount = 0;
6398
}
6499
}
100+
101+
result status() const { return _state->status; }
102+
uint16_t getErrorCount() const { return _state->errorcount; }
65103
};

wled00/ntp.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "wled.h"
33
#include "fcn_declare.h"
44
#include "asyncDNS.h"
5+
#include <memory>
56

67
// WARNING: may cause errors in sunset calculations on ESP8266, see #3400
78
// building with `-D WLED_USE_REAL_MATH` will prevent those errors at the expense of flash and RAM
@@ -181,9 +182,11 @@ void handleTime() {
181182
}
182183
}
183184

185+
184186
void handleNetworkTime()
185187
{
186-
static AsyncDNS* ntpDNSlookup = nullptr;
188+
static std::unique_ptr<AsyncDNS> ntpDNSlookup;
189+
187190
if (ntpEnabled && ntpConnected && millis() - ntpLastSyncTime > (1000*NTP_SYNC_INTERVAL) && WLED_CONNECTED)
188191
{
189192
if (millis() - ntpPacketSentTime > 10000)
@@ -193,7 +196,8 @@ void handleNetworkTime()
193196
#endif
194197
if (!ntpServerIP.fromString(ntpServerName)) // check if server is IP or domain
195198
{
196-
if (ntpDNSlookup == nullptr) ntpDNSlookup = new AsyncDNS;
199+
if (!ntpDNSlookup) ntpDNSlookup = make_unique<AsyncDNS>();
200+
if (!ntpDNSlookup) return; // allocation failure, try again later
197201
AsyncDNS::result res = ntpDNSlookup->status();
198202
switch (res) {
199203
case AsyncDNS::result::Idle:
@@ -208,8 +212,7 @@ void handleNetworkTime()
208212
ntpServerIP = ntpDNSlookup->getIP();
209213
DEBUG_PRINTF_P(PSTR("NTP IP resolved: %s\n"), ntpServerIP.toString().c_str());
210214
sendNTPPacket();
211-
delete ntpDNSlookup;
212-
ntpDNSlookup = nullptr;
215+
ntpDNSlookup.reset();
213216
break;
214217

215218
case AsyncDNS::result::Error:
@@ -218,8 +221,7 @@ void handleNetworkTime()
218221
if (ntpDNSlookup->getErrorCount() > 6) {
219222
// after 6 failed attempts (30min), reset network connection as dns is probably stuck (TODO: IDF bug, should be fixed in V5)
220223
if (offMode) forceReconnect = true; // do not disturb while LEDs are running
221-
delete ntpDNSlookup;
222-
ntpDNSlookup = nullptr;
224+
ntpDNSlookup.reset();
223225
}
224226
ntpLastSyncTime = millis() - (1000*NTP_SYNC_INTERVAL - 300000); // pause for 5 minutes
225227
break;

0 commit comments

Comments
 (0)