ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
dns_server.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_server.h
6 * @brief Layer 3 name service - the answering side of DNS (RFC 1035), UDP port 53.
7 *
8 * A name server for a network with no path to a real one: register `name -> IPv4` A records
9 * (RFC 1035 sec 3.4.1) with @ref DnsServerNs::add and the device answers a question whose
10 * QTYPE is A and QCLASS is IN from that fixed table, and answers RCODE 3 Name Error for a name
11 * the table does not hold (RFC 1035 sec 4.1.1). Devices then use `printer.lan` instead of
12 * `192.168.1.5`, a companion to the NTP server for self-hosted, offline infrastructure. Zero
13 * heap; gated by PROTOCORE_ENABLE_DNS_SERVER.
14 *
15 * @ref DnsServerNs::build_response is pure: it reads a query message and writes a response
16 * message (RFC 1035 sec 4.1), asking a resolver callback for each QNAME, so the wire format is
17 * host-tested with no network stack under it. @ref DnsServerNs::begin binds UDP port 53
18 * (RFC 1035 sec 4.2.1) through the transport UDP listener and serves the built-in table
19 * (@ref DnsServerNs::lookup). It is a general resolver, distinct from the provisioning
20 * captive-portal DNS, which answers every name with the access point's own address; do not
21 * enable both.
22 *
23 * @author Douglas Quigg (dstroy0)
24 * @date 2026
25 */
26
27#ifndef PROTOCORE_DNS_SERVER_H
28#define PROTOCORE_DNS_SERVER_H
29
30#include "protocore_config.h" // the entry point: protocore_types.h for the widths
31
32#if PROTOCORE_ENABLE_DNS_SERVER
33
35
36/**
37 * @brief Resolve a QNAME to an IPv4 ADDRESS (RFC 1035 sec 3.4.1).
38 * @param name the queried name, upper or lower case as sent (match ignoring case, sec 2.3.3).
39 * @return the address in host byte order (0xC0A80105 == 192.168.1.5), or 0 for "not found".
40 */
41typedef uint32_t (*DnsResolveFn)(const char *name);
42
43/** @brief RFC 1035 sec 4.1: the query message that is read, and the response message written. */
44typedef struct
45{
46 const uint8_t *query; ///< the received query message, header first (RFC 1035 sec 4.1.1)
47 size_t qlen; ///< its length in octets; under 12 is shorter than the header
48 uint8_t *out; ///< where the response message is written
49 size_t out_cap; ///< how many octets that holds
50} DnsMsgArgs;
51
52/** @brief RFC 1035 sec 4.1.3: what the answer resource record carries, and where its RDATA comes from. */
53typedef struct
54{
55 uint32_t ttl; ///< the TTL the answer RR advertises, in seconds (RFC 1035 sec 4.1.3)
56 DnsResolveFn resolve; ///< what a QNAME is resolved through
57} DnsAnswerArgs;
58
59/** @brief One A record of the built-in table: an owner name and its ADDRESS (RFC 1035 sec 3.4.1). */
60typedef struct
61{
62 const char *name; ///< the owner name, matched ignoring ASCII case (RFC 1035 sec 2.3.3)
63 uint8_t a; ///< ADDRESS octet 1 (RFC 1035 sec 3.4.1)
64 uint8_t b; ///< ADDRESS octet 2
65 uint8_t c; ///< ADDRESS octet 3
66 uint8_t d; ///< ADDRESS octet 4
67} DnsRecordArgs;
68
69/** @brief The name table's own state and the calls that reach it, described only in dns_server.c. */
70
71/**
72 * @brief The answering side of DNS.
73 *
74 * RFC 1035 sec 4.1: a query message in, a response message out. A caller sets the members a call
75 * takes, invokes it through ::DnsServer, and reads the outcome off the same handle. The name
76 * table itself is behind @ref internal.
77 *
78 * @var DnsServerNs::msg the query message read and the response written (RFC 1035 sec 4.1)
79 * @var DnsServerNs::ans what the answer RR carries (RFC 1035 sec 4.1.3)
80 * @var DnsServerNs::rec the record a table call names (RFC 1035 sec 3.4.1)
81 * @var DnsServerNs::ok a call's true/false outcome
82 * @var DnsServerNs::n octets the response builder wrote, 0 on a malformed query or
83 * a response buffer too small
84 * @var DnsServerNs::ip the ADDRESS a lookup found, host order, 0 when the name is absent
85 * @var DnsServerNs::build_response frame a response to a query, asking @c ans.resolve for the QNAME
86 * @var DnsServerNs::add record one name and its A record
87 * @var DnsServerNs::clear forget every recorded name
88 * @var DnsServerNs::begin start answering on UDP port 53 (RFC 1035 sec 4.2.1)
89 * @var DnsServerNs::lookup the ADDRESS recorded for a name, or 0
90 */
91typedef struct
92{
93 DnsMsgArgs msg; ///< the message pair a response is built from (RFC 1035 sec 4.1)
94 DnsAnswerArgs ans; ///< what the answer RR carries (RFC 1035 sec 4.1.3)
95 DnsRecordArgs rec; ///< the record a table call names (RFC 1035 sec 3.4.1)
96 proto_bool ok;
97 size_t n;
98 uint32_t ip;
99} DnsServerVars;
100
101/** @brief The operands and the outcome. */
102extern DnsServerVars DnsServerV;
103
104/** @brief The entries. */
105typedef struct
106{
107 void (*const build_response)(uint8_t *work);
108 void (*const add)(uint8_t *work);
109 void (*const clear)(uint8_t *work);
110 void (*const begin)(uint8_t *work);
111 void (*const lookup)(uint8_t *work);
112} DnsServerNs;
113
114// What the table binds, defined once in the .c and taking one parameter each: everything
115// else an entry needs is an operand in DnsServerV or a region of the borrow at a fixed offset.
116void protocore_dns_server_build_response(uint8_t *work);
117void protocore_dns_server_add(uint8_t *work);
118void protocore_dns_server_clear(uint8_t *work);
119void protocore_dns_server_begin(uint8_t *work);
120void protocore_dns_server_lookup(uint8_t *work);
121
122// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
123// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
124// `DnsServer.build_response(work)` resolves to a named function and becomes a DIRECT call. An extern table
125// leaves the call indirect and the symbol live at every level, -O2 -flto included.
126static const DnsServerNs DnsServer __attribute__((unused)) = {
127 .build_response = protocore_dns_server_build_response,
128 .add = protocore_dns_server_add,
129 .clear = protocore_dns_server_clear,
130 .begin = protocore_dns_server_begin,
131 .lookup = protocore_dns_server_lookup,
132};
133
134/**
135 * @brief The PROTOCORE_DNS_SERVER_BORROW bytes this module's state lives in.
136 *
137 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
138 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
139 * walks, so the state lasts the life of the program.
140 *
141 * @return the span.
142 */
143uint8_t *protocore_dns_server_span(void);
144
145/**
146 * @brief The built-in table as a ::DnsResolveFn, for @ref DnsServerNs::ans.resolve.
147 *
148 * Scans the same records @ref DnsServerNs::add fills and @ref DnsServerNs::lookup reads, matching
149 * ignoring ASCII case (RFC 1035 sec 2.3.3), and touches no member of ::DnsServer, so a build in
150 * progress keeps the members it was given. @ref DnsServerNs::begin resolves through this.
151 *
152 * @return the ADDRESS in host byte order, or 0 when the name is absent.
153 */
154uint32_t protocore_dns_server_resolve(const char *name);
155
157
158#endif // PROTOCORE_ENABLE_DNS_SERVER
159
160#endif // PROTOCORE_DNS_SERVER_H
#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