ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
dns_resolver.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file dns_resolver.h
6 * @brief Layer 3 (Network) - the asking side of DNS: a question out, an A record back
7 * (PROTOCORE_ENABLE_DNS_RESOLVER).
8 *
9 * RFC 1035 sec 4.1.2 QNAME in, RFC 1035 sec 3.4.1 A RDATA out. The query carries one question,
10 * QTYPE A, QCLASS IN, with RD set (RFC 1035 sec 4.1.1), and travels over UDP to server port 53
11 * (RFC 1035 sec 4.2.1).
12 *
13 * A response is accepted only when its ID echoes the query's, QR is set, and RCODE is 0
14 * (RFC 1035 sec 4.1.1); RFC 5452 sec 9.1 names the ID as one of the attributes a response has to
15 * match before its data is used. The address it yields is then classified against the IPv4
16 * Special-Purpose Address Registry (RFC 6890 sec 2.2.2) and the host group range
17 * (RFC 1112 sec 4): a remote name answering with "This host on this network", Limited Broadcast,
18 * Loopback, or a host group address is refused.
19 *
20 * Nothing here blocks. The module owns one timer and one in-flight query: ::ResolverNs::resolve
21 * starts the query, marks itself busy and reports ::PROTOCORE_DNS_BUSY, and the caller asks again on
22 * its own tick. The response arrives on the UDP listener's normal drain, so the answer lands without
23 * this module pumping anything. Busy is the sending side only, and the response handler is always
24 * armed.
25 *
26 * Two backends, chosen by PROTOCORE_HAS_VENDOR_DNS_RESOLVER. Where the platform's stack has its own
27 * resolver the module marshals into it and inherits its nameserver list and its cache. Where it does
28 * not, the portable resolver asks PROTOCORE_DNS_SERVER over the UDP listener, one query at a time
29 * with no cache, and ::ResolverNs::set_server points it at whatever address DHCP or provisioning
30 * turned up.
31 *
32 * The query and the response are codecs in their own right, so they are reached as calls and tested
33 * as such: ::ResolverNs::query_build writes the question section, ::ResolverNs::answer_parse reads
34 * the first A record back, and the resolve is what puts a socket between them.
35 *
36 * @author Douglas Quigg (dstroy0)
37 * @date 2026
38 */
39
40#ifndef PROTOCORE_DNS_RESOLVER_H
41#define PROTOCORE_DNS_RESOLVER_H
42
43#include "protocore_config.h"
44
45#if PROTOCORE_ENABLE_DNS_RESOLVER
46
48
49/**
50 * @brief RFC 1034 sec 5.3.1: what the platform's resolver hands back when an answer arrives.
51 *
52 * The name is echoed so one callback can serve several outstanding queries; @p addr is null when
53 * the name did not resolve.
54 */
55typedef void (*protocore_net_dns_found_fn)(const char *name, const protocore_net_ip *addr, void *arg);
56
57/** @brief IPv4 address category: RFC 6890 sec 2.2.2 registry entries, plus RFC 1112 sec 4. */
58typedef enum PROTO_ENUM_PACKED
59{
60 PROTOCORE_IP_UNSPECIFIED = 0, ///< 0.0.0.0/8 "This host on this network" (RFC 6890 sec 2.2.2)
61 PROTOCORE_IP_LOOPBACK, ///< 127.0.0.0/8 Loopback (RFC 6890 sec 2.2.2)
62 PROTOCORE_IP_PRIVATE, ///< 10/8, 172.16/12, 192.168/16 Private-Use (RFC 6890 sec 2.2.2)
63 PROTOCORE_IP_LINKLOCAL, ///< 169.254.0.0/16 Link Local (RFC 6890 sec 2.2.2)
64 PROTOCORE_IP_MULTICAST, ///< 224.0.0.0 to 239.255.255.255 host group (RFC 1112 sec 4)
65 PROTOCORE_IP_BROADCAST, ///< 255.255.255.255/32 Limited Broadcast (RFC 6890 sec 2.2.2)
66 PROTOCORE_IP_PUBLIC, ///< globally-routable unicast
67} protocore_ip_class;
68
69/** @brief Where a resolve stands. */
70typedef enum PROTO_ENUM_PACKED
71{
72 PROTOCORE_DNS_READY = 0, ///< u32 holds the address
73 PROTOCORE_DNS_BUSY, ///< a query is out; ask again on the next tick
74 PROTOCORE_DNS_FAILED, ///< the query did not leave, or the one in flight passed its deadline
75} protocore_dns_state;
76
77/** @brief The address a classify or a verify judges, as a host-order word. */
78typedef struct
79{
80 uint32_t ip; ///< the IPv4 address, host order (e.g. (10u << 24) | (0u << 16) | (0u << 8) | 1u)
81} DnsAddrArgs;
82
83/** @brief RFC 1035 sec 4.1.2 question section: the name asked for, its ID, and where it is written. */
84typedef struct
85{
86 const char *host; ///< QNAME, dotted (RFC 1035 sec 4.1.2)
87 uint16_t id; ///< the header ID a build stamps and a parse demands (RFC 1035 sec 4.1.1)
88 uint8_t *out; ///< where a build writes the query message
89 size_t cap; ///< how many octets that has
90} DnsQueryArgs;
91
92/** @brief RFC 1035 sec 4.1.3 answer section: the response message a parse reads. */
93typedef struct
94{
95 const uint8_t *pkt; ///< the response message as it arrived
96 size_t len; ///< its length in octets
97} DnsAnswerArgs;
98
99/** @brief The nameserver the portable backend sends its queries to (RFC 1035 sec 4.2.1). */
100typedef struct
101{
102 const char *ip; ///< its address as a dotted quad
103} DnsServerArgs;
104
105/**
106 * @brief The DNS resolver.
107 *
108 * A caller sets the members a call takes, invokes it through ::Resolver, and reads the outcome off
109 * the same handle. The in-flight query and its timer are behind @ref internal.
110 *
111 * @var ResolverNs::addr the address a classify or a verify judges
112 * @var ResolverNs::query the question a resolve or a build asks (RFC 1035 sec 4.1.2)
113 * @var ResolverNs::answer the response message a parse reads (RFC 1035 sec 4.1.3)
114 * @var ResolverNs::server the nameserver the portable backend asks (RFC 1035 sec 4.2.1)
115 * @var ResolverNs::ok a call's true/false outcome
116 * @var ResolverNs::n octets a build wrote, or 0 when the name does not encode or does not fit
117 * @var ResolverNs::u32 the address a resolve or a parse reports, host order; 0 when it has none
118 * @var ResolverNs::cls the category a classify reports
119 * @var ResolverNs::state where a resolve stands
120 * @var ResolverNs::classify what kind of address a host-order IPv4 word names
121 * @var ResolverNs::verify whether that word is a plausible A record for a remote host
122 * @var ResolverNs::query_build write a standard A-record question (RFC 1035 sec 4.1.1, sec 4.1.2)
123 * @var ResolverNs::answer_parse read the first A record out of a response (RFC 1035 sec 3.4.1)
124 * @var ResolverNs::resolve ask for a host's IPv4 address, host order
125 * @var ResolverNs::resolve_verified as resolve, and require the answer to pass @ref verify
126 * @var ResolverNs::busy a query is out
127 * @var ResolverNs::set_server point the portable backend at a nameserver, as a literal address
128 *
129 * resolve answers a dotted quad from the name itself and reports ::PROTOCORE_DNS_READY. Any other
130 * name starts a query, marks the module busy and reports ::PROTOCORE_DNS_BUSY; the caller asks again
131 * with the same host on its next tick and gets ::PROTOCORE_DNS_READY once the response parses, or
132 * ::PROTOCORE_DNS_FAILED once PROTOCORE_DNS_TIMEOUT_MS passes. One query is in flight at a time, so
133 * a second host asked while busy reports ::PROTOCORE_DNS_BUSY until the first settles.
134 *
135 * query_build writes a header carrying @ref DnsQueryArgs::id with RD set and QDCOUNT 1, then one
136 * question: the name, QTYPE A, QCLASS IN.
137 *
138 * answer_parse refuses a response whose ID is not @ref DnsQueryArgs::id, that is not a response,
139 * that carries a nonzero RCODE, or that holds no A record in class IN (RFC 1035 sec 4.1.1,
140 * RFC 5452 sec 9.1). It walks past CNAMEs and any other TYPE rather than assuming the first answer
141 * is the address, and follows the compression pointers those names use (RFC 1035 sec 4.1.4). It
142 * also reports false when the module's storage is unavailable.
143 *
144 * set_server replaces PROTOCORE_DNS_SERVER with what DHCP or provisioning turned up, once the app
145 * has it. It reports false when the address does not parse, and the previous server stands. On the
146 * vendor backend the stack owns its own nameserver list, so it reports false and changes nothing.
147 */
148typedef struct
149{
150 DnsAddrArgs addr; ///< what a classify or a verify judges (RFC 6890 sec 2.2.2)
151 DnsQueryArgs query; ///< what a question names (RFC 1035 sec 4.1.2)
152 DnsAnswerArgs answer; ///< what a parse reads (RFC 1035 sec 4.1.3)
153 DnsServerArgs server; ///< where a portable query is sent (RFC 1035 sec 4.2.1)
154 proto_bool ok;
155 size_t n;
156 uint32_t u32;
157 protocore_ip_class cls;
158 protocore_dns_state state;
159} ResolverVars;
160
161/** @brief The operands and the outcome. */
162extern ResolverVars ResolverV;
163
164/** @brief The entries. */
165typedef struct
166{
167 void (*const classify)(uint8_t *work);
168 void (*const verify)(uint8_t *work);
169 void (*const query_build)(uint8_t *work);
170 void (*const answer_parse)(uint8_t *work);
171 void (*const resolve)(uint8_t *work);
172 void (*const resolve_verified)(uint8_t *work);
173 void (*const busy)(uint8_t *work);
174 void (*const set_server)(uint8_t *work);
175} ResolverNs;
176
177// What the table binds, defined once in the .c and taking one parameter each: everything
178// else an entry needs is an operand in ResolverV or a region of the borrow at a fixed offset.
179void protocore_resolver_classify(uint8_t *work);
180void protocore_resolver_verify(uint8_t *work);
181void protocore_resolver_query_build(uint8_t *work);
182void protocore_resolver_answer_parse(uint8_t *work);
183void protocore_resolver_resolve(uint8_t *work);
184void protocore_resolver_resolve_verified(uint8_t *work);
185void protocore_resolver_busy(uint8_t *work);
186void protocore_resolver_set_server(uint8_t *work);
187
188// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
189// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
190// `Resolver.classify(work)` resolves to a named function and becomes a DIRECT call. An extern table
191// leaves the call indirect and the symbol live at every level, -O2 -flto included.
192static const ResolverNs Resolver __attribute__((unused)) = {
193 .classify = protocore_resolver_classify,
194 .verify = protocore_resolver_verify,
195 .query_build = protocore_resolver_query_build,
196 .answer_parse = protocore_resolver_answer_parse,
197 .resolve = protocore_resolver_resolve,
198 .resolve_verified = protocore_resolver_resolve_verified,
199 .busy = protocore_resolver_busy,
200 .set_server = protocore_resolver_set_server,
201};
202
203/**
204 * @brief The PROTOCORE_DNS_RESOLVER_BORROW bytes this module's state lives in.
205 *
206 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
207 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
208 * walks, so the state lasts the life of the program.
209 *
210 * @return the span.
211 */
212uint8_t *protocore_dns_resolver_span(void);
213
215
216#endif // PROTOCORE_ENABLE_DNS_RESOLVER
217#endif // PROTOCORE_DNS_RESOLVER_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97