summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/dnsfunnel/deps-exe/dnsfunnel-daemon1
-rw-r--r--src/dnsfunnel/deps-exe/dnsfunnel-translate1
-rw-r--r--src/dnsfunnel/deps-exe/dnsfunneld4
-rw-r--r--src/dnsfunnel/dnsfunnel-daemon.c150
-rw-r--r--src/dnsfunnel/dnsfunnel-translate.c93
-rw-r--r--src/dnsfunnel/dnsfunneld.c305
-rw-r--r--src/dnsfunnel/dnsfunneld.h44
-rw-r--r--src/dnsfunnel/dnsfunneld_answer.c132
-rw-r--r--src/dnsfunnel/dnsfunneld_process.c136
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 *)&notif)) 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 *)&notif)) 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)) ;
+}