fix: populate ldns submodule and add autotools to LDNS build stage

- Re-cloned zonemaster-ldns with --recurse-submodules so the bundled
  ldns C library source (including Changelog and configure.ac) is present
- Added autoconf, automake, libtool to Dockerfile.backend ldns-build stage
  so libtoolize + autoreconf can generate ldns/configure during make

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-21 08:33:38 +02:00
parent 8d4eaa1489
commit eaaa8f6a11
541 changed files with 138189 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
# Standard installation pathnames
# See the file LICENSE for the license
SHELL = @SHELL@
VERSION = @PACKAGE_VERSION@
basesrcdir = $(shell basename `pwd`)
srcdir = @srcdir@
prefix = @prefix@
exec_prefix = @exec_prefix@
bindir = @bindir@
mandir = @mandir@
CC = @CC@
CFLAGS = @CFLAGS@ -Wall -I.
CPPFLAGS = @CPPFLAGS@
LDFLAGS = @LDFLAGS@
LIBS = @LIBS@
LDNSDIR = @LDNSDIR@
COMPILE = $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $(LIBS)
LINT = splint
LINTFLAGS = +quiet -weak -warnposix -unrecog -Din_addr_t=uint32_t -Du_int=unsigned -Du_char=uint8_t -preproc
HEADER = config.h
SOURCES = ldns-testns.c nsd-ldnsd.c
PROGRAMS=$(SOURCES:.c=)
.PHONY: all clean realclean
all: $(PROGRAMS)
all-static: $(PROGRAMS:=-stc)
%: $(srcdir)/%.c
$(COMPILE) -o $@ $(srcdir)/$@.c
%-stc:
@# can't mix implicit and static rules
@if [ $(srcdir)/$(@:-stc=).c -nt $(@:-stc=) ] ; then \
echo "$(CC) $(CPPFLAGS) $(CFLAGS) -lpcap -lcrypto -o $(@:-stc=) $(srcdir)/$(@:-stc=).c $(LDNSDIR)/lib/libldns.a" ; \
$(CC) $(CPPFLAGS) $(CFLAGS) -lpcap -lcrypto -o $(@:-stc=) $(srcdir)/$(@:-stc=).c $(LDNSDIR)/lib/libldns.a ; \
fi ;
lint:
for i in $(SOURCES); do \
$(LINT) $(LINTFLAGS) -I. -I$(srcdir) $(srcdir)/$$i $(CPPFLAGS); \
if [ $$? -ne 0 ] ; then exit 1 ; fi ; \
done
clean:
rm -f *.o
rm -f $(PROGRAMS)
realclean: clean
rm -rf autom4te.cache/
rm -f config.log config.status aclocal.m4 config.h.in configure Makefile
rm -f config.h
confclean: clean
rm -rf config.log config.status config.h Makefile

View File

