Hi everyone,
I've put together a patch for 6.0-stable that adds domain name
matching support to rebound(8). The patch is quite rough at the
moment.
The config is as follows:
match "local." 10.0.0.53
match "." 8.8.8.8
Requests to foo.local. are sent over to 10.0.0.53, all other requests
go to 8.8.8.8. In my implementation, the first match wins.
General drawbacks:
- rebound has to parse DNS requests. I tried to keep the parsing code
as small as possible to avoid security problems.
Drawbacks in current implementation:
- No caching for DNS requests over TCP. I am planning to implement
this via a unified cache that works for both UDP and TCP.
- non-blocking connect(2) support for TCP. The original code handled
that but I reworked it because I wanted to get it working first.
What do you think?
===
RCS file: /cvs/src/usr.sbin/rebound/rebound.c,v
retrieving revision 1.65
diff -u -p -r1.65 rebound.c
--- rebound.c 2 Jul 2016 17:09:09 - 1.65
+++ rebound.c 16 Sep 2016 12:29:39 -
@@ -37,6 +37,8 @@
#include
#include
+#define LEN(x) (sizeof (x) / sizeof *(x))
+
uint16_t randomid(void);
static struct timespec now;
@@ -100,6 +102,13 @@ struct request {
};
static TAILQ_HEAD(, request) reqfifo;
+struct match {
+ char pat[256];
+ struct sockaddr_storage to;
+ TAILQ_ENTRY(match) entry;
+};
+static TAILQ_HEAD(, match) matches;
+
static int conncount;
static int connmax;
static uint64_t conntotal;
@@ -215,10 +224,94 @@ servfail(int ud, uint16_t id, struct soc
sendto(ud, &pkt, sizeof(pkt), 0, fromaddr, fromlen);
}
+static size_t
+readn(int fd, void *buf, size_t n)
+{
+ size_t total = 0;
+ size_t r;
+
+ while (n > 0) {
+ r = read(fd, buf + total, n);
+ if (r == 0 || r == -1)
+ return -1;
+ total += r;
+ n -= r;
+ }
+ return total;
+}
+
+static size_t
+writen(int fd, void *buf, size_t n)
+{
+ size_t total = 0;
+ size_t r;
+
+ while (n > 0) {
+ r = write(fd, buf + total, n);
+ if (r == 0 || r == -1)
+ return -1;
+ total += r;
+ n -= r;
+ }
+ return total;
+}
+
+int
+parsedomain(uint8_t *buf, size_t buflen, char *host, size_t hostlen)
+{
+ uint8_t *bp = &buf[0], *be = &buf[buflen];
+ char *hp = &host[0], *he = &host[hostlen];
+
+ bp += sizeof(struct dnspacket);
+ if (bp >= be)
+ return -1;
+ for (;;) {
+ uint8_t len = *bp++;
+ if (len == 0)
+ break;
+ if (bp + len >= be || hp + len >= he)
+ return -1;
+ memcpy(hp, bp, len);
+ bp += len;
+ hp += len;
+ *hp++ = '.';
+ if (hp == he)
+ return -1;
+ }
+ *hp = '\0';
+ return 0;
+}
+
+int
+matchreq(uint8_t *buf, size_t buflen, struct sockaddr_storage *to)
+{
+ char host[65536];
+ struct match *match;
+
+ /* XXX: check flags/qdcount? */
+ if (parsedomain(buf, buflen, host, sizeof(host)) == -1)
+ return -1;
+ TAILQ_FOREACH(match, &matches, entry) {
+ size_t hlen = strlen(host);
+ size_t glen = strlen(match->pat);
+ if (hlen < glen)
+ continue;
+ if (strcmp(&host[hlen - glen], match->pat) == 0) {
+ memcpy(to, &match->to, sizeof(*to));
+ logmsg(LOG_DEBUG, "matched domain %s with %s",
+ host, match->pat);
+ /* first match wins */
+ return 0;
+ }
+ }
+ return -1;
+}
+
static struct request *
-newrequest(int ud, struct sockaddr *remoteaddr)
+newrequest(int ud)
{
- struct sockaddr from;
+ struct sockaddr_storage remoteaddr;
+ struct sockaddr from, *to;
socklen_t fromlen;
struct request *req;
uint8_t buf[65536];
@@ -271,13 +364,17 @@ newrequest(int ud, struct sockaddr *remo
}
req->cacheent = hit;
- req->s = socket(remoteaddr->sa_family, SOCK_DGRAM, 0);
+ if (matchreq(buf, r, &remoteaddr) == -1)
+ goto fail;
+ to = (struct sockaddr *)&remoteaddr;
+
+ req->s = socket(to->sa_family, SOCK_DGRAM, 0);
if (req->s == -1)
goto fail;
TAILQ_INSERT_TAIL(&reqfifo, req, fifo);
- if (connect(req->s, remoteaddr, remoteaddr->sa_len) == -1) {
+ if (connect(req->s, to, to->sa_len) == -1) {
logmsg(LOG_NOTICE, "failed to connect (%d)", errno);
if (errno == EADDRNOTAVAIL)
servfail(ud, req->clientid, &from, fromlen);
@@ -335,36 +432,18 @@ sendreply(int