ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
dns_resolver.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file dns_resolver.cpp
6 * @brief IPv4 classifier / verifier (pure) + lwIP DNS resolve (ESP32).
7 *
8 * The resolve marshals dns_gethostbyname into tcpip_thread and polls a done flag
9 * with a deadline - the same cross-thread pattern the http_client uses.
10 */
11
13
14#if PC_NEED_DNS_RESOLVER
15
16#if defined(ARDUINO)
17#include "lwip/def.h"
18#include "lwip/dns.h"
19#include "lwip/ip_addr.h"
20#include "lwip/priv/tcpip_priv.h"
21#include "services/system/clock.h" // pc_millis() - the single pluggable monotonic source
22#include <Arduino.h>
23#endif
24pc_ip_class pc_dns_resolver_classify(uint32_t ip)
25{
26 if (ip == 0u)
27 {
28 return pc_ip_class::PC_IP_UNSPECIFIED;
29 }
30 if (ip == 0xFFFFFFFFu)
31 {
32 return pc_ip_class::PC_IP_BROADCAST;
33 }
34 uint8_t a = (uint8_t)((ip >> 24) & 0xFF);
35 uint8_t b = (uint8_t)((ip >> 16) & 0xFF);
36 if (a == 127)
37 {
38 return pc_ip_class::PC_IP_LOOPBACK;
39 }
40 if (a == 10)
41 {
42 return pc_ip_class::PC_IP_PRIVATE;
43 }
44 if (a == 172 && b >= 16 && b <= 31)
45 {
46 return pc_ip_class::PC_IP_PRIVATE;
47 }
48 if (a == 192 && b == 168)
49 {
50 return pc_ip_class::PC_IP_PRIVATE;
51 }
52 if (a == 169 && b == 254)
53 {
54 return pc_ip_class::PC_IP_LINKLOCAL;
55 }
56 if (a >= 224 && a <= 239)
57 {
58 return pc_ip_class::PC_IP_MULTICAST;
59 }
60 return pc_ip_class::PC_IP_PUBLIC;
61}
62
63bool pc_dns_resolver_verify(uint32_t ip)
64{
65 switch (pc_dns_resolver_classify(ip))
66 {
67 case pc_ip_class::PC_IP_UNSPECIFIED: // 0.0.0.0 - blocked / no answer
68 case pc_ip_class::PC_IP_BROADCAST: // 255.255.255.255 - never a host
69 case pc_ip_class::PC_IP_LOOPBACK: // 127.x - DNS-rebinding to localhost
70 case pc_ip_class::PC_IP_MULTICAST: // 224-239 - never an A-record host
71 return false;
72 default:
73 return true; // private / link-local / public are plausible
74 }
75}
76
77#ifdef ARDUINO
78
79namespace
80{
81// All DNS-resolve binding state, owned by one instance (internal linkage): the resolved
82// address plus the done/ok flags the lwIP callback sets, grouped so it is one named owner,
83// unreachable cross-TU. The flags are volatile: the callback runs on tcpip_thread while the
84// resolve loop polls them.
85struct DnsResolverCtx
86{
87 ip_addr_t addr;
88 volatile bool done = false;
89 volatile bool ok = false;
90};
91DnsResolverCtx s_dr;
92
93struct DnsCall
94{
95 struct tcpip_api_call_data base;
96 const char *host;
97};
98
99void dns_cb(const char *name, const ip_addr_t *addr, void *arg)
100{
101 (void)name;
102 (void)arg;
103 if (addr)
104 {
105 s_dr.addr = *addr;
106 s_dr.ok = true;
107 }
108 s_dr.done = true;
109}
110
111err_t do_dns(struct tcpip_api_call_data *c)
112{
113 const char *host = ((DnsCall *)c)->host;
114 err_t e = dns_gethostbyname(host, &s_dr.addr, dns_cb, nullptr);
115 if (e == ERR_OK) // already cached
116 {
117 s_dr.ok = true;
118 s_dr.done = true;
119 }
120 else if (e != ERR_INPROGRESS) // hard failure
121 {
122 s_dr.done = true;
123 }
124 return ERR_OK;
125}
126
127uint32_t to_host_order(const ip_addr_t *a)
128{
129 return lwip_ntohl(ip4_addr_get_u32(ip_2_ip4(a)));
130}
131} // namespace
132
133bool pc_dns_resolver_resolve(const char *host, uint32_t *out_ip)
134{
135 if (!host || !out_ip)
136 {
137 return false;
138 }
139
140 ip_addr_t literal;
141 if (ipaddr_aton(host, &literal)) // dotted-quad fast path, no DNS
142 {
143 *out_ip = to_host_order(&literal);
144 return true;
145 }
146
147 s_dr.done = false;
148 s_dr.ok = false;
149 DnsCall k;
150 memset(&k, 0, sizeof(k));
151 k.host = host;
152 tcpip_api_call(do_dns, &k.base); // resolve in the lwIP thread
153
154 uint32_t deadline = pc_millis() + PC_DNS_TIMEOUT_MS;
155 while (!s_dr.done && (int32_t)(deadline - pc_millis()) > 0)
156 {
157 pcdelay(5);
158 }
159
160 if (!s_dr.ok)
161 {
162 return false;
163 }
164 *out_ip = to_host_order(&s_dr.addr);
165 return true;
166}
167
168#else // host build - no real resolver; a host test can inject a synthetic answer
169
170namespace
171{
172// The injected synthetic answer, owned by one instance (internal linkage): a host test sets
173// it via pc_dns_resolver_test_set_resolve() and pc_dns_resolver_resolve() returns it, grouped so it is one
174// named owner rather than loose file-scope mutables.
175struct DnsTestCtx
176{
177 bool ok = false;
178 uint32_t ip = 0;
179};
180DnsTestCtx s_dns_test;
181} // namespace
182
183void pc_dns_resolver_test_set_resolve(bool ok, uint32_t ip)
184{
185 s_dns_test.ok = ok;
186 s_dns_test.ip = ip;
187}
188bool pc_dns_resolver_resolve(const char *, uint32_t *out_ip)
189{
190 if (!s_dns_test.ok)
191 {
192 return false;
193 }
194 if (out_ip)
195 {
196 *out_ip = s_dns_test.ip;
197 }
198 return true;
199}
200
201#endif // ARDUINO
202
203bool pc_dns_resolver_resolve_verified(const char *host, uint32_t *out_ip)
204{
205 uint32_t ip = 0;
206 if (!pc_dns_resolver_resolve(host, &ip))
207 {
208 return false;
209 }
210 if (!pc_dns_resolver_verify(ip))
211 {
212 return false;
213 }
214 if (out_ip)
215 {
216 *out_ip = ip;
217 }
218 return true;
219}
220
221#endif // PC_NEED_DNS_RESOLVER
Pluggable monotonic clock for all library timing.
void pcdelay(uint32_t ms)
Block for at least ms milliseconds - the library's single delay primitive.
Definition clock.h:89
uint32_t pc_millis(void)
The library's monotonic time at 1000 Hz (milliseconds).
Definition clock.h:71
DNS resolver with answer verification (PC_ENABLE_DNS_RESOLVER).
#define PC_DNS_TIMEOUT_MS
DNS resolve timeout in milliseconds.