@@ -0,0 +1,181 @@
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.57)
AC_INIT(ldns, 1.7.0, dns-team@nlnetlabs.nl,libdns)
AC_CONFIG_SRCDIR([nsd-ldnsd.c])
OURCPPFLAGS='-D_POSIX_C_SOURCE=200112 -D_XOPEN_SOURCE=600 -D__EXTENSIONS__ -D__BSD_VISIBLE'
CPPFLAGS=${CPPFLAGS:-${OURCPPFLAGS}}
OURCFLAGS='-g'
CFLAGS=${CFLAGS:-${OURCFLAGS}}
AC_AIX
# Checks for programs.
AC_PROG_CC
AC_PROG_MAKE_SET
dnl routine to help check for compiler flags.
AC_DEFUN([CHECK_COMPILER_FLAG],
[
AC_REQUIRE([AC_PROG_CC])
AC_MSG_CHECKING(whether $CC supports -$1)
cache=`echo $1 | sed 'y%.=/+-%___p_%'`
AC_CACHE_VAL(cv_prog_cc_flag_$cache,
[
echo 'void f(){}' >conftest.c
if test -z "`$CC -$1 -c conftest.c 2>&1`"; then
eval "cv_prog_cc_flag_$cache=yes"
else
eval "cv_prog_cc_flag_$cache=no"
fi
rm -f conftest*
])
if eval "test \"`echo '$cv_prog_cc_flag_'$cache`\" = yes"; then
AC_MSG_RESULT(yes)
:
$2
else
AC_MSG_RESULT(no)
:
$3
fi
])
CHECK_COMPILER_FLAG(O2, [CFLAGS="$CFLAGS -O2"])
CHECK_COMPILER_FLAG(std=c99, [CFLAGS="$CFLAGS -std=c99"], [CFLAGS="$CFLAGS -ansi"])
AC_CHECK_HEADERS([sys/types.h getopt.h stdlib.h stdio.h assert.h netinet/in.h ctype.h time.h pcap.h arpa/inet.h sys/time.h sys/socket.h],,, [AC_INCLUDES_DEFAULT])
AC_CHECK_HEADERS([netinet/in_systm.h net/if.h netinet/ip.h netinet/udp.h netinet/if_ether.h],,, [
AC_INCLUDES_DEFAULT
#ifdef HAVE_NETINET_IN_SYSTM_H
#include <netinet/in_systm.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_NET_IF_H
#include <net/if.h>
#endif])
AC_CHECK_HEADERS([sys/param.h sys/mount.h],,,
[AC_INCLUDES_DEFAULT]
[
[
#if HAVE_SYS_PARAM_H
# include <sys/param.h>
#endif
]
])
AC_CHECK_LIB(socket, socket)
AC_CHECK_LIB(nsl, inet_ntop)
# check for ldns
AC_ARG_WITH(ldns,
AC_HELP_STRING([--with-ldns=PATH specify prefix of path of ldns library to use])
,
[
specialldnsdir="$withval"
CPPFLAGS="$CPPFLAGS -I$withval/include"
LDFLAGS="$LDFLAGS -L$withval/lib"
LDNSDIR="$withval"
]
)
# check for ldns development source tree
AC_MSG_CHECKING([for ldns devel source])
ldns_dev_dir=../..
if test -f $ldns_dev_dir/ldns/util.h && \
grep LDNS_VERSION $ldns_dev_dir/ldns/util.h >/dev/null; then
ldns_version=`grep LDNS_VERSION $ldns_dev_dir/ldns/util.h | sed -e 's/^.*"\(.*\)".*$/\1/'`
AC_MSG_RESULT([using $ldns_dev_dir with $ldns_version])
CPPFLAGS="$CPPFLAGS -I$ldns_dev_dir/include"
LDFLAGS="$LDFLAGS -L$ldns_dev_dir/lib"
LIBS="$LIBS -lldns"
AC_DEFINE(HAVE_LIBLDNS, 1, [If the ldns library is available.])
LDNSDIR="$ldns_dev_dir"
else
AC_MSG_RESULT([no])
AC_CHECK_LIB(ldns, ldns_rr_new,, [
AC_MSG_ERROR([Can't find ldns library])
]
)
fi
AC_SUBST(LDNSDIR)
AC_CHECK_HEADER(ldns/ldns.h,, [
AC_MSG_ERROR([Can't find ldns headers])
], [AC_INCLUDES_DEFAULT]
)
AC_CHECK_LIB(pcap, pcap_open_offline,, [
AC_MSG_WARN([Can't find pcap library (needed for ldns-dpa, will not build dpa now.)])
]
)
AC_CHECK_FUNCS(isblank)
AH_BOTTOM([
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#if STDC_HEADERS
#include <stdlib.h>
#include <stddef.h>
#endif
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_NETINET_UDP_H
#include <netinet/udp.h>
#endif
#ifdef HAVE_TIME_H
#include <time.h>
#endif
#ifdef HAVE_PCAP_H
#include <pcap.h>
#endif
#ifdef HAVE_NETINET_IN_SYSTM_H
#include <netinet/in_systm.h>
#endif
#ifdef HAVE_NETINET_IP_H
#include <netinet/ip.h>
#endif
#ifdef HAVE_NET_IF_H
#include <net/if.h>
#endif
#ifdef HAVE_NETINET_IF_ETHER_H
#include <netinet/if_ether.h>
#endif
])
AC_CONFIG_FILES([Makefile])
AC_CONFIG_HEADER([config.h])
AC_OUTPUT

View File

@@ -0,0 +1,806 @@
/*
* ldns-testns. Light-weight DNS daemon, gives canned replies.
*
* Tiny dns server, that responds with specially crafted replies
* to requests. For testing dns software.
*
* (c) NLnet Labs, 2005, 2006
* See the file LICENSE for the license
*/
/*
* This program is a debugging aid. It can is not efficient, especially
* with a long config file, but it can give any reply to any query.
* This can help the developer pre-script replies for queries.
*
* It listens to IP4 UDP and TCP by default.
* You can specify a packet RR by RR with header flags to return.
*
* Missing features:
* - hexdump support, for 'formerr' packets.
* - cannot mess up the header at present.
* - matching content different from reply content.
*/
/*
The data file format is as follows:
; comment.
; a number of entries, these are processed first to last.
; a line based format.
$ORIGIN origin
$TTL default_ttl
ENTRY_BEGIN
; first give MATCH lines, that say what queries are matched
; by this entry.
; 'opcode' makes the query match the opcode from the reply
; if you leave it out, any opcode matches this entry.
; 'qtype' makes the query match the qtype from the reply
; 'qname' makes the query match the qname from the reply
; 'serial=1023' makes the query match if ixfr serial is 1023.
MATCH [opcode] [qtype] [qname] [serial=<value>]
MATCH [UDP|TCP]
MATCH ...
; Then the REPLY header is specified.
REPLY opcode, rcode or flags.
(opcode) QUERY IQUERY STATUS NOTIFY UPDATE
(rcode) NOERROR FORMERR SERVFAIL NXDOMAIN NOTIMPL YXDOMAIN
YXRRSET NXRRSET NOTAUTH NOTZONE
(flags) QR AA TC RD CD RA AD
REPLY ...
; any additional actions to do.
; 'copy_id' copies the ID from the query to the answer.
ADJUST copy_id
; 'sleep=10' sleeps for 10 seconds before giving the answer (TCP is open)
ADJUST [sleep=<num>] ; sleep before giving any reply
ADJUST [packet_sleep=<num>] ; sleep before this packet in sequence
SECTION QUESTION
<RRs, one per line> ; the RRcount is determined automatically.
SECTION ANSWER
<RRs, one per line>
SECTION AUTHORITY
<RRs, one per line>
SECTION ADDITIONAL
<RRs, one per line>
EXTRA_PACKET ; follow with SECTION, REPLY for more packets.
ENTRY_END
*/
#include "config.h"
#include <ldns/ldns.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <netinet/igmp.h>
#include <errno.h>
#define INBUF_SIZE 4096 /* max size for incoming queries */
#define MAX_LINE 10240 /* max line length */
#define DEFAULT_PORT 53 /* default if no -p port is specified */
#define CONN_BACKLOG 5 /* 5 connections queued up for tcp */
static const char* prog_name = "ldns-testns";
static FILE* logfile = 0;
static int verbose = 0;
enum transport_type {transport_any = 0, transport_udp, transport_tcp };
/* struct to keep a linked list of reply packets for a query */
struct reply_packet {
struct reply_packet* next;
ldns_pkt* reply;
int packet_sleep; /* seconds to sleep before giving packet */
};
/* data structure to keep the canned queries in */
/* format is the 'matching query' and the 'canned answer' */
struct entry {
/* match */
/* How to match an incoming query with this canned reply */
bool match_opcode; /* match query opcode with answer opcode */
bool match_qtype; /* match qtype with answer qtype */
bool match_qname; /* match qname with answer qname */
bool match_serial; /* match SOA serial number, from auth section */
uint32_t ixfr_soa_serial; /* match query serial with this value. */
enum transport_type match_transport; /* match on UDP/TCP */
/* pre canned reply */
struct reply_packet *reply_list;
/* how to adjust the reply packet */
bool copy_id; /* copy over the ID from the query into the answer */
int sleeptime; /* in seconds */
/* next in list */
struct entry* next;
};
static void usage()
{
printf("Usage: %s [options] <datafile>\n", prog_name);
printf(" -p listens on the specified port, default %d.\n", DEFAULT_PORT);
printf(" -v more verbose, prints queries, answers and matching.\n");
printf("The program answers queries with canned replies from the datafile.\n");
exit(EXIT_FAILURE);
}
static void log_msg(const char* msg, ...)
{
va_list args;
va_start(args, msg);
vfprintf(logfile, msg, args);
fflush(logfile);
va_end(args);
}
static void error(const char* msg, ...)
{
va_list args;
va_start(args, msg);
fprintf(logfile, "%s error: ", prog_name);
vfprintf(logfile, msg, args);
fprintf(logfile, "\n");
fflush(stdout);
va_end(args);
exit(EXIT_FAILURE);
}
static int bind_port(int sock, int port)
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = (in_port_t)htons((uint16_t)port);
addr.sin_addr.s_addr = INADDR_ANY;
return bind(sock, (struct sockaddr *)&addr, (socklen_t) sizeof(addr));
}
static bool isendline(char c)
{
if(c == ';' || c == '#'
|| c == '\n' || c == 0)
return true;
return false;
}
/* true if the string starts with the keyword given. Moves the str ahead. */
static bool str_keyword(const char** str, const char* keyword)
{
size_t len = strlen(keyword);
assert(str && keyword);
if(strncmp(*str, keyword, len) != 0)
return false;
*str += len;
while(isspace((unsigned char)**str))
(*str)++;
return true;
}
static struct reply_packet*
entry_add_reply(struct entry* entry)
{
struct reply_packet* pkt = (struct reply_packet*)malloc(
sizeof(struct reply_packet));
struct reply_packet ** p = &entry->reply_list;
pkt->next = NULL;
pkt->packet_sleep = 0;
pkt->reply = ldns_pkt_new();
/* link at end */
while(*p)
p = &((*p)->next);
*p = pkt;
return pkt;
}
static void matchline(const char* line, struct entry* e)
{
const char* parse = line;
while(*parse) {
if(isendline(*parse))
return;
if(str_keyword(&parse, "opcode")) {
e->match_opcode = true;
} else if(str_keyword(&parse, "qtype")) {
e->match_qtype = true;
} else if(str_keyword(&parse, "qname")) {
e->match_qname = true;
} else if(str_keyword(&parse, "UDP")) {
e->match_transport = transport_udp;
} else if(str_keyword(&parse, "TCP")) {
e->match_transport = transport_tcp;
} else if(str_keyword(&parse, "serial")) {
e->match_serial = true;
if(*parse != '=' && *parse != ':')
error("expected = or : in MATCH: %s", line);
parse++;
e->ixfr_soa_serial = (uint32_t)strtol(parse, (char**)&parse, 10);
while(isspace((unsigned char)*parse))
parse++;
} else {
error("could not parse MATCH: '%s'", parse);
}
}
}
static void replyline(const char* line, ldns_pkt *reply)
{
const char* parse = line;
while(*parse) {
if(isendline(*parse))
return;
/* opcodes */
if(str_keyword(&parse, "QUERY")) {
ldns_pkt_set_opcode(reply, LDNS_PACKET_QUERY);
} else if(str_keyword(&parse, "IQUERY")) {
ldns_pkt_set_opcode(reply, LDNS_PACKET_IQUERY);
} else if(str_keyword(&parse, "STATUS")) {
ldns_pkt_set_opcode(reply, LDNS_PACKET_STATUS);
} else if(str_keyword(&parse, "NOTIFY")) {
ldns_pkt_set_opcode(reply, LDNS_PACKET_NOTIFY);
} else if(str_keyword(&parse, "UPDATE")) {
ldns_pkt_set_opcode(reply, LDNS_PACKET_UPDATE);
/* rcodes */
} else if(str_keyword(&parse, "NOERROR")) {
ldns_pkt_set_rcode(reply, LDNS_RCODE_NOERROR);
} else if(str_keyword(&parse, "FORMERR")) {
ldns_pkt_set_rcode(reply, LDNS_RCODE_FORMERR);
} else if(str_keyword(&parse, "SERVFAIL")) {
ldns_pkt_set_rcode(reply, LDNS_RCODE_SERVFAIL);
} else if(str_keyword(&parse, "NXDOMAIN")) {
ldns_pkt_set_rcode(reply, LDNS_RCODE_NXDOMAIN);
} else if(str_keyword(&parse, "NOTIMPL")) {
ldns_pkt_set_rcode(reply, LDNS_RCODE_NOTIMPL);
} else if(str_keyword(&parse, "YXDOMAIN")) {
ldns_pkt_set_rcode(reply, LDNS_RCODE_YXDOMAIN);
} else if(str_keyword(&parse, "YXRRSET")) {
ldns_pkt_set_rcode(reply, LDNS_RCODE_YXRRSET);
} else if(str_keyword(&parse, "NXRRSET")) {
ldns_pkt_set_rcode(reply, LDNS_RCODE_NXRRSET);
} else if(str_keyword(&parse, "NOTAUTH")) {
ldns_pkt_set_rcode(reply, LDNS_RCODE_NOTAUTH);
} else if(str_keyword(&parse, "NOTZONE")) {
ldns_pkt_set_rcode(reply, LDNS_RCODE_NOTZONE);
/* flags */
} else if(str_keyword(&parse, "QR")) {
ldns_pkt_set_qr(reply, true);
} else if(str_keyword(&parse, "AA")) {
ldns_pkt_set_aa(reply, true);
} else if(str_keyword(&parse, "TC")) {
ldns_pkt_set_tc(reply, true);
} else if(str_keyword(&parse, "RD")) {
ldns_pkt_set_rd(reply, true);
} else if(str_keyword(&parse, "CD")) {
ldns_pkt_set_cd(reply, true);
} else if(str_keyword(&parse, "RA")) {
ldns_pkt_set_ra(reply, true);
} else if(str_keyword(&parse, "AD")) {
ldns_pkt_set_ad(reply, true);
} else {
error("could not parse REPLY: '%s'", parse);
}
}
}
static void adjustline(const char* line, struct entry* e,
struct reply_packet* pkt)
{
const char* parse = line;
while(*parse) {
if(isendline(*parse))
return;
if(str_keyword(&parse, "copy_id")) {
e->copy_id = true;
} else if(str_keyword(&parse, "sleep=")) {
e->sleeptime = strtol(parse, (char**)&parse, 10);
while(isspace((unsigned char)*parse))
parse++;
} else if(str_keyword(&parse, "packet_sleep=")) {
pkt->packet_sleep = strtol(parse, (char**)&parse, 10);
while(isspace((unsigned char)*parse))
parse++;
} else {
error("could not parse ADJUST: '%s'", parse);
}
}
}
static struct entry* new_entry()
{
struct entry* e = LDNS_MALLOC(struct entry);
memset(e, 0, sizeof(e));
e->match_opcode = false;
e->match_qtype = false;
e->match_qname = false;
e->match_serial = false;
e->ixfr_soa_serial = 0;
e->match_transport = transport_any;
e->reply_list = NULL;
e->copy_id = false;
e->sleeptime = 0;
e->next = NULL;
return e;
}
static void get_origin(const char* name, int lineno, ldns_rdf** origin, char* parse)
{
/* snip off rest of the text so as to make the parse work in ldns */
char* end;
char store;
ldns_status status;
ldns_rdf_free(*origin);
*origin = NULL;
end=parse;
while(!isspace((unsigned char)*end) && !isendline(*end))
end++;
store = *end;
*end = 0;
log_msg("parsing '%s'\n", parse);
status = ldns_str2rdf_dname(origin, parse);
*end = store;
if (status != LDNS_STATUS_OK)
error("%s line %d:\n\t%s: %s", name, lineno,
ldns_get_errorstr_by_id(status), parse);
}
/* reads the canned reply file and returns a list of structs */
static struct entry* read_datafile(const char* name)
{
struct entry* list = NULL;
struct entry* last = NULL;
struct entry* current = NULL;
FILE *in;
int lineno = 0;
char line[MAX_LINE];
const char* parse;
ldns_pkt_section add_section = LDNS_SECTION_QUESTION;
uint16_t default_ttl = 0;
ldns_rdf* origin = NULL;
ldns_rdf* prev_rr = NULL;
int entry_num = 0;
struct reply_packet *cur_reply = NULL;
if((in=fopen(name, "r")) == NULL) {
error("could not open file %s: %s", name, strerror(errno));
}
while(fgets(line, (int)sizeof(line), in) != NULL) {
line[MAX_LINE-1] = 0;
parse = line;
lineno ++;
while(isspace((unsigned char)*parse))
parse++;
/* test for keywords */
if(isendline(*parse))
continue; /* skip comment and empty lines */
if(str_keyword(&parse, "ENTRY_BEGIN")) {
if(current) {
error("%s line %d: previous entry does not ENTRY_END",
name, lineno);
}
current = new_entry();
cur_reply = entry_add_reply(current);
if(last)
last->next = current;
else list = current;
last = current;
continue;
} else if(str_keyword(&parse, "$ORIGIN")) {
get_origin(name, lineno, &origin, (char*)parse);
continue;
} else if(str_keyword(&parse, "$TTL")) {
default_ttl = (uint16_t)atoi(parse);
continue;
}
/* working inside an entry */
if(!current) {
error("%s line %d: expected ENTRY_BEGIN but got %s",
name, lineno, line);
}
if(str_keyword(&parse, "MATCH")) {
matchline(parse, current);
} else if(str_keyword(&parse, "REPLY")) {
replyline(parse, cur_reply->reply);
} else if(str_keyword(&parse, "ADJUST")) {
adjustline(parse, current, cur_reply);
} else if(str_keyword(&parse, "EXTRA_PACKET")) {
cur_reply = entry_add_reply(current);
} else if(str_keyword(&parse, "SECTION")) {
if(str_keyword(&parse, "QUESTION"))
add_section = LDNS_SECTION_QUESTION;
else if(str_keyword(&parse, "ANSWER"))
add_section = LDNS_SECTION_ANSWER;
else if(str_keyword(&parse, "AUTHORITY"))
add_section = LDNS_SECTION_AUTHORITY;
else if(str_keyword(&parse, "ADDITIONAL"))
add_section = LDNS_SECTION_ADDITIONAL;
else error("%s line %d: bad section %s", name, lineno, parse);
} else if(str_keyword(&parse, "ENTRY_END")) {
current = 0;
entry_num ++;
} else {
/* it must be a RR, parse and add to packet. */
ldns_rr* n = NULL;
ldns_status status;
status = ldns_rr_new_frm_str(&n, parse, default_ttl, origin, &prev_rr);
if (status != LDNS_STATUS_OK)
error("%s line %d:\n\t%s: %s", name, lineno,
ldns_get_errorstr_by_id(status), parse);
ldns_pkt_push_rr(cur_reply->reply, add_section, n);
}
}
log_msg("Read %d entries\n", entry_num);
fclose(in);
return list;
}
static ldns_rr_type get_qtype(ldns_pkt* p)
{
if(!ldns_rr_list_rr(ldns_pkt_question(p), 0))
return 0;
return ldns_rr_get_type(ldns_rr_list_rr(ldns_pkt_question(p), 0));
}
static ldns_rdf* get_owner(ldns_pkt* p)
{
if(!ldns_rr_list_rr(ldns_pkt_question(p), 0))
return NULL;
return ldns_rr_owner(ldns_rr_list_rr(ldns_pkt_question(p), 0));
}
static uint32_t get_serial(ldns_pkt* p)
{
/* get authority section SOA serial value */
ldns_rr *rr = ldns_rr_list_rr(ldns_pkt_authority(p), 0);
ldns_rdf *rdf;
uint32_t val;
if(!rr) return 0;
rdf = ldns_rr_rdf(rr, 2);
if(!rdf) return 0;
val = ldns_rdf2native_int32(rdf);
if(verbose) log_msg("found serial %u in msg. ", (int)val);
return val;
}
/* finds entry in list, or returns NULL */
static struct entry* find_match(struct entry* entries, ldns_pkt* query_pkt,
enum transport_type transport)
{
struct entry* p = entries;
ldns_pkt* reply = NULL;
for(p=entries; p; p=p->next) {
if(verbose) log_msg("comparepkt: ");
reply = p->reply_list->reply;
if(p->match_opcode && ldns_pkt_get_opcode(query_pkt) !=
ldns_pkt_get_opcode(reply)) {
if(verbose) log_msg("bad opcode\n");
continue;
}
if(p->match_qtype && get_qtype(query_pkt) != get_qtype(reply)) {
if(verbose) log_msg("bad qtype\n");
continue;
}
if(p->match_qname) {
if(!get_owner(query_pkt) || !get_owner(reply) ||
ldns_dname_compare(
get_owner(query_pkt), get_owner(reply)) != 0) {
if(verbose) log_msg("bad qname\n");
continue;
}
}
if(p->match_serial && get_serial(query_pkt) != p->ixfr_soa_serial) {
if(verbose) log_msg("bad serial\n");
continue;
}
if(p->match_transport != transport_any && p->match_transport != transport) {
if(verbose) log_msg("bad transport\n");
continue;
}
if(verbose) log_msg("match!\n");
return p;
}
return NULL;
}
static void
adjust_packet(struct entry* match, ldns_pkt* answer_pkt, ldns_pkt* query_pkt)
{
/* copy & adjust packet */
if(match->copy_id)
ldns_pkt_set_id(answer_pkt, ldns_pkt_id(query_pkt));
if(match->sleeptime > 0) {
if(verbose) log_msg("sleeping for %d seconds\n", match->sleeptime);
sleep(match->sleeptime);
}
}
/*
* Parses data buffer to a query, finds the correct answer
* and calls the given function for every packet to send.
*/
static void
handle_query(uint8_t* inbuf, ssize_t inlen, struct entry* entries, int* count,
enum transport_type transport, void (*sendfunc)(uint8_t*, size_t, void*),
void* userdata)
{
ldns_status status;
ldns_pkt *query_pkt = NULL;
ldns_pkt *answer_pkt = NULL;
struct reply_packet *p;
ldns_rr *query_rr = NULL;
uint8_t *outbuf = NULL;
size_t answer_size = 0;
struct entry* entry = NULL;
status = ldns_wire2pkt(&query_pkt, inbuf, (size_t)inlen);
if (status != LDNS_STATUS_OK) {
log_msg("Got bad packet: %s\n", ldns_get_errorstr_by_id(status));
return;
}
query_rr = ldns_rr_list_rr(ldns_pkt_question(query_pkt), 0);
log_msg("query %d: id %d: %s %d bytes: ", ++(*count), (int)ldns_pkt_id(query_pkt),
(transport==transport_tcp)?"TCP":"UDP", inlen);
ldns_rr_print(logfile, query_rr);
if(verbose) ldns_pkt_print(logfile, query_pkt);
/* fill up answer packet */
entry = find_match(entries, query_pkt, transport);
if(!entry || !entry->reply_list) {
log_msg("no answer packet for this query, no reply.\n");
ldns_pkt_free(query_pkt);
return;
}
for(p = entry->reply_list; p; p = p->next)
{
if(verbose) log_msg("Answer pkt:\n");
answer_pkt = ldns_pkt_clone(p->reply);
adjust_packet(entry, answer_pkt, query_pkt);
if(verbose) ldns_pkt_print(logfile, answer_pkt);
status = ldns_pkt2wire(&outbuf, answer_pkt, &answer_size);
log_msg("Answer packet size: %u bytes.\n", (unsigned int)answer_size);
if (status != LDNS_STATUS_OK) {
log_msg("Error creating answer: %s\n", ldns_get_errorstr_by_id(status));
ldns_pkt_free(query_pkt);
return;
}
ldns_pkt_free(answer_pkt);
answer_pkt = NULL;
if(p->packet_sleep) {
if(verbose) log_msg("sleeping for next packet"
" %d secs\n", p->packet_sleep);
sleep(p->packet_sleep);
if(verbose) log_msg("wakeup for next packet "
"(slept %d secs)\n", p->packet_sleep);
}
sendfunc(outbuf, answer_size, userdata);
LDNS_FREE(outbuf);
outbuf = NULL;
answer_size = 0;
}
ldns_pkt_free(query_pkt);
}
struct handle_udp_userdata {
int udp_sock;
struct sockaddr_storage addr_him;
socklen_t hislen;
};
static void
send_udp(uint8_t* buf, size_t len, void* data)
{
struct handle_udp_userdata *userdata = (struct handle_udp_userdata*)data;
/* udp send reply */
ssize_t nb;
nb = sendto(userdata->udp_sock, buf, len, 0,
(struct sockaddr*)&userdata->addr_him, userdata->hislen);
if(nb == -1)
log_msg("sendto(): %s\n", strerror(errno));
else if((size_t)nb != len)
log_msg("sendto(): only sent %d of %d octets.\n",
(int)nb, (int)len);
}
static void
handle_udp(int udp_sock, struct entry* entries, int *count)
{
ssize_t nb;
uint8_t inbuf[INBUF_SIZE];
struct handle_udp_userdata userdata;
userdata.udp_sock = udp_sock;
userdata.hislen = (socklen_t)sizeof(userdata.addr_him);
/* udp recv */
nb = recvfrom(udp_sock, inbuf, INBUF_SIZE, 0,
(struct sockaddr*)&userdata.addr_him, &userdata.hislen);
if (nb < 1) {
log_msg("recvfrom(): %s\n", strerror(errno));
return;
}
handle_query(inbuf, nb, entries, count, transport_udp, send_udp, &userdata);
}
static void
read_n_bytes(int sock, uint8_t* buf, size_t sz)
{
size_t count = 0;
while(count < sz) {
ssize_t nb = read(sock, buf+count, sz-count);
if(nb < 0) {
log_msg("read(): %s\n", strerror(errno));
return;
}
count += nb;
}
}
static void
write_n_bytes(int sock, uint8_t* buf, size_t sz)
{
size_t count = 0;
while(count < sz) {
ssize_t nb = write(sock, buf+count, sz-count);
if(nb < 0) {
log_msg("write(): %s\n", strerror(errno));
return;
}
count += nb;
}
}
struct handle_tcp_userdata {
int s;
};
static void
send_tcp(uint8_t* buf, size_t len, void* data)
{
struct handle_tcp_userdata *userdata = (struct handle_tcp_userdata*)data;
uint16_t tcplen;
/* tcp send reply */
tcplen = htons(len);
write_n_bytes(userdata->s, (uint8_t*)&tcplen, sizeof(tcplen));
write_n_bytes(userdata->s, buf, len);
}
static void
handle_tcp(int tcp_sock, struct entry* entries, int *count)
{
int s;
struct sockaddr_storage addr_him;
socklen_t hislen;
uint8_t inbuf[INBUF_SIZE];
uint16_t tcplen;
struct handle_tcp_userdata userdata;
/* accept */
hislen = (socklen_t)sizeof(addr_him);
if((s = accept(tcp_sock, (struct sockaddr*)&addr_him, &hislen)) < 0) {
log_msg("accept(): %s\n", strerror(errno));
return;
}
userdata.s = s;
/* tcp recv */
read_n_bytes(s, (uint8_t*)&tcplen, sizeof(tcplen));
tcplen = ntohs(tcplen);
if(tcplen >= INBUF_SIZE) {
log_msg("query %d bytes too large, buffer %d bytes.\n",
tcplen, INBUF_SIZE);
close(s);
return;
}
read_n_bytes(s, inbuf, tcplen);
handle_query(inbuf, tcplen, entries, count, transport_tcp, send_tcp, &userdata);
close(s);
}
int
main(int argc, char **argv)
{
/* arguments */
int c;
int port = DEFAULT_PORT;
const char* datafile;
int count;
/* network */
int udp_sock, tcp_sock;
fd_set rset, wset, eset;
struct timeval timeout;
int maxfd;
/* dns */
struct entry* entries;
/* parse arguments */
logfile = stdout;
prog_name = argv[0];
log_msg("%s: start\n", prog_name);
while((c = getopt(argc, argv, "p:v")) != -1) {
switch(c) {
case 'p':
port = atoi(optarg);
if (port < 1) {
error("Invalid port %s, use a number.", optarg);
}
break;
case 'v':
verbose++;
break;
default:
usage();
break;
}
}
argc -= optind;
argv += optind;
if(argc == 0 || argc > 1)
usage();
datafile = argv[0];
log_msg("Reading datafile %s\n", datafile);
entries = read_datafile(datafile);
log_msg("Listening on port %d\n", port);
if((udp_sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
error("udp socket(): %s\n", strerror(errno));
}
if((tcp_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
error("tcp socket(): %s\n", strerror(errno));
}
c = 1;
if(setsockopt(tcp_sock, SOL_SOCKET, SO_REUSEADDR, &c, sizeof(int)) < 0) {
error("setsockopt(SO_REUSEADDR): %s\n", strerror(errno));
}
/* bind ip4 */
if (bind_port(udp_sock, port)) {
error("cannot bind(): %s\n", strerror(errno));
}
if (bind_port(tcp_sock, port)) {
error("cannot bind(): %s\n", strerror(errno));
}
if (listen(tcp_sock, CONN_BACKLOG) < 0) {
error("listen(): %s\n", strerror(errno));
}
/* service */
count = 0;
timeout.tv_sec = 0;
timeout.tv_usec = 0;
while (1) {
FD_ZERO(&rset);
FD_ZERO(&wset);
FD_ZERO(&eset);
FD_SET(udp_sock, &rset);
FD_SET(tcp_sock, &rset);
maxfd = udp_sock;
if(tcp_sock > maxfd)
maxfd = tcp_sock;
if(select(maxfd+1, &rset, &wset, &eset, NULL) < 0) {
error("select(): %s\n", strerror(errno));
}
if(FD_ISSET(udp_sock, &rset)) {
handle_udp(udp_sock, entries, &count);
}
if(FD_ISSET(tcp_sock, &rset)) {
handle_tcp(tcp_sock, entries, &count);
}
}
return 0;
}

View File

@@ -0,0 +1,196 @@
/*
* nsd-ldnsd. Light-weight DNS daemon, which sends IXFRs
*
* Tiny dns server to show how a real one could be built.
* This version is used for NSD test, send out IXFR's only.
*
* (c) NLnet Labs, 2005, 2006
* See the file LICENSE for the license
*/
#include "config.h"
#include <ldns/ldns.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <netinet/igmp.h>
#include <errno.h>
#define INBUF_SIZE 4096
#define MAX_LEN 1024
void usage(FILE *output)
{
fprintf(output, "Usage: nsd-ldnsd -# <port> <zone> <soa-serial>\n");
fprintf(output, "Listens on the specified port and answer every query with an IXFR\n");
fprintf(output, "This is NOT a full-fledged authoritative nameserver! It is NOTHING.\n");
fprintf(output, "-# quit after this many queries.\n");
}
static int udp_bind(int sock, int port, const char *my_address)
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = (in_port_t)htons((uint16_t)port);
addr.sin_addr.s_addr = INADDR_ANY;
return bind(sock, (struct sockaddr *)&addr, (socklen_t) sizeof(addr));
}
int
main(int argc, char **argv)
{
/* arguments */
int port;
int soa;
ldns_rdf *zone_name;
size_t count;
size_t maxcount;
/* network */
int sock;
ssize_t nb;
struct sockaddr addr_me;
struct sockaddr addr_him;
socklen_t hislen = sizeof(addr_him);
const char *my_address;
uint8_t inbuf[INBUF_SIZE];
uint8_t *outbuf;
/* dns */
ldns_status status;
ldns_pkt *query_pkt;
ldns_pkt *answer_pkt;
size_t answer_size;
ldns_rr *query_rr;
ldns_rr *rr;
char rr_string[MAX_LEN + 1];
ldns_rr *soa_rr;
char soa_string[MAX_LEN + 1];
/* use this to listen on specified interfaces later? */
my_address = NULL;
if(argc == 5) {
/* -# num given */
if (argv[1][0] == '-') {
maxcount = atoi(argv[1] + 1);
if (maxcount == 0) {
usage(stdout);
exit(EXIT_FAILURE);
} else {
fprintf(stderr, "quitting after %d qs\n", (int)maxcount);
}
} else {
fprintf(stderr, "Use -Number for max count\n");
exit(EXIT_FAILURE);
}
argc--;
argv++;
} else {
maxcount = 0;
}
if (argc != 4) {
usage(stdout);
exit(EXIT_FAILURE);
} else {
port = atoi(argv[1]);
if (port < 1) {
fprintf(stderr, "Use a number for the port\n");
usage(stdout);
exit(EXIT_FAILURE);
}
zone_name = ldns_dname_new_frm_str(argv[2]);
if (!zone_name) {
fprintf(stderr, "Illegal domain name: %s\n", argv[2]);
usage(stdout);
exit(EXIT_FAILURE);
}
soa = atoi(argv[3]);
if (soa < 1) {
fprintf(stderr, "Illegal soa number\n");
usage(stdout);
exit(EXIT_FAILURE);
}
}
printf("Listening on port %d\n", port);
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
fprintf(stderr, "%s: socket(): %s\n", argv[0], strerror(errno));
exit(EXIT_FAILURE);
}
memset(&addr_me, 0, sizeof(addr_me));
/* bind: try all ports in that range */
if (udp_bind(sock, port, my_address)) {
fprintf(stderr, "%s: cannot bind(): %s\n", argv[0], strerror(errno));
}
/* create our ixfr answer */
answer_pkt = ldns_pkt_new();
snprintf(rr_string, MAX_LEN, "%s IN IXFR", argv[2]);
(void)ldns_rr_new_frm_str(&rr, rr_string , 0, NULL, NULL);
(void)ldns_pkt_push_rr(answer_pkt, LDNS_SECTION_QUESTION, rr);
/* next add some rrs, with SOA stuff so that we mimic or ixfr reply */
snprintf(soa_string, MAX_LEN, "%s IN SOA miek.miek.nl elektron.atoom.net %d 1 2 3000 4",
argv[2], soa);
(void)ldns_rr_new_frm_str(&soa_rr, soa_string, 0, NULL, NULL);
snprintf(rr_string, MAX_LEN, "%s IN A 127.0.0.1", argv[2]);
(void)ldns_rr_new_frm_str(&rr, rr_string , 0, NULL, NULL);
/* compose the ixfr pkt */
(void)ldns_pkt_push_rr(answer_pkt, LDNS_SECTION_ANSWER, soa_rr);
(void)ldns_pkt_push_rr(answer_pkt, LDNS_SECTION_ANSWER, rr);
(void)ldns_pkt_push_rr(answer_pkt, LDNS_SECTION_ANSWER, soa_rr);
/* Done. Now receive */
count = 0;
while (1) {
nb = recvfrom(sock, inbuf, INBUF_SIZE, 0, &addr_him, &hislen);
if (nb < 1) {
fprintf(stderr, "%s: recvfrom(): %s\n",
argv[0], strerror(errno));
exit(EXIT_FAILURE);
}
printf("Got query of %d bytes\n", (int)nb);
status = ldns_wire2pkt(&query_pkt, inbuf, nb);
if (status != LDNS_STATUS_OK) {
printf("Got bad packet: %s\n", ldns_get_errorstr_by_id(status));
continue;
}
query_rr = ldns_rr_list_rr(ldns_pkt_question(query_pkt), 0);
printf("%d QUERY RR +%d: \n", (int)++count, ldns_pkt_id(query_pkt));
ldns_rr_print(stdout, query_rr);
ldns_pkt_set_id(answer_pkt, ldns_pkt_id(query_pkt));
status = ldns_pkt2wire(&outbuf, answer_pkt, &answer_size);
printf("Answer packet size: %u bytes.\n", (unsigned int) answer_size);
if (status != LDNS_STATUS_OK) {
printf("Error creating answer: %s\n", ldns_get_errorstr_by_id(status));
} else {
nb = (size_t) sendto(sock, outbuf, answer_size, 0, &addr_him, hislen);
}
if (maxcount > 0 && count >= maxcount) {
fprintf(stderr, "%d queries seen... goodbye\n", (int)count);
exit(EXIT_SUCCESS);
}
}
return 0;
}

View File

@@ -0,0 +1,188 @@
/*
* nsec3-covers. Parses NSEC3s from drill and shows what is covered.
*
* Pipe the output of the dig/drill query with NSEC3s to the tool.
* It will print which domain names are covered by NSEC3s.
*
* (c) NLnet Labs, 2005, 2006
* See the file LICENSE for the license
*/
#include "config.h"
#include <ldns/ldns.h>
#include <errno.h>
void usage(FILE *output)
{
fprintf(output, "Usage: dig +dnssec <query> | nsec3-covers or\n"
"drill -D <query> | nsec3-covers\n");
}
void abort_ldns_error(const char* str, ldns_status err)
{
fprintf(stderr, "error: %s: %s\n", str, ldns_get_errorstr_by_id(err));
exit(1);
}
char*
skip_comments_and_query(FILE* in, ldns_rdf ** qname)
{
static char buf[10240];
/* read comment lines */
while(1) {
if(!fgets(buf, sizeof(buf), in))
return 0; /* EOF */
printf("%s", buf); /* echo */
if(strcmp(buf, "") == 0 || strcmp(buf, "\n") == 0)
continue;
if(buf[0] != ';')
break;
if(strncmp(buf, ";; QUESTION SECTION:", 20) == 0)
{
char *q_rr = buf;
/* read question on next line, ;;s before */
if(!fgets(buf, sizeof(buf), in)) return 0;
while(*q_rr == ';' || *q_rr == ' ' || *q_rr == '\t')
++q_rr;
printf("Question: %s", q_rr);
*strchr(q_rr, '\t') = 0;
*qname = ldns_dname_new_frm_str(q_rr);
}
}
return buf;
}
void
read_in(ldns_rr_list* list, ldns_rdf** qname, FILE *in)
{
char* buf;
while((buf=skip_comments_and_query(in, qname)))
{
/* add rr */
ldns_rr *rr=0;
ldns_rdf *origin=0, *prev=0;
ldns_status err;
uint16_t ttl = 3600;
if((err=ldns_rr_new_frm_str(&rr, buf, ttl, origin, &prev)) !=
LDNS_STATUS_OK)
abort_ldns_error("read rr", err);
ldns_rr_list_push_rr(list, rr);
}
printf("nsec3-covers: read %d rrs\n", (int)ldns_rr_list_rr_count(list));
if(!qname) {
printf("Could not read question name\n");
exit(1);
}
printf("nsec3-covers: qname is ");
ldns_rdf_print(stdout, *qname);
printf("\n");
}
struct donelist {
ldns_rdf* name;
struct donelist* next;
};
static struct donelist *done = 0;
/* this is a linear speed test (slow for large numbers).
but the dig response will be small anyway. */
int check_done(ldns_rdf *qname)
{
struct donelist* p = done;
while(p) {
if(ldns_dname_compare(qname, p->name)==0)
return 1;
p = p->next;
}
/* not done yet add to list */
p = (struct donelist*)malloc(sizeof(struct donelist));
p->name = qname;
p->next = done;
done = p;
return 0;
}
void
check_cover(ldns_rr_list *list, ldns_rdf *qname)
{
ldns_status status;
size_t i;
if(check_done(qname))
return;
for(i=0; i<ldns_rr_list_rr_count(list); ++i)
{
ldns_rr* nsec3 = ldns_rr_list_rr(list, i);
if(ldns_rr_get_type(nsec3) != LDNS_RR_TYPE_NSEC3) {
/* skip non nsec3 */
continue;
}
ldns_rdf* hashed = ldns_nsec3_hash_name_frm_nsec3(
nsec3, qname);
status = ldns_dname_cat(hashed, ldns_dname_left_chop(
ldns_rr_owner(nsec3)));
if(status != LDNS_STATUS_OK)
abort_ldns_error("ldns_dname_cat", status);
if(ldns_dname_compare(hashed, ldns_rr_owner(nsec3)) == 0) {
ldns_rdf_print(stdout, ldns_rr_owner(nsec3));
printf(" proves ");
ldns_rdf_print(stdout, qname);
printf(" exists.\n");
}
else if(ldns_nsec_covers_name(nsec3, hashed)) {
ldns_rdf_print(stdout, ldns_rr_owner(nsec3));
printf(" proves ");
ldns_rdf_print(stdout, qname);
printf(" does not exist.\n");
}
ldns_rdf_free(hashed);
}
}
void
covertests(ldns_rr_list *list, ldns_rdf *qname)
{
size_t i;
ldns_rdf *smaller = qname;
ldns_rdf *wcard = ldns_dname_new_frm_str("*");
for(i=0; i<ldns_dname_label_count(qname)+1; ++i)
{
check_cover(list, smaller);
ldns_rdf* wcardchild = ldns_dname_cat_clone(wcard, smaller);
check_cover(list, wcardchild);
smaller = ldns_dname_left_chop(smaller);
}
/* check covers by weird names */
if(0) {
check_cover(list, ldns_dname_new_frm_str("x.bar.example."));
check_cover(list, ldns_dname_new_frm_str("bar.example."));
}
}
int
main(int argc, char **argv)
{
size_t i;
if(argc != 1) {
usage(stderr);
return 0;
}
/* read in */
ldns_rr_list *list = ldns_rr_list_new();
ldns_rdf *qname = 0;
read_in(list, &qname, stdin);
/* check covers */
covertests(list, qname);
for(i=0; i<ldns_rr_list_rr_count(list); ++i)
{
ldns_rr* rr = ldns_rr_list_rr(list, i);
if(!ldns_dname_is_subdomain(qname, ldns_rr_owner(rr))) {
covertests(list, ldns_rr_owner(rr));
}
}
ldns_rr_list_deep_free(list);
return 0;
}

View File

@@ -0,0 +1,77 @@
# comment at start.
; Test queries for ldns-testns.
; This is a line based format config file.
$ORIGIN example.com
$TTL 3600
# Below are the entries, they are tested for a match one by one.
# enclose each in ENTRY_BEGIN and ENTRY_END.
ENTRY_BEGIN
# MATCH makes sure the query and answer match on:
MATCH opcode qtype qname ; for default queries
# or you can match on a specific value.
; MATCH serial=1024 ; for this query also match SOA serial.
; MATCH UDP ; query must be by udp.
#
# REPLY lines give header info for the reply:
# (opcode) QUERY IQUERY STATUS NOTIFY UPDATE
# (rcode) NOERROR FORMERR SERVFAIL NXDOMAIN NOTIMPL YXDOMAIN
# YXRRSET NXRRSET NOTAUTH NOTZONE
# (flags) QR AA TC RD CD RA AD
REPLY QR ; this is a query reply.
# ADJUST: runtime modifications to the reply. usually copy_id.
ADJUST copy_id
# add RRs to a section, QUESTION ANSWER AUTHORITY ADDITIONAL
SECTION QUESTION
# RRs, (an RR must be on a single line).
@ IN A
SECTION ANSWER
@ A 192.168.0.123
@ TXT "This record is unexpected."
SECTION ADDITIONAL
@ A 192.1.2.3
ENTRY_END
ENTRY_BEGIN
MATCH TCP opcode qname
ADJUST copy_id
SECTION QUESTION
axfr.example.com. IN AXFR
SECTION ANSWER
axfr.example.com. IN SOA a. b. 10 60 60 60 60 60
bla.axfr.example.com. IN TXT "bla"
EXTRA_PACKET
ADJUST packet_sleep=4
SECTION ANSWER
bla.axfr.example.com. IN TXT "bla2"
axfr.example.com. IN SOA a. b. 10 60 60 60 60 60
ENTRY_END
ENTRY_BEGIN
MATCH TCP
REPLY SERVFAIL
ADJUST copy_id
ldns.testns.example. TXT "The ldnstestns server handled your TCP request"
ENTRY_END
ENTRY_BEGIN
MATCH opcode qtype qname
MATCH serial=102
ADJUST copy_id
REPLY QR AA
SECTION QUESTION
example.net. IXFR
SECTION ANSWER
example.net. SOA ns1.example.net. . 0 103 0 0 0
ENTRY_END
ENTRY_BEGIN
; Keep this as the last entry.
; matches anything and returns this packet.
; so you will get an answer for every query.
REPLY SERVFAIL
ADJUST copy_id
ldns.testns.example. TXT "The ldnstestns server did not find a match for your query"
ENTRY_END