Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_dns.c

n_dns regression: parse a DNS query question and build an A-record response.

n_dns regression: parse a DNS query question and build an A-record response.

Author
Castagnier Mickael
Version
1.0
/*
* Nilorea Library
* Copyright (C) 2005-2026 Castagnier Mickael
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "nilorea/n_dns.h"
#include "nilorea/n_log.h"
#include <stdio.h>
#include <string.h>
static int failures = 0;
/* Parse the -V LOG_LEVEL verbosity argument. */
void process_args(int argc, char** argv) {
int opt = 0;
while ((opt = getopt(argc, argv, "hvV:")) != EOF) {
switch (opt) {
case 'V':
if (!strncmp("LOG_NULL", optarg, 8))
else if (!strncmp("LOG_NOTICE", optarg, 10))
else if (!strncmp("LOG_INFO", optarg, 8))
else if (!strncmp("LOG_ERR", optarg, 7))
else if (!strncmp("LOG_DEBUG", optarg, 9))
else {
fprintf(stderr, "Unknown log level %s\n", optarg);
exit(1);
}
break;
case 'v':
fprintf(stderr, "ex_dns\n");
exit(1);
case 'h':
default:
fprintf(stderr, "usage: %s [-V LOG_LEVEL]\n", argv[0]);
break;
}
}
}
/* Build a minimal DNS query (header + one question) for @p name / @p qtype. */
static size_t build_query(const char* name, uint16_t qtype, uint16_t id, unsigned char* out) {
size_t pos = 0;
const char* p = name;
out[pos++] = (unsigned char)(id >> 8);
out[pos++] = (unsigned char)(id & 0xFF);
out[pos++] = 0x01;
out[pos++] = 0x00; /* flags: RD set */
out[pos++] = 0x00;
out[pos++] = 0x01; /* QDCOUNT = 1 */
out[pos++] = 0x00;
out[pos++] = 0x00; /* ANCOUNT */
out[pos++] = 0x00;
out[pos++] = 0x00; /* NSCOUNT */
out[pos++] = 0x00;
out[pos++] = 0x00; /* ARCOUNT */
while (*p) {
const char* dot = strchr(p, '.');
size_t llen = dot ? (size_t)(dot - p) : strlen(p);
out[pos++] = (unsigned char)llen;
memcpy(out + pos, p, llen);
pos += llen;
if (!dot)
break;
p = dot + 1;
}
out[pos++] = 0x00; /* root label */
out[pos++] = (unsigned char)(qtype >> 8);
out[pos++] = (unsigned char)(qtype & 0xFF);
out[pos++] = 0x00;
out[pos++] = 0x01; /* QCLASS = IN */
return pos;
}
static void test_parse(void) {
unsigned char q[512];
size_t qlen;
uint16_t id = 0;
uint16_t qtype = 0;
char name[N_DNS_QNAME_MAX];
qlen = build_query("abc123.oast.example.com", N_DNS_TYPE_A, 0x1234, q);
if (n_dns_parse_query(q, qlen, &id, name, sizeof(name), &qtype) != 0) {
n_log(LOG_ERR, "parse of a valid query failed");
return;
}
if (id != 0x1234) {
n_log(LOG_ERR, "parse id: got %u expected 0x1234", (unsigned)id);
}
if (strcmp(name, "abc123.oast.example.com") != 0) {
n_log(LOG_ERR, "parse qname: got '%s'", name);
}
if (qtype != N_DNS_TYPE_A) {
n_log(LOG_ERR, "parse qtype: got %u", (unsigned)qtype);
}
/* names are lowercased for case-insensitive matching */
qlen = build_query("Token.OAST.Test", N_DNS_TYPE_A, 1, q);
if (n_dns_parse_query(q, qlen, NULL, name, sizeof(name), NULL) != 0 || strcmp(name, "token.oast.test") != 0) {
n_log(LOG_ERR, "parse lowercasing failed: '%s'", name);
}
}
static void test_build_response(void) {
unsigned char q[512];
unsigned char resp[512];
size_t qlen;
int rlen;
unsigned char ip[4] = {127, 0, 0, 1};
uint32_t ipv4_be;
memcpy(&ipv4_be, ip, 4);
qlen = build_query("tok.oast.test", N_DNS_TYPE_A, 0xBEEF, q);
rlen = n_dns_build_a_response(q, qlen, ipv4_be, 60, resp, sizeof(resp));
if (rlen <= 0) {
n_log(LOG_ERR, "build response failed (%d)", rlen);
return;
}
/* response length = question bytes + 16-byte A answer */
if ((size_t)rlen != qlen + 16) {
n_log(LOG_ERR, "response length: got %d expected %zu", rlen, qlen + 16);
}
/* QR bit set, id echoed, ANCOUNT = 1 */
if ((resp[2] & 0x80) == 0) {
n_log(LOG_ERR, "response QR bit not set");
}
if (resp[0] != 0xBE || resp[1] != 0xEF) {
n_log(LOG_ERR, "response id not echoed");
}
if (resp[6] != 0x00 || resp[7] != 0x01) {
n_log(LOG_ERR, "response ANCOUNT != 1");
}
/* the answer starts at qlen: a compression pointer to the question */
if (resp[qlen] != 0xC0 || resp[qlen + 1] != 0x0C) {
n_log(LOG_ERR, "answer name is not a pointer to the question");
}
/* the trailing 4 RDATA bytes are the IP in order */
if (resp[rlen - 4] != 127 || resp[rlen - 3] != 0 || resp[rlen - 2] != 0 || resp[rlen - 1] != 1) {
n_log(LOG_ERR, "answer RDATA is not 127.0.0.1");
}
/* the built response parses back to the same question */
{
char name[N_DNS_QNAME_MAX];
if (n_dns_parse_query(resp, (size_t)rlen, NULL, name, sizeof(name), NULL) != 0 || strcmp(name, "tok.oast.test") != 0) {
n_log(LOG_ERR, "response question does not round-trip: '%s'", name);
}
}
}
static void test_errors(void) {
unsigned char q[512];
unsigned char resp[512];
size_t qlen;
unsigned char ip[4] = {10, 0, 0, 1};
uint32_t ipv4_be;
char name[N_DNS_QNAME_MAX];
memcpy(&ipv4_be, ip, 4);
/* NULL / too-short messages are rejected */
if (n_dns_parse_query(NULL, 12, NULL, name, sizeof(name), NULL) != -1) {
n_log(LOG_ERR, "parse NULL should fail");
}
if (n_dns_parse_query(q, 5, NULL, name, sizeof(name), NULL) != -1) {
n_log(LOG_ERR, "parse too-short should fail");
}
/* a compression pointer in the question is rejected */
qlen = build_query("x.test", N_DNS_TYPE_A, 1, q);
q[12] = 0xC0; /* corrupt the first label length into a pointer */
if (n_dns_parse_query(q, qlen, NULL, name, sizeof(name), NULL) != -1) {
n_log(LOG_ERR, "parse with compression should fail");
}
/* a too-small output buffer is rejected */
qlen = build_query("x.test", N_DNS_TYPE_A, 1, q);
if (n_dns_build_a_response(q, qlen, ipv4_be, 60, resp, 8) != -1) {
n_log(LOG_ERR, "build with tiny buffer should fail");
}
}
int main(int argc, char** argv) {
process_args(argc, argv);
if (failures) {
n_log(LOG_ERR, "ex_dns: %d failure(s)", failures);
return 1;
}
n_log(LOG_NOTICE, "ex_dns: all checks passed");
return 0;
}
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.