416 lines
10 KiB
C
416 lines
10 KiB
C
/* $NetBSD: h_dns_server.c,v 1.4 2014/03/29 16:10:54 gson Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 2013 The NetBSD Foundation, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to The NetBSD Foundation
|
|
* by Andreas Gustafsson.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
|
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/*
|
|
* A minimal DNS server capable of providing canned answers to the
|
|
* specific queries issued by t_hostent.sh and nothing more.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__RCSID("$NetBSD: h_dns_server.c,v 1.4 2014/03/29 16:10:54 gson Exp $");
|
|
|
|
#include <ctype.h>
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <memory.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <netinet/in.h>
|
|
#ifdef __NetBSD__
|
|
#include <netinet6/in6.h>
|
|
#endif
|
|
|
|
#ifdef __FreeBSD__
|
|
#include <paths.h>
|
|
#endif
|
|
|
|
union sockaddr_either {
|
|
struct sockaddr s;
|
|
struct sockaddr_in sin;
|
|
struct sockaddr_in6 sin6;
|
|
};
|
|
|
|
#ifdef DEBUG
|
|
#define DPRINTF(...) fprintf(stderr, __VA_ARGS__)
|
|
#else
|
|
#define DPRINTF(...)
|
|
#endif
|
|
|
|
/* A DNS question and its corresponding answer */
|
|
|
|
struct dns_data {
|
|
size_t qname_size;
|
|
const char *qname; /* Wire-encode question name */
|
|
int qtype;
|
|
size_t answer_size;
|
|
const char *answer; /* One wire-encoded answer RDATA */
|
|
};
|
|
|
|
/* Convert C string constant to length + data pair */
|
|
#define STR_DATA(s) sizeof(s) - 1, s
|
|
|
|
/* Canned DNS queestion-answer pairs */
|
|
struct dns_data data[] = {
|
|
/* Forward mappings */
|
|
/* localhost IN A -> 127.0.0.1 */
|
|
{ STR_DATA("\011localhost\000"), 1,
|
|
STR_DATA("\177\000\000\001") },
|
|
/* localhost IN AAAA -> ::1 */
|
|
{ STR_DATA("\011localhost\000"), 28,
|
|
STR_DATA("\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001") },
|
|
/* sixthavenue.astron.com IN A -> 38.117.134.16 */
|
|
{ STR_DATA("\013sixthavenue\006astron\003com\000"), 1,
|
|
STR_DATA("\046\165\206\020") },
|
|
/* sixthavenue.astron.com IN AAAA -> 2620:106:3003:1f00:3e4a:92ff:fef4:e180 */
|
|
{ STR_DATA("\013sixthavenue\006astron\003com\000"), 28,
|
|
STR_DATA("\x26\x20\x01\x06\x30\x03\x1f\x00\x3e\x4a\x92\xff\xfe\xf4\xe1\x80") },
|
|
/* Reverse mappings */
|
|
{ STR_DATA("\0011\0010\0010\003127\007in-addr\004arpa\000"), 12,
|
|
STR_DATA("\011localhost\000") },
|
|
{ STR_DATA("\0011\0010\0010\0010\0010\0010\0010\0010"
|
|
"\0010\0010\0010\0010\0010\0010\0010\0010"
|
|
"\0010\0010\0010\0010\0010\0010\0010\0010"
|
|
"\0010\0010\0010\0010\0010\0010\0010\0010"
|
|
"\003ip6\004arpa\000"), 12,
|
|
STR_DATA("\011localhost\000") },
|
|
{ STR_DATA("\00216\003134\003117\00238"
|
|
"\007in-addr\004arpa\000"), 12,
|
|
STR_DATA("\013sixthavenue\006astron\003com\000") },
|
|
{ STR_DATA("\0010\0018\0011\001e\0014\001f\001e\001f"
|
|
"\001f\001f\0012\0019\001a\0014\001e\0013"
|
|
"\0010\0010\001f\0011\0013\0010\0010\0013"
|
|
"\0016\0010\0011\0010\0010\0012\0016\0012"
|
|
"\003ip6\004arpa\000"), 12,
|
|
STR_DATA("\013sixthavenue\006astron\003com\000") },
|
|
/* End marker */
|
|
{ STR_DATA(""), 0, STR_DATA("") }
|
|
};
|
|
|
|
/*
|
|
* Compare two DNS names for equality. If equal, return their
|
|
* length, and if not, return zero. Does not handle compression.
|
|
*/
|
|
static int
|
|
name_eq(const unsigned char *a, const unsigned char *b) {
|
|
const unsigned char *a_save = a;
|
|
for (;;) {
|
|
int i;
|
|
int lena = *a++;
|
|
int lenb = *b++;
|
|
if (lena != lenb)
|
|
return 0;
|
|
if (lena == 0)
|
|
return a - a_save;
|
|
for (i = 0; i < lena; i++)
|
|
if (tolower(a[i]) != tolower(b[i]))
|
|
return 0;
|
|
a += lena;
|
|
b += lena;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
static char *
|
|
name2str(const void *v, char *buf, size_t buflen) {
|
|
const unsigned char *a = v;
|
|
char *b = buf;
|
|
char *eb = buf + buflen;
|
|
|
|
#define ADDC(c) do { \
|
|
if (b < eb) \
|
|
*b++ = c; \
|
|
else \
|
|
return NULL; \
|
|
} while (/*CONSTCOND*/0)
|
|
for (int did = 0;; did++) {
|
|
int lena = *a++;
|
|
if (lena == 0) {
|
|
ADDC('\0');
|
|
return buf;
|
|
}
|
|
if (did)
|
|
ADDC('.');
|
|
for (int i = 0; i < lena; i++)
|
|
ADDC(a[i]);
|
|
a += lena;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef __FreeBSD__
|
|
/* XXX the daemon2_* functions should be in a library */
|
|
|
|
int __daemon2_detach_pipe[2];
|
|
|
|
static int
|
|
daemon2_fork(void)
|
|
{
|
|
int r;
|
|
int fd;
|
|
int i;
|
|
|
|
/*
|
|
* Set up the pipe, making sure the write end does not
|
|
* get allocated one of the file descriptors that will
|
|
* be closed in daemon2_detach().
|
|
*/
|
|
for (i = 0; i < 3; i++) {
|
|
r = pipe(__daemon2_detach_pipe);
|
|
if (r < 0)
|
|
return -1;
|
|
if (__daemon2_detach_pipe[1] <= STDERR_FILENO &&
|
|
(fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) {
|
|
(void)dup2(fd, __daemon2_detach_pipe[0]);
|
|
(void)dup2(fd, __daemon2_detach_pipe[1]);
|
|
if (fd > STDERR_FILENO)
|
|
(void)close(fd);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
r = fork();
|
|
if (r < 0) {
|
|
return -1;
|
|
} else if (r == 0) {
|
|
/* child */
|
|
close(__daemon2_detach_pipe[0]);
|
|
return 0;
|
|
}
|
|
/* Parent */
|
|
|
|
(void) close(__daemon2_detach_pipe[1]);
|
|
|
|
for (;;) {
|
|
char dummy;
|
|
r = read(__daemon2_detach_pipe[0], &dummy, 1);
|
|
if (r < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
_exit(1);
|
|
} else if (r == 0) {
|
|
_exit(1);
|
|
} else { /* r > 0 */
|
|
_exit(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
daemon2_detach(int nochdir, int noclose)
|
|
{
|
|
int r;
|
|
int fd;
|
|
|
|
if (setsid() == -1)
|
|
return -1;
|
|
|
|
if (!nochdir)
|
|
(void)chdir("/");
|
|
|
|
if (!noclose && (fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) {
|
|
(void)dup2(fd, STDIN_FILENO);
|
|
(void)dup2(fd, STDOUT_FILENO);
|
|
(void)dup2(fd, STDERR_FILENO);
|
|
if (fd > STDERR_FILENO)
|
|
(void)close(fd);
|
|
}
|
|
|
|
while (1) {
|
|
r = write(__daemon2_detach_pipe[1], "", 1);
|
|
if (r < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
/* May get "broken pipe" here if parent is killed */
|
|
return -1;
|
|
} else if (r == 0) {
|
|
/* Should not happen */
|
|
return -1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
(void) close(__daemon2_detach_pipe[1]);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
int main(int argc, char **argv) {
|
|
int s, r, protocol;
|
|
union sockaddr_either saddr;
|
|
struct dns_data *dp;
|
|
unsigned char *p;
|
|
char pidfile_name[40];
|
|
FILE *f;
|
|
int one = 1;
|
|
#ifdef DEBUG
|
|
char buf1[1024], buf2[1024];
|
|
#endif
|
|
|
|
#ifdef __FreeBSD__
|
|
daemon2_fork();
|
|
#endif
|
|
if (argc < 2 || ((protocol = argv[1][0]) != '4' && protocol != '6'))
|
|
errx(1, "usage: dns_server 4 | 6");
|
|
s = socket(protocol == '4' ? PF_INET : PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (s < 0)
|
|
err(1, "socket");
|
|
if (protocol == '4') {
|
|
memset(&saddr.sin, 0, sizeof(saddr.sin));
|
|
saddr.sin.sin_family = AF_INET;
|
|
saddr.sin.sin_len = sizeof(saddr.sin);
|
|
saddr.sin.sin_port = htons(53);
|
|
saddr.sin.sin_addr.s_addr = INADDR_ANY;
|
|
} else {
|
|
static struct in6_addr loopback = IN6ADDR_LOOPBACK_INIT;
|
|
memset(&saddr.sin6, 0, sizeof(saddr.sin6));
|
|
saddr.sin6.sin6_family = AF_INET6;
|
|
saddr.sin6.sin6_len = sizeof(saddr.sin6);
|
|
saddr.sin6.sin6_port = htons(53);
|
|
saddr.sin6.sin6_addr = loopback;
|
|
}
|
|
|
|
r = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
|
|
if (r < 0)
|
|
err(1, "setsockopt");
|
|
|
|
r = bind(s,
|
|
(struct sockaddr *) &saddr,
|
|
protocol == '4' ? sizeof(struct sockaddr_in) :
|
|
sizeof(struct sockaddr_in6));
|
|
if (r < 0)
|
|
err(1, "bind");
|
|
|
|
snprintf(pidfile_name, sizeof pidfile_name,
|
|
"dns_server_%c.pid", protocol);
|
|
f = fopen(pidfile_name, "w");
|
|
fprintf(f, "%d", getpid());
|
|
fclose(f);
|
|
#ifdef __FreeBSD__
|
|
#ifdef DEBUG
|
|
daemon2_detach(0, 1);
|
|
#else
|
|
daemon2_detach(0, 0);
|
|
#endif
|
|
#else
|
|
#ifdef DEBUG
|
|
daemon(0, 1);
|
|
#else
|
|
daemon(0, 0);
|
|
#endif
|
|
#endif
|
|
|
|
for (;;) {
|
|
unsigned char buf[512];
|
|
union sockaddr_either from;
|
|
ssize_t nrecv, nsent;
|
|
socklen_t fromlen =
|
|
protocol == '4' ? sizeof(struct sockaddr_in) :
|
|
sizeof(struct sockaddr_in6);
|
|
memset(buf, 0, sizeof buf);
|
|
nrecv = recvfrom(s, buf, sizeof buf, 0, &from.s, &fromlen);
|
|
if (nrecv < 0)
|
|
err(1, "recvfrom");
|
|
if (nrecv < 12) {
|
|
DPRINTF("Too short %zd\n", nrecv);
|
|
continue;
|
|
}
|
|
if ((buf[2] & 0x80) != 0) {
|
|
DPRINTF("Not a query 0x%x\n", buf[2]);
|
|
continue;
|
|
}
|
|
if (!(buf[4] == 0 && buf[5] == 1)) {
|
|
DPRINTF("QCOUNT is not 1 0x%x 0x%x\n", buf[4], buf[5]);
|
|
continue; /* QDCOUNT is not 1 */
|
|
}
|
|
|
|
for (dp = data; dp->qname_size != 0; dp++) {
|
|
int qtype, qclass;
|
|
p = buf + 12; /* Point to QNAME */
|
|
int n = name_eq(p, (const unsigned char *) dp->qname);
|
|
if (n == 0) {
|
|
DPRINTF("no match name %s != %s\n",
|
|
name2str(p, buf1, sizeof(buf1)),
|
|
name2str(dp->qname, buf2, sizeof(buf2)));
|
|
continue; /* Name does not match */
|
|
}
|
|
DPRINTF("match name %s\n",
|
|
name2str(p, buf1, sizeof(buf1)));
|
|
p += n; /* Skip QNAME */
|
|
qtype = *p++ << 8;
|
|
qtype |= *p++;
|
|
if (qtype != dp->qtype) {
|
|
DPRINTF("no match name 0x%x != 0x%x\n",
|
|
qtype, dp->qtype);
|
|
continue;
|
|
}
|
|
DPRINTF("match type 0x%x\n", qtype);
|
|
qclass = *p++ << 8;
|
|
qclass |= *p++;
|
|
if (qclass != 1) { /* IN */
|
|
DPRINTF("no match class %d != 1\n", qclass);
|
|
continue;
|
|
}
|
|
DPRINTF("match class %d\n", qclass);
|
|
goto found;
|
|
}
|
|
continue;
|
|
found:
|
|
buf[2] |= 0x80; /* QR */
|
|
buf[3] |= 0x80; /* RA */
|
|
memset(buf + 6, 0, 6); /* Clear ANCOUNT, NSCOUNT, ARCOUNT */
|
|
buf[7] = 1; /* ANCOUNT */
|
|
memcpy(p, dp->qname, dp->qname_size);
|
|
p += dp->qname_size;
|
|
*p++ = dp->qtype >> 8;
|
|
*p++ = dp->qtype & 0xFF;
|
|
*p++ = 0;
|
|
*p++ = 1; /* IN */
|
|
memset(p, 0, 4); /* TTL = 0 */
|
|
p += 4;
|
|
*p++ = 0; /* RDLENGTH MSB */
|
|
*p++ = dp->answer_size; /* RDLENGTH LSB */
|
|
memcpy(p, dp->answer, dp->answer_size);
|
|
p += dp->answer_size;
|
|
nsent = sendto(s, buf, p - buf, 0, &from.s, fromlen);
|
|
DPRINTF("sent %zd\n", nsent);
|
|
if (nsent != p - buf)
|
|
warn("sendto");
|
|
}
|
|
}
|