Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_dns.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 *
17 * SPDX-License-Identifier: Apache-2.0
18 */
19
27#include "nilorea/n_common.h"
28#include "nilorea/n_dns.h"
29#include "nilorea/n_log.h"
30
31#include <stdio.h>
32#include <string.h>
33
34static int failures = 0;
35
36/* Parse the -V LOG_LEVEL verbosity argument. */
37void process_args(int argc, char** argv) {
38 int opt = 0;
39 while ((opt = getopt(argc, argv, "hvV:")) != EOF) {
40 switch (opt) {
41 case 'V':
42 if (!strncmp("LOG_NULL", optarg, 8))
44 else if (!strncmp("LOG_NOTICE", optarg, 10))
46 else if (!strncmp("LOG_INFO", optarg, 8))
48 else if (!strncmp("LOG_ERR", optarg, 7))
50 else if (!strncmp("LOG_DEBUG", optarg, 9))
52 else {
53 fprintf(stderr, "Unknown log level %s\n", optarg);
54 exit(1);
55 }
56 break;
57 case 'v':
58 fprintf(stderr, "ex_dns\n");
59 exit(1);
60 case 'h':
61 default:
62 fprintf(stderr, "usage: %s [-V LOG_LEVEL]\n", argv[0]);
63 break;
64 }
65 }
66}
67
68/* Build a minimal DNS query (header + one question) for @p name / @p qtype. */
69static size_t build_query(const char* name, uint16_t qtype, uint16_t id, unsigned char* out) {
70 size_t pos = 0;
71 const char* p = name;
72 out[pos++] = (unsigned char)(id >> 8);
73 out[pos++] = (unsigned char)(id & 0xFF);
74 out[pos++] = 0x01;
75 out[pos++] = 0x00; /* flags: RD set */
76 out[pos++] = 0x00;
77 out[pos++] = 0x01; /* QDCOUNT = 1 */
78 out[pos++] = 0x00;
79 out[pos++] = 0x00; /* ANCOUNT */
80 out[pos++] = 0x00;
81 out[pos++] = 0x00; /* NSCOUNT */
82 out[pos++] = 0x00;
83 out[pos++] = 0x00; /* ARCOUNT */
84 while (*p) {
85 const char* dot = strchr(p, '.');
86 size_t llen = dot ? (size_t)(dot - p) : strlen(p);
87 out[pos++] = (unsigned char)llen;
88 memcpy(out + pos, p, llen);
89 pos += llen;
90 if (!dot)
91 break;
92 p = dot + 1;
93 }
94 out[pos++] = 0x00; /* root label */
95 out[pos++] = (unsigned char)(qtype >> 8);
96 out[pos++] = (unsigned char)(qtype & 0xFF);
97 out[pos++] = 0x00;
98 out[pos++] = 0x01; /* QCLASS = IN */
99 return pos;
100}
101
102static void test_parse(void) {
103 unsigned char q[512];
104 size_t qlen;
105 uint16_t id = 0;
106 uint16_t qtype = 0;
107 char name[N_DNS_QNAME_MAX];
108
109 qlen = build_query("abc123.oast.example.com", N_DNS_TYPE_A, 0x1234, q);
110 if (n_dns_parse_query(q, qlen, &id, name, sizeof(name), &qtype) != 0) {
111 n_log(LOG_ERR, "parse of a valid query failed");
112 failures++;
113 return;
114 }
115 if (id != 0x1234) {
116 n_log(LOG_ERR, "parse id: got %u expected 0x1234", (unsigned)id);
117 failures++;
118 }
119 if (strcmp(name, "abc123.oast.example.com") != 0) {
120 n_log(LOG_ERR, "parse qname: got '%s'", name);
121 failures++;
122 }
123 if (qtype != N_DNS_TYPE_A) {
124 n_log(LOG_ERR, "parse qtype: got %u", (unsigned)qtype);
125 failures++;
126 }
127
128 /* names are lowercased for case-insensitive matching */
129 qlen = build_query("Token.OAST.Test", N_DNS_TYPE_A, 1, q);
130 if (n_dns_parse_query(q, qlen, NULL, name, sizeof(name), NULL) != 0 || strcmp(name, "token.oast.test") != 0) {
131 n_log(LOG_ERR, "parse lowercasing failed: '%s'", name);
132 failures++;
133 }
134}
135
136static void test_build_response(void) {
137 unsigned char q[512];
138 unsigned char resp[512];
139 size_t qlen;
140 int rlen;
141 unsigned char ip[4] = {127, 0, 0, 1};
142 uint32_t ipv4_be;
143 memcpy(&ipv4_be, ip, 4);
144
145 qlen = build_query("tok.oast.test", N_DNS_TYPE_A, 0xBEEF, q);
146 rlen = n_dns_build_a_response(q, qlen, ipv4_be, 60, resp, sizeof(resp));
147 if (rlen <= 0) {
148 n_log(LOG_ERR, "build response failed (%d)", rlen);
149 failures++;
150 return;
151 }
152 /* response length = question bytes + 16-byte A answer */
153 if ((size_t)rlen != qlen + 16) {
154 n_log(LOG_ERR, "response length: got %d expected %zu", rlen, qlen + 16);
155 failures++;
156 }
157 /* QR bit set, id echoed, ANCOUNT = 1 */
158 if ((resp[2] & 0x80) == 0) {
159 n_log(LOG_ERR, "response QR bit not set");
160 failures++;
161 }
162 if (resp[0] != 0xBE || resp[1] != 0xEF) {
163 n_log(LOG_ERR, "response id not echoed");
164 failures++;
165 }
166 if (resp[6] != 0x00 || resp[7] != 0x01) {
167 n_log(LOG_ERR, "response ANCOUNT != 1");
168 failures++;
169 }
170 /* the answer starts at qlen: a compression pointer to the question */
171 if (resp[qlen] != 0xC0 || resp[qlen + 1] != 0x0C) {
172 n_log(LOG_ERR, "answer name is not a pointer to the question");
173 failures++;
174 }
175 /* the trailing 4 RDATA bytes are the IP in order */
176 if (resp[rlen - 4] != 127 || resp[rlen - 3] != 0 || resp[rlen - 2] != 0 || resp[rlen - 1] != 1) {
177 n_log(LOG_ERR, "answer RDATA is not 127.0.0.1");
178 failures++;
179 }
180
181 /* the built response parses back to the same question */
182 {
183 char name[N_DNS_QNAME_MAX];
184 if (n_dns_parse_query(resp, (size_t)rlen, NULL, name, sizeof(name), NULL) != 0 || strcmp(name, "tok.oast.test") != 0) {
185 n_log(LOG_ERR, "response question does not round-trip: '%s'", name);
186 failures++;
187 }
188 }
189}
190
191static void test_errors(void) {
192 unsigned char q[512];
193 unsigned char resp[512];
194 size_t qlen;
195 unsigned char ip[4] = {10, 0, 0, 1};
196 uint32_t ipv4_be;
197 char name[N_DNS_QNAME_MAX];
198 memcpy(&ipv4_be, ip, 4);
199
200 /* NULL / too-short messages are rejected */
201 if (n_dns_parse_query(NULL, 12, NULL, name, sizeof(name), NULL) != -1) {
202 n_log(LOG_ERR, "parse NULL should fail");
203 failures++;
204 }
205 if (n_dns_parse_query(q, 5, NULL, name, sizeof(name), NULL) != -1) {
206 n_log(LOG_ERR, "parse too-short should fail");
207 failures++;
208 }
209
210 /* a compression pointer in the question is rejected */
211 qlen = build_query("x.test", N_DNS_TYPE_A, 1, q);
212 q[12] = 0xC0; /* corrupt the first label length into a pointer */
213 if (n_dns_parse_query(q, qlen, NULL, name, sizeof(name), NULL) != -1) {
214 n_log(LOG_ERR, "parse with compression should fail");
215 failures++;
216 }
217
218 /* a too-small output buffer is rejected */
219 qlen = build_query("x.test", N_DNS_TYPE_A, 1, q);
220 if (n_dns_build_a_response(q, qlen, ipv4_be, 60, resp, 8) != -1) {
221 n_log(LOG_ERR, "build with tiny buffer should fail");
222 failures++;
223 }
224}
225
226int main(int argc, char** argv) {
228 process_args(argc, argv);
229
230 test_parse();
232 test_errors();
233
234 if (failures) {
235 n_log(LOG_ERR, "ex_dns: %d failure(s)", failures);
236 return 1;
237 }
238 n_log(LOG_NOTICE, "ex_dns: all checks passed");
239 return 0;
240}
static int failures
int main(void)
void process_args(int argc, char **argv)
Definition ex_common.c:48
static void test_errors(void)
Definition ex_digest.c:274
static size_t build_query(const char *name, uint16_t qtype, uint16_t id, unsigned char *out)
Definition ex_dns.c:69
static void test_build_response(void)
Definition ex_dns.c:136
static void test_parse(void)
Definition ex_dns.c:102
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:89
#define LOG_DEBUG
debug-level messages
Definition n_log.h:84
#define LOG_ERR
error conditions
Definition n_log.h:76
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL )
Definition n_log.c:121
#define LOG_NOTICE
normal but significant condition
Definition n_log.h:80
#define LOG_NULL
no log output
Definition n_log.h:46
#define LOG_INFO
informational
Definition n_log.h:82
#define N_DNS_TYPE_A
DNS resource-record type A (IPv4 address).
Definition n_dns.h:45
int n_dns_build_a_response(const unsigned char *query, size_t qlen, uint32_t ipv4_be, uint32_t ttl, unsigned char *out, size_t outsz)
Build a DNS response answering a query's first question with one A record.
Definition n_dns.c:94
#define N_DNS_QNAME_MAX
Maximum decoded QNAME length, including the terminating NUL.
Definition n_dns.h:43
int n_dns_parse_query(const unsigned char *msg, size_t len, uint16_t *id_out, char *qname_out, size_t qname_sz, uint16_t *qtype_out)
Parse the header id and the first question (QNAME + QTYPE) of a DNS message.
Definition n_dns.c:39
Common headers and low-level functions & define.
Minimal DNS message helpers: parse a query question and build an A-record response.
Generic log system.