diff options
author | Ted Unangst <tedu@cvs.openbsd.org> | 2015-10-15 19:43:31 +0000 |
---|---|---|
committer | Ted Unangst <tedu@cvs.openbsd.org> | 2015-10-15 19:43:31 +0000 |
commit | a70fd1bcbddd9be3b0c2d799461503a96ee43aa8 (patch) | |
tree | 132dac0cec92ceddd733cda64e2da439a0954cb5 | |
parent | de73e0d3c414627c4d00fa11fb776907574d5562 (diff) |
import rebound, a lightweight dns proxy, for further polishing
-rw-r--r-- | usr.sbin/rebound/Makefile | 9 | ||||
-rw-r--r-- | usr.sbin/rebound/randomid.c | 78 | ||||
-rw-r--r-- | usr.sbin/rebound/rebound.8 | 38 | ||||
-rw-r--r-- | usr.sbin/rebound/rebound.c | 523 |
4 files changed, 648 insertions, 0 deletions
diff --git a/usr.sbin/rebound/Makefile b/usr.sbin/rebound/Makefile new file mode 100644 index 00000000000..8510bffcb2b --- /dev/null +++ b/usr.sbin/rebound/Makefile @@ -0,0 +1,9 @@ +# $OpenBSD: Makefile,v 1.1 2015/10/15 19:43:30 tedu Exp $ + +PROG= rebound +SRCS= rebound.c randomid.c +CFLAGS+=-Wall + +MAN= rebound.8 + +.include <bsd.prog.mk> diff --git a/usr.sbin/rebound/randomid.c b/usr.sbin/rebound/randomid.c new file mode 100644 index 00000000000..5b98be2ef92 --- /dev/null +++ b/usr.sbin/rebound/randomid.c @@ -0,0 +1,78 @@ +/* $OpenBSD: randomid.c,v 1.1 2015/10/15 19:43:30 tedu Exp $ */ + +/* + * Copyright (c) 2008 Theo de Raadt, Ryan McBride + * + * Slightly different algorithm from the one designed by + * Matthew Dillon <dillon@backplane.com> for The DragonFly Project + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This code was used for generating IP IDs. It also works for DNS IDs. + */ + +/* + * Random DNS sequence number generator. Use the system PRNG to shuffle + * the 65536 entry ID space. We reshuffle the ID we pick out of the array + * into the previous 32767 cells, providing an guarantee that an ID will not + * be reused for at least 32768 calls. + */ +#include <stdlib.h> +#define nitems(arr) (sizeof(arr) / sizeof(arr[0])) + +static u_int16_t ip_shuffle[65536]; +static int isindex = 0; + +/* + * Return a random DNS id. Shuffle the new value we get into the previous half + * of the ip_shuffle ring (-32767 or swap with ourself), to avoid duplicates + * occuring too quickly but also still be random. + * + * 0 is a special DNS ID -- don't return it. + */ +uint16_t +randomid(void) +{ + static int ipid_initialized; + u_int16_t si, r; + int i, i2; + + if (!ipid_initialized) { + ipid_initialized = 1; + + /* + * Initialize with a random permutation. Do so using Knuth + * which avoids the exchange in the Durstenfeld shuffle. + * (See "The Art of Computer Programming, Vol 2" 3rd ed, pg. 145). + */ + for (i = 0; i < nitems(ip_shuffle); ++i) { + i2 = arc4random_uniform(i + 1); + ip_shuffle[i] = ip_shuffle[i2]; + ip_shuffle[i2] = i; + } + } + + do { + arc4random_buf(&si, sizeof(si)); + i = isindex & 0xFFFF; + i2 = (isindex - (si & 0x7FFF)) & 0xFFFF; + r = ip_shuffle[i]; + ip_shuffle[i] = ip_shuffle[i2]; + ip_shuffle[i2] = r; + isindex++; + } while (r == 0); + + return (r); +} diff --git a/usr.sbin/rebound/rebound.8 b/usr.sbin/rebound/rebound.8 new file mode 100644 index 00000000000..fced4394229 --- /dev/null +++ b/usr.sbin/rebound/rebound.8 @@ -0,0 +1,38 @@ +.\" $OpenBSD: rebound.8,v 1.1 2015/10/15 19:43:30 tedu Exp $ +.\" +.\"Copyright (c) 2015 Ted Unangst <tedu@openbsd.org> +.\" +.\"Permission to use, copy, modify, and distribute this software for any +.\"purpose with or without fee is hereby granted, provided that the above +.\"copyright notice and this permission notice appear in all copies. +.\" +.\"THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\"WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\"MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\"ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\"WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\"ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\"OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.Dd $Mdocdate: October 15 2015 $ +.Dt REBOUND 8 +.Os +.Sh NAME +.Nm rebound +.Nd DNS proxy +.Sh SYNOPSIS +.Nm rebound +.Op Fl c Ar config +.Sh DESCRIPTION +The +.Nm +daemon proxies DNS requests. +.Sh SEE ALSO +.Xr resolv.conf 5 , +.Xr unbound 8 +.Sh HISTORY +The +.Nm +daemon first appeared in +.Ox 5.9 . +.Sh AUTHORS +.An Ted Unangst Aq Mt tedu@openbsd.org diff --git a/usr.sbin/rebound/rebound.c b/usr.sbin/rebound/rebound.c new file mode 100644 index 00000000000..0a670a16333 --- /dev/null +++ b/usr.sbin/rebound/rebound.c @@ -0,0 +1,523 @@ +/* $OpenBSD: rebound.c,v 1.1 2015/10/15 19:43:30 tedu Exp $ */ +/* + * Copyright (c) 2015 Ted Unangst <tedu@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <sys/queue.h> +#include <sys/event.h> +#include <sys/time.h> +#include <sys/signal.h> +#include <sys/wait.h> + +#include <signal.h> +#include <syslog.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <err.h> +#include <unistd.h> +#include <assert.h> +#include <pwd.h> +#include <errno.h> +#include <getopt.h> +#include <stdarg.h> + +uint16_t randomid(void); + +struct timespec now; +int debug; + +struct dnspacket { + uint16_t id; + uint16_t flags; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; + /* ... */ +}; + +struct dnsrr { + uint16_t type; + uint16_t class; + uint32_t ttl; + uint16_t rdatalen; + /* ... */ +}; + +struct dnscache { + TAILQ_ENTRY(dnscache) cache; + struct dnspacket *req; + size_t reqlen; + struct dnspacket *resp; + size_t resplen; + struct timespec ts; +}; +TAILQ_HEAD(, dnscache) cache; + +struct request { + int s; + int client; + struct sockaddr from; + socklen_t fromlen; + struct timespec ts; + TAILQ_ENTRY(request) fifo; + uint16_t clientid; + uint16_t reqid; + struct dnscache *cacheent; +}; +TAILQ_HEAD(, request) reqfifo; + + +void +logmsg(int prio, const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + if (debug) { + vfprintf(stderr, msg, ap); + fprintf(stderr, "\n"); + } else { + vsyslog(prio, msg, ap); + } + va_end(ap); +} + +struct dnscache * +cachelookup(struct dnspacket *dnsreq, size_t reqlen) +{ + struct dnscache *hit; + uint16_t origid; + + origid = dnsreq->id; + dnsreq->id = 0; + TAILQ_FOREACH(hit, &cache, cache) { + if (memcmp(hit->req, dnsreq, reqlen) == 0) + break; + } + dnsreq->id = origid; + return hit; +} + +struct request * +newrequest(int ud, struct sockaddr *remoteaddr) +{ + struct sockaddr from; + socklen_t fromlen; + struct request *req; + uint8_t buf[65536]; + struct dnspacket *dnsreq; + struct dnscache *hit; + size_t r; + + dnsreq = (struct dnspacket *)buf; + + fromlen = sizeof(from); + r = recvfrom(ud, buf, sizeof(buf), 0, &from, &fromlen); + if (r == 0 || r == -1 || r < sizeof(struct dnspacket)) + return NULL; + + if ((hit = cachelookup(dnsreq, r))) { + hit->resp->id = dnsreq->id; + sendto(ud, hit->resp, hit->resplen, 0, &from, fromlen); + return NULL; + } + + if (!(req = calloc(1, sizeof(*req)))) + return NULL; + + req->client = -1; + memcpy(&req->from, &from, fromlen); + req->fromlen = fromlen; + + req->clientid = dnsreq->id; + req->reqid = randomid(); + dnsreq->id = req->reqid; + + hit = calloc(1, sizeof(*hit)); + if (hit) { + hit->req = malloc(r); + if (hit->req) { + memcpy(hit->req, dnsreq, r); + hit->req->id = 0; + } else { + free(hit); + hit = NULL; + + } + } + req->cacheent = hit; + + req->s = socket(AF_INET, SOCK_DGRAM, 0); + if (req->s == -1) + goto fail; + if (connect(req->s, remoteaddr, remoteaddr->sa_len) == -1) { + logmsg(0, "failed to connect"); + goto fail; + } + if (send(req->s, buf, r, 0) != r) + goto fail; + req->ts = now; + req->ts.tv_sec += 30; + + return req; +fail: + close(req->s); + free(req); + return NULL; +} + +void +sendreply(int ud, struct request *req) +{ + uint8_t buf[65536]; + struct dnspacket *resp; + size_t r; + + resp = (struct dnspacket *)buf; + + r = recv(req->s, buf, sizeof(buf), 0); + if (r == 0 || r == -1 || r < sizeof(struct dnspacket)) + return; + if (resp->id != req->reqid) + return; + resp->id = req->clientid; + sendto(ud, buf, r, 0, &req->from, req->fromlen); + if (req->cacheent) { + req->cacheent->ts = now; + req->cacheent->ts.tv_sec += 10; + TAILQ_INSERT_TAIL(&cache, req->cacheent, cache); + req->cacheent->resp = malloc(r); + if (!req->cacheent->resp) + return; + memcpy(req->cacheent->resp, buf, r); + req->cacheent->resplen = r; + } +} + +void +freerequest(struct request *req) { + TAILQ_REMOVE(&reqfifo, req, fifo); + close(req->client); + close(req->s); + free(req); +} + +void +freecacheent(struct dnscache *ent) { + TAILQ_REMOVE(&cache, ent, cache); + free(ent->req); + free(ent->resp); + free(ent); +} + +struct request * +newtcprequest(int ld, struct sockaddr *remoteaddr) +{ + struct request *req; + + if (!(req = malloc(sizeof(*req)))) + return NULL; + + req->s = -1; + req->fromlen = sizeof(req->from); + req->client = accept(ld, &req->from, &req->fromlen); + if (req->client == -1) + goto fail; + + req->s = socket(AF_INET, SOCK_STREAM, 0); + if (req->s == -1) + goto fail; + if (connect(req->s, remoteaddr, remoteaddr->sa_len) == -1) + goto fail; + if (setsockopt(req->client, SOL_SOCKET, SO_SPLICE, &req->s, + sizeof(req->s)) == -1) + goto fail; + if (setsockopt(req->s, SOL_SOCKET, SO_SPLICE, &req->client, + sizeof(req->client)) == -1) + goto fail; + req->ts = now; + req->ts.tv_sec += 30; + + return req; +fail: + close(req->s); + close(req->client); + free(req); + return NULL; +} + +int +readconfig(FILE *conf, struct sockaddr_storage *remoteaddr) +{ + char buf[1024]; + struct sockaddr_in *sin = (struct sockaddr_in *)remoteaddr; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)remoteaddr; + + if (fgets(buf, sizeof(buf), conf) == NULL) + return -1; + buf[strcspn(buf, "\n")] = '\0'; + + memset(remoteaddr, 0, sizeof(*remoteaddr)); + if (inet_pton(AF_INET, buf, &sin->sin_addr) == 1) { + sin->sin_len = sizeof(*sin); + sin->sin_family = AF_INET; + sin->sin_port = htons(53); + return AF_INET; + } else if (inet_pton(AF_INET6, buf, &sin6->sin6_addr) == 1) { + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_family = AF_INET6; + sin6->sin6_port = htons(53); + return AF_INET6; + } else { + return -1; + } +} + +int +launch(const char *confname, int ud, int ld, int kq) +{ + struct sockaddr_storage remoteaddr; + struct kevent chlist[1], kev[4]; + struct timespec ts, *timeout = NULL; + struct request *req; + struct dnscache *ent; + struct passwd *pwd; + FILE *conf; + int i, r, af; + pid_t child; + + conf = fopen(confname, "r"); + if (!conf) { + logmsg(LOG_DAEMON | LOG_ERR, "failed to open config %s", confname); + return -1; + } + + if (!debug) { + if ((child = fork())) + return child; + } + + pwd = getpwnam("nobody"); + + if (chroot("/var/empty") || chdir("/")) { + logmsg(LOG_DAEMON | LOG_ERR, "chroot failed (%d)", errno); + exit(1); + } + + setproctitle("worker"); + setresuid(pwd->pw_uid, pwd->pw_uid, pwd->pw_uid); + + close(kq); + + af = readconfig(conf, &remoteaddr); + fclose(conf); + if (af == -1) { + logmsg(LOG_DAEMON | LOG_ERR, "failed to read config %s", confname); + exit(1); + } + + kq = kqueue(); + + EV_SET(&kev[0], ud, EVFILT_READ, EV_ADD, 0, 0, NULL); + EV_SET(&kev[1], ld, EVFILT_READ, EV_ADD, 0, 0, NULL); + EV_SET(&kev[2], SIGHUP, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); + kevent(kq, kev, 3, NULL, 0, NULL); + signal(SIGHUP, SIG_IGN); + while (1) { + r = kevent(kq, NULL, 0, kev, 4, timeout); + if (r == -1) { + logmsg(LOG_DAEMON | LOG_ERR, "kevent failed (%d)", errno); + exit(1); + } + clock_gettime(CLOCK_MONOTONIC, &now); + + for (i = 0; i < r; i++) { + if (kev[i].filter == EVFILT_SIGNAL) { + exit(0); + } else if (kev[i].ident == ud) { + req = newrequest(ud, + (struct sockaddr *)&remoteaddr); + if (req) { + EV_SET(&chlist[0], req->s, EVFILT_READ, + EV_ADD, 0, 0, NULL); + kevent(kq, chlist, 1, NULL, 0, NULL); + TAILQ_INSERT_TAIL(&reqfifo, req, fifo); + } + } else if (kev[i].ident == ld) { + req = newtcprequest(ld, + (struct sockaddr *)&remoteaddr); + if (req) { + EV_SET(&chlist[0], req->s, EVFILT_READ, + EV_ADD, 0, 0, NULL); + kevent(kq, chlist, 1, NULL, 0, NULL); + TAILQ_INSERT_TAIL(&reqfifo, req, fifo); + } + } else { + /* use a tree here? */ + req = TAILQ_FIRST(&reqfifo); + while (req) { + if (req->s == kev[i].ident) + break; + req = TAILQ_NEXT(req, fifo); + } + assert(req); + if (req->client == -1) + sendreply(ud, req); + freerequest(req); + } + } + + timeout = NULL; + /* burn old cache entries */ + while ((ent = TAILQ_FIRST(&cache))) { + if (timespeccmp(&ent->ts, &now, <=)) + freecacheent(ent); + else + break; + } + if (ent) { + timespecsub(&ent->ts, &now, &ts); + timeout = &ts; + } + + /* burn stalled requests */ + while ((req = TAILQ_FIRST(&reqfifo))) { + if (timespeccmp(&req->ts, &now, <=)) + freerequest(req); + else + break; + } + if (req && (!ent || timespeccmp(&req->ts, &ent->ts, <=))) { + timespecsub(&req->ts, &now, &ts); + timeout = &ts; + } + + } + exit(0); +} + +static void __dead +usage(void) +{ + fprintf(stderr, "usage:\trebound [-c config]\n"); + exit(1); +} + +int +main(int argc, char **argv) +{ + struct sockaddr_in bindaddr; + int r, kq, ld, ud, ch; + int one; + pid_t child; + struct kevent kev; + int hupped = 0; + struct timespec ts, *timeout = NULL; + const char *conffile = "/etc/rebound.conf"; + + while ((ch = getopt(argc, argv, "c:d")) != -1) { + switch (ch) { + case 'c': + conffile = optarg; + break; + case 'd': + debug = 1; + break; + default: + usage(); + break; + } + } + argv += optind; + argc -= optind; + + if (argc) + usage(); + + if (!debug) + daemon(0, 0); + + TAILQ_INIT(&reqfifo); + TAILQ_INIT(&cache); + + memset(&bindaddr, 0, sizeof(bindaddr)); + bindaddr.sin_len = sizeof(bindaddr); + bindaddr.sin_family = AF_INET; + bindaddr.sin_port = htons(53); + inet_aton("127.0.0.1", &bindaddr.sin_addr); + + ud = socket(AF_INET, SOCK_DGRAM, 0); + if (ud == -1) + err(1, "socket"); + if (bind(ud, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) == -1) + err(1, "bind"); + + ld = socket(AF_INET, SOCK_STREAM, 0); + if (ld == -1) + err(1, "socket"); + one = 1; + setsockopt(ld, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (bind(ld, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) == -1) + err(1, "bind"); + if (listen(ld, 10) == -1) + err(1, "listen"); + + if (debug) { + launch(conffile, ud, ld, -1); + return 1; + } + + kq = kqueue(); + + EV_SET(&kev, SIGHUP, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); + kevent(kq, &kev, 1, NULL, 0, NULL); + signal(SIGHUP, SIG_IGN); + while (1) { + child = launch(conffile, ud, ld, kq); + if (child == -1) { + logmsg(LOG_DAEMON | LOG_ERR, "failed to launch"); + return 1; + } + /* monitor child */ + EV_SET(&kev, child, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); + kevent(kq, &kev, 1, NULL, 0, NULL); + + /* wait for something to happen: HUP or child exiting */ + while (1) { + r = kevent(kq, NULL, 0, &kev, 1, timeout); + if (r == 0) { + logmsg(LOG_DAEMON | LOG_ERR, "child died without HUP"); + return 1; + } + if (kev.filter == EVFILT_SIGNAL) { + /* signaled. kill child. */ + hupped = 1; + kill(child, SIGHUP); + break; + } + /* child died. wait one second for our own HUP. */ + memset(&ts, 0, sizeof(ts)); + ts.tv_sec = 1; + timeout = &ts; + } + wait(NULL); + } + return 1; +} |