diff options
author | Laurent Bercot <ska-skaware@skarnet.org> | 2020-12-28 22:09:00 +0000 |
---|---|---|
committer | Laurent Bercot <ska-skaware@skarnet.org> | 2020-12-28 22:09:00 +0000 |
commit | fab29b78d87d0114ecbdfbcac3b0e7c0edb463ba (patch) | |
tree | 786d09f619fa7302851e02fa7d51f53151db518c /src | |
download | dnsfunnel-fab29b78d87d0114ecbdfbcac3b0e7c0edb463ba.tar.gz dnsfunnel-fab29b78d87d0114ecbdfbcac3b0e7c0edb463ba.tar.xz dnsfunnel-fab29b78d87d0114ecbdfbcac3b0e7c0edb463ba.zip |
Initial commit
Diffstat (limited to 'src')
-rw-r--r-- | src/dnsfunnel/deps-exe/dnsfunnel-daemon | 1 | ||||
-rw-r--r-- | src/dnsfunnel/deps-exe/dnsfunnel-translate | 1 | ||||
-rw-r--r-- | src/dnsfunnel/deps-exe/dnsfunneld | 4 | ||||
-rw-r--r-- | src/dnsfunnel/dnsfunnel-daemon.c | 150 | ||||
-rw-r--r-- | src/dnsfunnel/dnsfunnel-translate.c | 93 | ||||
-rw-r--r-- | src/dnsfunnel/dnsfunneld.c | 305 | ||||
-rw-r--r-- | src/dnsfunnel/dnsfunneld.h | 44 | ||||
-rw-r--r-- | src/dnsfunnel/dnsfunneld_answer.c | 132 | ||||
-rw-r--r-- | src/dnsfunnel/dnsfunneld_process.c | 136 |
9 files changed, 866 insertions, 0 deletions
diff --git a/src/dnsfunnel/deps-exe/dnsfunnel-daemon b/src/dnsfunnel/deps-exe/dnsfunnel-daemon new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/dnsfunnel/deps-exe/dnsfunnel-daemon @@ -0,0 +1 @@ +-lskarnet diff --git a/src/dnsfunnel/deps-exe/dnsfunnel-translate b/src/dnsfunnel/deps-exe/dnsfunnel-translate new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/dnsfunnel/deps-exe/dnsfunnel-translate @@ -0,0 +1 @@ +-lskarnet diff --git a/src/dnsfunnel/deps-exe/dnsfunneld b/src/dnsfunnel/deps-exe/dnsfunneld new file mode 100644 index 0000000..90302a1 --- /dev/null +++ b/src/dnsfunnel/deps-exe/dnsfunneld @@ -0,0 +1,4 @@ +dnsfunneld_answer.o +dnsfunneld_process.o +-ls6dns +-lskarnet diff --git a/src/dnsfunnel/dnsfunnel-daemon.c b/src/dnsfunnel/dnsfunnel-daemon.c new file mode 100644 index 0000000..1df6a38 --- /dev/null +++ b/src/dnsfunnel/dnsfunnel-daemon.c @@ -0,0 +1,150 @@ +/* ISC license. */ + +#include <skalibs/sysdeps.h> + +#ifndef SKALIBS_HASCHROOT +# error "this program can only be built on systems that provide a chroot() function" +#endif + +#include <skalibs/nonposix.h> /* chroot */ +#include <stdint.h> +#include <unistd.h> +#include <sys/types.h> +#include <stdlib.h> + +#include <skalibs/uint16.h> +#include <skalibs/types.h> +#include <skalibs/fmtscan.h> +#include <skalibs/sgetopt.h> +#include <skalibs/strerr2.h> +#include <skalibs/djbunix.h> +#include <skalibs/socket.h> +#include <skalibs/exec.h> + +#include <dnsfunnel/config.h> + +#define USAGE "dnsfunnel-daemon [ -v verbosity ] [ -d notif ] [ -U | -u uid -g gid ] [ -i ip:port ] [ -R root ] [ -b bufsize ] [ -f cachelist ] [ -T | -t ] [ -N | -n ] " +#define dieusage() strerr_dieusage(100, USAGE) + +int main (int argc, char const *const *argv) +{ + int notif = 0 ; + unsigned int verbosity = 1 ; + unsigned int bufsize = 131072 ; + int flagU = 0 ; + uid_t uid = -1 ; + gid_t gid = -1 ; + char const *ipport = "127.0.0.1:53" ; + char const *newroot = 0 ; + char const *cachelist = DNSFUNNEL_DEFAULT_CACHELIST ; + uint32_t ops = 0 ; + PROG = "dnsfunnel-daemon" ; + { + subgetopt_t l = SUBGETOPT_ZERO ; + for (;;) + { + int opt = subgetopt_r(argc, argv, "v:d:Uu:g:i:R:b:f:TtNn", &l) ; + if (opt == -1) break ; + switch (opt) + { + case 'v' : if (!uint0_scan(l.arg, &verbosity)) dieusage() ; break ; + case 'd' : if (!uint0_scan(l.arg, (unsigned int *)¬if)) dieusage() ; break ; + case 'U' : flagU = 1 ; break ; + case 'u' : if (!uid0_scan(l.arg, &uid)) dieusage() ; break ; + case 'g' : if (!gid0_scan(l.arg, &gid)) dieusage() ; break ; + case 'i' : ipport = l.arg ; break ; + case 'R' : newroot = l.arg ; break ; + case 'b' : if (!uint0_scan(l.arg, &bufsize)) dieusage() ; break ; + case 'f' : cachelist = l.arg ; break ; + case 'T' : ops &= ~1 ; break ; + case 't' : ops |= 1 ; break ; + case 'N' : ops &= ~2 ; break ; + case 'n' : ops |= 2 ; break ; + default : dieusage() ; + } + } + argc -= l.ind ; argv += l.ind ; + } + + { + int fd ; + char ip[4] ; + uint16_t port ; + size_t pos = ip4_scan(ipport, ip) ; + if (!pos) dieusage() ; + if (ipport[pos] != ':') dieusage() ; + if (!uint160_scan(ipport + pos + 1, &port)) dieusage() ; + fd = socket_udp4() ; + if (fd < 0) strerr_diefu1sys(111, "create UDP socket") ; + if (socket_bind4_reuse(fd, ip, port) < 0) + { + char fmti[IP4_FMT] ; + char fmtp[UINT16_FMT] ; + fmti[ip4_fmt(fmti, ip)] = 0 ; + fmtp[uint16_fmt(fmtp, port)] = 0 ; + strerr_diefu4sys(111, "bind on ip ", fmti, " port ", fmtp) ; + } + if (bufsize) socket_tryreservein(fd, bufsize) ; + if (fd_move(0, fd) < 0) + strerr_diefu1sys(111, "move file descriptors") ; + } + + if (newroot) + { + if (chdir(newroot) < 0 || chroot(".") < 0) + strerr_diefu2sys(111, "chroot to ", newroot) ; + } + + if (flagU) + { + char const *x = getenv("UID") ; + if (x && !uid0_scan(x, &uid)) + strerr_dieinvalid(100, "UID") ; + x = getenv("GID") ; + if (x && !gid0_scan(x, &gid)) + strerr_dieinvalid(100, "GID") ; + } + if (gid != (gid_t)-1 && setgid(gid) < 0) + { + char fmt[GID_FMT] ; + fmt[gid_fmt(fmt, gid)] = 0 ; + strerr_diefu2sys(111, "setgid to ", fmt) ; + } + if (uid != (uid_t)-1 && setuid(uid) < 0) + { + char fmt[UID_FMT] ; + fmt[uid_fmt(fmt, uid)] = 0 ; + strerr_diefu2sys(111, "setuid to ", fmt) ; + } + + { + char const *newargv[10] = { "dnsfunneld" } ; + char const *newenvp[1] = { 0 } ; + unsigned int m = 1 ; + char fmtv[UINT_FMT] ; + char fmtn[UINT_FMT] ; + char fmto[UINT_FMT] ; + if (verbosity != 1) + { + fmtv[uint_fmt(fmtv, verbosity)] = 0 ; + newargv[m++] = "-v" ; + newargv[m++] = fmtv ; + } + if (notif) + { + fmtn[uint_fmt(fmtn, notif)] = 0 ; + newargv[m++] = "-d" ; + newargv[m++] = fmtn ; + } + if (ops) + { + fmto[uint_fmt(fmto, ops)] = 0 ; + newargv[m++] = "-o" ; + newargv[m++] = fmto ; + } + newargv[m++] = "--" ; + newargv[m++] = cachelist ; + newargv[m++] = 0 ; + xexec_ae(DNSFUNNEL_BINPREFIX "dnsfunneld", newargv, newenvp) ; + } +} diff --git a/src/dnsfunnel/dnsfunnel-translate.c b/src/dnsfunnel/dnsfunnel-translate.c new file mode 100644 index 0000000..70610b8 --- /dev/null +++ b/src/dnsfunnel/dnsfunnel-translate.c @@ -0,0 +1,93 @@ +/* ISC license. */ + +#include <string.h> + +#include <skalibs/bytestr.h> +#include <skalibs/sgetopt.h> +#include <skalibs/buffer.h> +#include <skalibs/fmtscan.h> +#include <skalibs/strerr2.h> +#include <skalibs/djbunix.h> +#include <skalibs/ip46.h> + +#include <s6-dns/s6dns-constants.h> + +#include <dnsfunnel/config.h> + +#define USAGE "dnsfunnel-translate [ -i resolvconf ] [ -o cachelist ] [ -x ignoredip ]" +#define dieusage() strerr_dieusage(100, USAGE) + + +static size_t parse_nameservers (ip46_t *list, char const *file, char const *ignore) +{ + static char const zero[SKALIBS_IP_SIZE] = { 0 } ; + char buf[4096] ; + size_t n = 0, i = 0 ; + ssize_t len = openreadnclose(file, buf, 4095) ; + if (len < 0) strerr_diefu2sys(111, "open ", file) ; + buf[len++] = '\n' ; + while ((i < len) && (n < S6DNS_MAX_SERVERS)) + { + size_t j = byte_chr(buf + i, len - i, '\n') ; + if ((i + j < len) && (j > 13U) && !memcmp("nameserver", buf + i, 10)) + { + size_t k = 0 ; + while ((buf[i+10+k] == ' ') || (buf[i+10+k] == '\t')) k++ ; + if (k && ip46_scan(buf+i+10+k, list + n) + && memcmp(list[n].ip, zero, SKALIBS_IP_SIZE) + && (ip46_is6(list + n) || memcmp(list[n].ip, ignore, 4)) + ) n++ ; + } + i += j + 1 ; + } + return n ; +} + + +int main (int argc, char const *const *argv) +{ + ip46_t list[S6DNS_MAX_SERVERS] = { IP46_ZERO } ; + char const *resolvconf = "/etc/resolv.conf" ; + char const *cachelist = DNSFUNNEL_DEFAULT_CACHELIST ; + char ignore[4] = "\177\0\0\1" ; + size_t n ; + PROG = "dnsfunnel-translate" ; + { + subgetopt_t l = SUBGETOPT_ZERO ; + for (;;) + { + int opt = subgetopt_r(argc, argv, "i:o:x:", &l) ; + if (opt == -1) break ; + switch (opt) + { + case 'i' : resolvconf = l.arg ; break ; + case 'o' : cachelist = l.arg ; break ; + case 'x' : if (!ip4_scan(l.arg, ignore)) dieusage() ; break ; + default : dieusage() ; + } + } + argc -= l.ind ; argv += l.ind ; + } + + n = parse_nameservers(list, resolvconf, ignore) ; + if (!n) strerr_dief2x(1, "no suitable cache address in ", resolvconf) ; + + { + char buf[4096] ; + buffer b ; + int fd = openc_trunc(cachelist) ; + if (fd < 0) strerr_diefu2sys(111, "open ", cachelist) ; + buffer_init(&b, &buffer_write, fd, buf, 4096) ; + for (size_t i = 0 ; i < n ; i++) + { + char fmt[IP46_FMT] ; + size_t len = ip46_fmt(fmt, list + i) ; + fmt[len++] = '\n' ; + if (buffer_put(&b, fmt, len) < len) + strerr_diefu2sys(111, "write to ", cachelist) ; + } + if (!buffer_flush(&b)) + strerr_diefu2sys(111, "write to ", cachelist) ; + } + return 0 ; +} diff --git a/src/dnsfunnel/dnsfunneld.c b/src/dnsfunnel/dnsfunneld.c new file mode 100644 index 0000000..bd4dc89 --- /dev/null +++ b/src/dnsfunnel/dnsfunneld.c @@ -0,0 +1,305 @@ +/* ISC license. */ + +#include <stdint.h> +#include <string.h> +#include <signal.h> +#include <fcntl.h> +#include <errno.h> + +#include <skalibs/uint32.h> +#include <skalibs/types.h> +#include <skalibs/allreadwrite.h> +#include <skalibs/error.h> +#include <skalibs/bitarray.h> +#include <skalibs/strerr2.h> +#include <skalibs/sgetopt.h> +#include <skalibs/stralloc.h> +#include <skalibs/sig.h> +#include <skalibs/socket.h> +#include <skalibs/tai.h> +#include <skalibs/djbunix.h> +#include <skalibs/iopause.h> +#include <skalibs/selfpipe.h> +#include <skalibs/gensetdyn.h> + +#include <s6-dns/s6dns.h> + +#include "dnsfunneld.h" + +#define USAGE "dnsfunneld [ -v verbosity ] [ -d notif ] [ -o operations ] cachelist" +#define dieusage() strerr_dieusage(100, USAGE) + +#define DNSFUNNELD_INPUT_MAX 64 + +unsigned int verbosity = 1 ; +static tain_t globaltto = TAIN_INFINITE_RELATIVE ; +static int cont = 1 ; +static char const *cachelistfile = 0 ; +static s6dns_ip46list_t cachelist ; +static uint32_t ops = 0 ; + +static inline void X (void) +{ + strerr_dief1x(101, "internal inconsistency. Please submit a bug-report.") ; +} + +static inline void s6dns_ip46list_copy (s6dns_ip46list_t *dst, ip46full_t const *src, size_t n) +{ + if (n >= S6DNS_MAX_SERVERS) n = S6DNS_MAX_SERVERS - 1 ; + for (size_t i = 0 ; i < n ; i++) + { + memcpy(dst->ip + i * SKALIBS_IP_SIZE, src[i].ip, SKALIBS_IP_SIZE) ; +#ifdef SKALIBS_IPV6_ENABLED + bitarray_poke(dst->is6, i, ip46_is6(src + i)) ; +#endif + } + memset(dst->ip + n * SKALIBS_IP_SIZE, 0, SKALIBS_IP_SIZE) ; +} + +static int load_cachelist (int initial) +{ + char buf[4096] ; + ip46full_t list[S6DNS_MAX_SERVERS] ; + size_t n ; + ssize_t r = openreadnclose_nb(cachelistfile, buf, 4095) ; + if (r < 0) return -1 ; + buf[r++] = 0 ; + ip46_scanlist(list, S6DNS_MAX_SERVERS, buf, &n) ; + if (!n) return -2 ; + s6dns_ip46list_copy(&cachelist, list, n) ; + return 0 ; +} + +static inline void handle_signals (void) +{ + for (;;) + { + switch (selfpipe_read()) + { + case -1 : strerr_diefu1sys(111, "read from selfpipe") ; + case 0 : return ; + case SIGTERM : cont = 0 ; break ; + case SIGHUP : + { + switch (load_cachelist(0)) + { + case 0 : query_process_reload() ; break ; + case -1 : strerr_warnwu2sys("read ", cachelistfile) ; break ; + case -2 : strerr_warnw2x("invalid cache list in ", cachelistfile) ; break ; + default : X() ; + } + break ; + } + default : X() ; + } + } +} + +static dfquery_t const dfquery_zero = DFQUERY_ZERO ; +static gensetdyn queries = GENSETDYN_INIT(dfquery_t, 16, 3, 8) ; +static uint32_t sentinel ; +#define inflight (gensetdyn_n(&queries) - 1) +#define QUERY(i) GENSETDYN_P(dfquery_t, &queries, i) + +void query_new (s6dns_domain_t const *d, uint16_t qtype, uint16_t id, uint32_t ip, uint16_t port, uint32_t procid) +{ + dfquery_t q = + { + .next = QUERY(sentinel)->next, + .xindex = 0, + .procid = procid, + .ip = ip, + .port = port, + .id = id, + .dt = S6DNS_ENGINE_ZERO + } ; + tain_t deadline ; + uint32_t i ; + if (!gensetdyn_new(&queries, &i)) + strerr_diefu1sys(111, "create new query") ; + tain_add_g(&deadline, &globaltto) ; + if (!s6dns_engine_init_g(&q.dt, &cachelist, S6DNS_O_RECURSIVE, d->s, d->len, qtype, &deadline)) + strerr_diefu1sys(111, "start new query") ; + *QUERY(i) = q ; + QUERY(sentinel)->next = i ; +} + +static inline void sanitize_and_new (char const *buf, unsigned int len, char const *ippack, uint16_t port) +{ + s6dns_domain_t d ; + uint32_t ip ; + unsigned int pos ; + s6dns_message_header_t hdr ; + s6dns_message_counts_t counts ; + uint16_t qtype ; + if (!s6dns_message_parse_init(&hdr, &counts, buf, len, &pos) + || hdr.qr + || hdr.opcode + || !hdr.rd + || hdr.counts.qd != 1 || hdr.counts.an || hdr.counts.ns || hdr.counts.nr + || !s6dns_message_parse_question(&counts, &d, &qtype, buf, len, &pos)) + return ; + uint32_unpack_big(ippack, &ip) ; + if (ops) query_process_question(ops, &d, qtype, hdr.id, ip, port) ; + else query_new(&d, qtype, hdr.id, ip, port, 0) ; +} + +int main (int argc, char const *const *argv) +{ + int spfd = -1 ; + int notif = -1 ; + PROG = "dnsfunneld" ; + { + subgetopt_t l = SUBGETOPT_ZERO ; + for (;;) + { + int opt = subgetopt_r(argc, argv, "v:d:o:", &l) ; + if (opt == -1) break ; + switch (opt) + { + case 'v' : if (!uint0_scan(l.arg, &verbosity)) dieusage() ; break ; + case 'd' : if (!uint0_scan(l.arg, (unsigned int *)¬if)) dieusage() ; break ; + case 'o' : if (!uint320_scan(l.arg, &ops)) dieusage() ; break ; + default : dieusage() ; + } + } + argc -= l.ind ; argv += l.ind ; + if (!argc) dieusage() ; + } + if (notif >= 0) + { + if (notif < 3) strerr_dief1x(100, "notification fd must be 3 or more") ; + if (fcntl(notif, F_GETFD) < 0) strerr_dief1sys(100, "invalid notification fd") ; + } + + if (ndelay_on(0) < 0) strerr_diefu1sys(111, "turn stdin non-blocking") ; + if (sig_ignore(SIGPIPE) < 0) strerr_diefu1sys(111, "ignore SIGPIPE") ; + cachelistfile = argv[0] ; + switch (load_cachelist(1)) + { + case 0 : break ; + case -1 : strerr_diefu2sys(111, "read ", cachelistfile) ; + case -2 : strerr_dief2x(100, "invalid cache list in ", cachelistfile) ; + default : X() ; + } + if (!s6dns_init()) strerr_diefu1sys(111, "s6dns_init") ; + spfd = selfpipe_init() ; + if (spfd < 0) strerr_diefu1sys(111, "init selfpipe") ; + { + sigset_t set ; + sigemptyset(&set) ; + sigaddset(&set, SIGTERM) ; + sigaddset(&set, SIGHUP) ; + if (selfpipe_trapset(&set) < 0) strerr_diefu1sys(111, "trap signals") ; + } + if (!gensetdyn_new(&queries, &sentinel)) + strerr_diefu1sys(111, "initialize query structure") ; + *QUERY(sentinel) = dfquery_zero ; + QUERY(sentinel)->next = sentinel ; + if (!query_process_init()) + strerr_diefu1sys(111, "initialize query processing") ; + tain_now_set_stopwatch_g() ; + + if (notif >= 0) + { + fd_write(notif, "\n", 1) ; + fd_close(notif) ; + } + + for (;;) + { + tain_t deadline = TAIN_INFINITE ; + uint32_t i = QUERY(sentinel)->next ; + uint32_t j = 2 ; + int r ; + iopause_fd x[2 + inflight] ; + + x[0].fd = spfd ; + x[0].events = IOPAUSE_READ ; + x[1].fd = 0 ; + x[1].events = (cont ? IOPAUSE_READ : 0) | (dfanswer_pending() ? IOPAUSE_WRITE : 0) ; + if (!x[1].events && !inflight) break ; + + while (i != sentinel) + { + dfquery_t *q = QUERY(i) ; + s6dns_engine_nextdeadline(&q->dt, &deadline) ; + x[j].fd = q->dt.fd ; + x[j].events = 0 ; + if (s6dns_engine_isreadable(&q->dt)) x[j].events |= IOPAUSE_READ ; + if (s6dns_engine_iswritable(&q->dt)) x[j].events |= IOPAUSE_WRITE ; + q->xindex = j++ ; + i = q->next ; + } + + r = iopause_g(x, j, &deadline) ; + if (r < 0) strerr_diefu1sys(111, "iopause") ; + + if (!r) + { + i = QUERY(sentinel)->next ; + j = sentinel ; + while (i != sentinel) + { + dfquery_t *q = QUERY(i) ; + uint32_t k = q->next ; + if (s6dns_engine_timeout_g(&q->dt)) + { + query_process_response_failure(ops, q) ; + QUERY(j)->next = k ; + stralloc_free(&q->dt.sa) ; + gensetdyn_delete(&queries, i) ; + } + else j = i ; + i = k ; + } + continue ; + } + + if (x[0].revents & IOPAUSE_READ) handle_signals() ; + + if (x[1].revents & IOPAUSE_WRITE) + { + int r = dfanswer_flush() ; + if (r < 0) strerr_diefu1sys(111, "send DNS answer to client") ; + } + + i = QUERY(sentinel)->next ; + j = sentinel ; + while (i != sentinel) + { + dfquery_t *q = QUERY(i) ; + uint32_t k = q->next ; + int r = s6dns_engine_event_g(&q->dt) ; + if (r) + { + if (r > 0) query_process_response_success(ops, q) ; + else query_process_response_failure(ops, q) ; + QUERY(j)->next = k ; + if (r > 0) s6dns_engine_free(&q->dt) ; + else stralloc_free(&q->dt.sa) ; + gensetdyn_delete(&queries, i) ; + } + else j = i ; + i = k ; + } + + if (x[0].revents & IOPAUSE_READ) + { + uint32_t n = DNSFUNNELD_INPUT_MAX ; + while (n--) + { + char ip[4] ; + uint16_t port ; + char buf[512] ; + ssize_t r = socket_recv4(0, buf, 512, ip, &port) ; + if (r < 0) + if (error_isagain(errno)) break ; + else strerr_diefu1sys(111, "socket_recv") ; + else if (!r) continue ; + else sanitize_and_new(buf, r, ip, port) ; + } + } + } + return 0 ; +} diff --git a/src/dnsfunnel/dnsfunneld.h b/src/dnsfunnel/dnsfunneld.h new file mode 100644 index 0000000..9fc0bbf --- /dev/null +++ b/src/dnsfunnel/dnsfunneld.h @@ -0,0 +1,44 @@ +/* ISC license. */ + +#ifndef DNSFUNNELD_H +#define DNSFUNNELD_H + +#include <stddef.h> +#include <stdint.h> + +#include <skalibs/gensetdyn.h> + +#include <s6-dns/s6dns-domain.h> +#include <s6-dns/s6dns-engine.h> + +typedef struct dfquery_s dfquery_t, *dfquery_t_ref ; +struct dfquery_s +{ + uint32_t next ; + uint32_t xindex ; + uint32_t procid ; + uint32_t ip ; + uint16_t port ; + uint16_t id ; + s6dns_engine_t dt ; +} ; +#define DFQUERY_ZERO { .next = 0, .xindex = 0, .procid = 0, .ip = 0, .port = 0, .id = 0, .dt = S6DNS_ENGINE_ZERO } + +extern unsigned int verbosity ; +extern size_t dfanswer_pending (void) ; +extern int dfanswer_flush (void) ; +extern void dfanswer_fail (dfquery_t const *) ; +extern void dfanswer_nxdomain (dfquery_t const *) ; +extern void dfanswer_nodata (dfquery_t const *) ; +extern void dfanswer_pass (dfquery_t const *, char *, unsigned int) ; + + +extern void query_new (s6dns_domain_t const *, uint16_t, uint16_t, uint32_t, uint16_t, uint32_t) ; + +extern int query_process_init (void) ; +extern void query_process_reload (void) ; +extern void query_process_question (uint32_t, s6dns_domain_t const *, uint16_t, uint16_t, uint32_t, uint16_t) ; +extern void query_process_response_failure (uint32_t, dfquery_t const *) ; +extern void query_process_response_success (uint32_t, dfquery_t const *) ; + +#endif diff --git a/src/dnsfunnel/dnsfunneld_answer.c b/src/dnsfunnel/dnsfunneld_answer.c new file mode 100644 index 0000000..a6ee526 --- /dev/null +++ b/src/dnsfunnel/dnsfunneld_answer.c @@ -0,0 +1,132 @@ +/* ISC license. */ + +#include <errno.h> +#include <stdint.h> +#include <string.h> + +#include <skalibs/uint16.h> +#include <skalibs/uint32.h> +#include <skalibs/error.h> +#include <skalibs/strerr2.h> +#include <skalibs/genqdyn.h> +#include <skalibs/socket.h> + +#include <s6-dns/s6dns-message.h> + +#include "dnsfunneld.h" + +typedef struct dfanswer_s dfanswer_t, *dfanswer_t_ref ; +struct dfanswer_s +{ + char buf[512] ; + char ip[4] ; + uint16_t len ; + uint16_t port ; +} ; +#define DFANSWER_ZERO { .buf = { 0 }, .ip = "\0\0\0", .len = 0, .port = 0 } + +static genqdyn dfanswers = GENQDYN_INIT(dfanswer_t, 1, 8) ; + +size_t dfanswer_pending () +{ + return genqdyn_n(&dfanswers) ; +} + +static void dfanswer_push (char const *s, size_t len, uint32_t ip, uint16_t port) +{ + if (len > 510) + { + if (verbosity) + strerr_warnw1x("answer too big, dropping - enable truncation to avoid this") ; + } + else + { + dfanswer_t ans = { .len = len, .port = port } ; + uint16_pack_big(ans.buf, ans.len) ; + memcpy(ans.buf, s+2, len) ; + uint32_pack_big(ans.ip, ip) ; + if (!genqdyn_push(&dfanswers, &ans)) + strerr_diefu1sys(111, "queue answer to client") ; + } +} + +int dfanswer_flush () +{ + while (dfanswer_pending()) + { + dfanswer_t *ans = GENQDYN_PEEK(dfanswer_t, &dfanswers) ; + if (socket_send4(0, ans->buf, ans->len, ans->ip, ans->port) < 0) + return error_isagain(errno) ? (errno = 0, 0) : -1 ; + genqdyn_pop(&dfanswers) ; + } + return 1 ; +} + +void dfanswer_fail (dfquery_t const *q) +{ + char buf[510] ; + uint16_t len ; + s6dns_message_header_t hdr ; + uint16_unpack_big(q->dt.sa.s, &len) ; + memcpy(buf, q->dt.sa.s + 2, len) ; + s6dns_message_header_unpack(buf, &hdr) ; + hdr.id = q->id ; + hdr.qr = 1 ; + hdr.aa = 0 ; + hdr.tc = 0 ; + hdr.rd = 1 ; + hdr.ra = 1 ; + hdr.z = 0 ; + hdr.rcode = 2 ; /* servfail */ + s6dns_message_header_pack(buf, &hdr) ; + dfanswer_push(buf, len, q->ip, q->port) ; +} + +void dfanswer_nxdomain (dfquery_t const *q) +{ + char buf[510] ; + uint16_t len ; + s6dns_message_header_t hdr ; + uint16_unpack_big(q->dt.sa.s, &len) ; + memcpy(buf, q->dt.sa.s + 2, len) ; + s6dns_message_header_unpack(buf, &hdr) ; + hdr.id = q->id ; + hdr.qr = 1 ; + hdr.aa = 1 ; + hdr.tc = 0 ; + hdr.rd = 1 ; + hdr.ra = 1 ; + hdr.z = 0 ; + hdr.rcode = 3 ; /* nxdomain */ + s6dns_message_header_pack(buf, &hdr) ; + dfanswer_push(buf, len, q->ip, q->port) ; +} + +void dfanswer_nodata (dfquery_t const *q) +{ + char buf[510] ; + uint16_t len ; + s6dns_message_header_t hdr ; + uint16_unpack_big(q->dt.sa.s, &len) ; + memcpy(buf, q->dt.sa.s + 2, len) ; + s6dns_message_header_unpack(buf, &hdr) ; + hdr.id = q->id ; + hdr.qr = 1 ; + hdr.aa = 1 ; + hdr.tc = 0 ; + hdr.rd = 1 ; + hdr.ra = 1 ; + hdr.z = 0 ; + hdr.rcode = 0 ; /* success */ + s6dns_message_header_pack(buf, &hdr) ; + dfanswer_push(buf, len, q->ip, q->port) ; +} + +void dfanswer_pass (dfquery_t const *q, char *s, unsigned int len) +{ + s6dns_message_header_t hdr ; + s6dns_message_header_unpack(s, &hdr) ; + hdr.id = q->id ; + s6dns_message_header_pack(s, &hdr) ; + dfanswer_push(s, len, q->ip, q->port) ; +} diff --git a/src/dnsfunnel/dnsfunneld_process.c b/src/dnsfunnel/dnsfunneld_process.c new file mode 100644 index 0000000..9d90289 --- /dev/null +++ b/src/dnsfunnel/dnsfunneld_process.c @@ -0,0 +1,136 @@ +/* ISC license. */ + +#include <stdint.h> + +#include <skalibs/uint16.h> +#include <skalibs/strerr2.h> +#include <skalibs/gensetdyn.h> + +#include <s6-dns/s6dns-constants.h> +#include <s6-dns/s6dns-domain.h> +#include <s6-dns/s6dns-message.h> +#include <s6-dns/s6dns-engine.h> + +#include "dnsfunneld.h" + +static gensetdyn rinfo = GENSETDYN_INIT(uint8_t, 16, 3, 8) ; +#define RINFO(i) GENSETDYN_P(uint8_t, &rinfo, i) + +int query_process_init () +{ + return 1 ; +} + +void query_process_reload () +{ +} + +void query_process_question (uint32_t ops, s6dns_domain_t const *d, uint16_t qtype, uint16_t id, uint32_t ip, uint16_t port) +{ + if (ops & 2 && (qtype == S6DNS_T_A || qtype == S6DNS_T_AAAA)) + { + uint32_t i ; + if (!gensetdyn_new(&rinfo, &i)) strerr_diefu1sys(111, "process query") ; + *RINFO(i) = (qtype == S6DNS_T_AAAA) << 7 ; + query_new(d, S6DNS_T_A, id, ip, port, i+1) ; + query_new(d, S6DNS_T_AAAA, id, ip, port, i+1) ; + } + else query_new(d, qtype, id, ip, port, 0) ; +} + +static inline unsigned int truncate_packet (char *s, unsigned int olen) +{ + s6dns_message_header_t hdr ; + s6dns_message_counts_t counts ; + unsigned int section ; + unsigned int pos ; + if (!s6dns_message_parse_init(&hdr, &counts, s, olen, &pos)) return 0 ; + if (hdr.rcode) return 0 ; + section = s6dns_message_parse_skipqd(&counts, s, olen, &pos) ; + while (section) + { + s6dns_message_rr_t rr ; + s6dns_message_counts_t newcounts = counts ; + unsigned int tmp = pos ; + if (!s6dns_message_parse_getrr(&rr, s, olen, &tmp)) return 0 ; + section = s6dns_message_parse_next(&newcounts, &rr, s, olen, &tmp) ; + if (tmp > 510) + { + hdr.counts.qd -= counts.qd ; + hdr.counts.an -= counts.an ; + hdr.counts.ns -= counts.ns ; + hdr.counts.nr -= counts.nr ; + s6dns_message_header_pack(s, &hdr) ; + return pos ; + } + pos = tmp ; + counts = newcounts ; + } + return olen ; +} + +static inline uint16_t extract_qtype (dfquery_t const *q) +{ + s6dns_domain_t name ; + uint16_t qtype ; + uint16_t len ; + s6dns_message_header_t hdr ; + s6dns_message_counts_t counts ; + unsigned int pos ; + uint16_unpack_big(q->dt.sa.s, &len) ; + if (!s6dns_message_parse_init(&hdr, &counts, q->dt.sa.s + 2, len, &pos)) return 0 ; + if (!s6dns_message_parse_question(&counts, &name, &qtype, q->dt.sa.s + 2, len, &pos)) return 0 ; + return qtype ; +} + +static int isnxdomain (dfquery_t const *q) +{ + s6dns_message_header_t hdr ; + s6dns_message_counts_t counts ; + unsigned int pos ; + if (!s6dns_message_parse_init(&hdr, &counts, s6dns_engine_packet(&q->dt), s6dns_engine_packetlen(&q->dt), &pos)) return 0 ; + return hdr.rcode == 3 ; +} + +static int input_event (dfquery_t const *q, unsigned int ev) +{ + static uint8_t const table[5][6] = + { + { 0x11, 0x03, 0x81, 0x02, 0x02, 0x04 }, + { 0x06, 0x06, 0x06, 0x05, 0x05, 0x05 }, + { 0x15, 0x25, 0x85, 0x06, 0x06, 0x06 }, + { 0x06, 0x06, 0x06, 0x25, 0x25, 0x45 }, + { 0x15, 0x45, 0x85, 0x06, 0x06, 0x06 } + } ; + uint8_t b = *RINFO(q->procid - 1) ; + uint8_t isaux = 3 * (b >> 7 != (extract_qtype(q) == S6DNS_T_AAAA)) ; + uint8_t state = (b >> isaux) & 7 ; + uint8_t c = table[state][ev + isaux] ; + state = c & 7 ; + *RINFO(q->procid - 1) = (b & ~(7 << isaux)) | (state << isaux) ; + if (c & 0x10) dfanswer_fail(q) ; + if (c & 0x20) dfanswer_nxdomain(q) ; + if (c & 0x40) dfanswer_nodata(q) ; + if (c & 0x80) dfanswer_pass(q, s6dns_engine_packet(&q->dt), s6dns_engine_packetlen(&q->dt)) ; + if (state >= 6) strerr_dief1x(101, "problem in main/aux transition table; please submit a bug-report.") ; + if (state == 5) gensetdyn_delete(&rinfo, q->procid - 1) ; + return !!(c & 0xf0) ; +} + +void query_process_response_failure (uint32_t ops, dfquery_t const *q) +{ + if (ops & 2 && q->procid && input_event(q, 0)) return ; + else dfanswer_fail(q) ; +} + +void query_process_response_success (uint32_t ops, dfquery_t const *q) +{ + if (ops & 2 && q->procid && input_event(q, 1 + !isnxdomain(q))) return ; + if (ops & 1 && s6dns_engine_packetlen(&q->dt) > 510) + { + unsigned int len = truncate_packet(s6dns_engine_packet(&q->dt), s6dns_engine_packetlen(&q->dt)) ; + if (!len) dfanswer_fail(q) ; + else dfanswer_pass(q, s6dns_engine_packet(&q->dt), len) ; + } + else dfanswer_pass(q, s6dns_engine_packet(&q->dt), s6dns_engine_packetlen(&q->dt)) ; +} |