]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
dnsmessage api for dynlib
authorDavid Hinkle <david.hinkle@securly.com>
Fri, 12 Mar 2021 23:04:33 +0000 (23:04 +0000)
committerDavid Hinkle <david.hinkle@securly.com>
Tue, 16 Mar 2021 04:13:15 +0000 (04:13 +0000)
Makefile.in
configure
dynlibmod/dnsmessage.c [new file with mode: 0644]
dynlibmod/dnsmessage.h [new file with mode: 0644]
dynlibmod/examples/Makefile [new file with mode: 0644]
dynlibmod/examples/resgen.c [new file with mode: 0644]
dynlibmod/test-resgen.conf [new file with mode: 0644]

index b640af006ba4ac23c58ea3a1e65f896ff5654f29..7f07dca5134a4057d4a74efecb631531c1a727de 100644 (file)
@@ -88,7 +88,7 @@ LINTFLAGS+="-D__uint16_t=uint16_t" "-DEVP_PKEY_ASN1_METHOD=int" "-D_RuneLocale=i
 
 INSTALL=$(SHELL) $(srcdir)/install-sh
 
-DYNLIBMOD_SRC=dynlibmod/dynlibmod.c
+DYNLIBMOD_SRC=dynlibmod/dynlibmod.c dynlibmod/dnsmessage.c
 DYNLIBMOD_OBJ=@DYNLIBMOD_OBJ@
 DYNLIBMOD_HEADER=@DYNLIBMOD_HEADER@
 DYNLIBMOD_EXTRALIBS=@DYNLIBMOD_EXTRALIBS@
@@ -431,6 +431,7 @@ dtstream.lo dtstream.o: $(srcdir)/dnstap/dtstream.c config.h $(srcdir)/dnstap/dt
 dnstap_fstrm.lo dnstap_fstrm.o: $(srcdir)/dnstap/dnstap_fstrm.c config.h $(srcdir)/dnstap/dnstap_fstrm.h
 unbound-dnstap-socket.lo unbound-dnstap-socket.o: $(srcdir)/dnstap/unbound-dnstap-socket.c config.h $(srcdir)/dnstap/dtstream.h
 dynlibmod.lo dynlibdmod.o: $(srcdir)/dynlibmod/dynlibmod.c config.h $(srcdir)/dynlibmod/dynlibmod.h
+dnsmessage.lo dynlibdmod.o: $(srcdir)/dynlibmod/dnsmessage.c config.h $(srcdir)/dynlibmod/dnsmessage.h
 
 # dnscrypt
 dnscrypt.lo dnscrypt.o: $(srcdir)/dnscrypt/dnscrypt.c config.h \
index b067e48fe96e4bf2971846fca3e711587eae9ebb..674ad9e42d44c6e10b14585f23e89e5eb82b3275 100755 (executable)
--- a/configure
+++ b/configure
@@ -17232,7 +17232,7 @@ $as_echo "#define WITH_DYNLIBMODULE 1" >>confdefs.h
 
   WITH_DYNLIBMODULE=yes
 
-  DYNLIBMOD_OBJ="dynlibmod.lo"
+  DYNLIBMOD_OBJ="dynlibmod.lo dnsmessage.lo"
 
   DYNLIBMOD_HEADER='$(srcdir)/dynlibmod/dynlibmod.h'
 
diff --git a/dynlibmod/dnsmessage.c b/dynlibmod/dnsmessage.c
new file mode 100644 (file)
index 0000000..a8b9e25
--- /dev/null
@@ -0,0 +1,218 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <malloc.h>
+
+#include <config.h>
+#include <util/module.h>
+#include <sldns/parseutil.h>
+#include <dynlibmod/dynlibmod.h>
+
+#include <sldns/sbuffer.h>
+#include <sldns/str2wire.h>
+#include <pythonmod/pythonmod_utils.h>
+#include <services/cache/dns.h>
+
+#include "dnsmessage.h"
+
+struct dynlibmod_section_txt {
+  char *txt;
+  struct dynlibmod_section_txt *next;
+};
+
+struct dynlibmod_dnsmessage {
+  char *rr_name;
+  sldns_rr_type rr_type;
+  sldns_rr_class rr_class;
+  uint32_t default_ttl;
+  uint16_t flags;
+
+  // Question, Answer, Authority, and Additional sections
+  struct dynlibmod_section_txt *sections[4];
+};
+
+struct dynlibmod_dnsmessage *dynlibmod_dnsmessage_new(char *rr_name, sldns_rr_type rr_type, sldns_rr_class rr_class, uint16_t flags, uint32_t default_ttl) {
+    struct dynlibmod_dnsmessage *dns_message = malloc(sizeof(struct dynlibmod_dnsmessage));
+
+    if (!dns_message) {
+        log_err("%s:%d malloc failure allocating dns_message", __FUNCTION__, __LINE__);
+        goto error;
+    }
+
+    memset(dns_message, 0, sizeof(struct dynlibmod_dnsmessage));
+
+    if (rr_name) {
+        dns_message->rr_name = strdup(rr_name);
+
+        if (!dns_message->rr_name) {
+            log_err("%s:%d malloc failure allocating rr_name", __FUNCTION__, __LINE__);
+            goto error;
+        }
+    }
+
+    dns_message->rr_type = rr_type;
+    dns_message->rr_class = rr_class;
+    dns_message->flags = flags;
+    dns_message->default_ttl = default_ttl;
+
+    return(dns_message);
+
+error:
+    if (dns_message)
+        free(rr_name);
+
+    free(dns_message);
+    return NULL;
+}
+
+void dynlibmod_dnsmessage_free(struct dynlibmod_dnsmessage *dnsmessage) {
+    if (!dnsmessage)
+        return;
+
+    free(dnsmessage->rr_name);
+    for (int i = 0; i < 4; i++)
+        for (struct dynlibmod_section_txt *ptr = dnsmessage->sections[i]; ptr;) {
+            free(ptr->txt);
+            struct dynlibmod_section_txt *tmp = ptr;
+            ptr = ptr->next;
+            free(tmp);
+        }
+
+    free(dnsmessage);
+}
+
+void dynlibmod_msg_append(struct dynlibmod_dnsmessage *msg, enum enum_section section, const char *format, ...) {
+    va_list args;
+
+    va_start(args, format);
+    dynlibmod_vmsg_append(msg, section, format, args);
+    va_end( args );
+}
+
+void dynlibmod_vmsg_append(struct dynlibmod_dnsmessage *msg, enum enum_section section, const char *format, va_list args) {
+    struct dynlibmod_section_txt *txt = malloc(sizeof(struct dynlibmod_section_txt));
+
+    if (!txt) {
+        log_err("%s:%d malloc failure allocating section txt structure", __FUNCTION__, __LINE__);
+        goto error;
+    }
+
+    memset(txt, 0, sizeof(struct dynlibmod_section_txt));
+
+    va_list cpy_args;
+    va_copy(cpy_args, args);
+
+    int len = vsnprintf(NULL, 0, format, args);
+    len++;
+    txt->txt = malloc(len);
+
+    if (!txt->txt) {
+        va_end(cpy_args);
+        log_err("%s:%d malloc failure allocating section txt member", __FUNCTION__, __LINE__);
+        goto error;
+    }
+
+    vsnprintf(txt->txt, len, format, cpy_args);
+    va_end(cpy_args);
+
+    if (!msg->sections[section]) {
+        msg->sections[section] = txt;
+        return;
+    }
+
+    struct dynlibmod_section_txt *ptr = NULL;
+    for (ptr = msg->sections[section]; ptr->next; ptr = ptr->next)
+        ;
+
+    ptr->next = txt;
+    return;
+
+error:
+    if (txt)
+        free(txt->txt);
+    free(txt);
+
+    return;
+}
+
+/**
+ * Enumerate a section from a dynlibmod_dnsmessage structure and add all of it's members to the dns message
+ */
+static int _dynlibmod_pushRRList(sldns_buffer* qb, struct dynlibmod_section_txt *txt, uint32_t default_ttl, int qsec, size_t count_offset) {
+    size_t len;
+    char* s;
+
+    for (; txt; txt = txt->next) {
+               s = txt->txt;
+
+        len = sldns_buffer_remaining(qb);
+        if(qsec) {
+            if(sldns_str2wire_rr_question_buf(s, sldns_buffer_current(qb), &len, NULL, NULL, 0, NULL, 0)!= 0)
+                return 0;
+        } else {
+            if(sldns_str2wire_rr_buf(s, sldns_buffer_current(qb), &len, NULL, default_ttl, NULL, 0, NULL, 0) != 0)
+                return 0;
+        }
+        sldns_buffer_skip(qb, len);
+
+        sldns_buffer_write_u16_at(qb, count_offset,
+        sldns_buffer_read_u16_at(qb, count_offset)+1);
+
+    }
+    return 1;
+}
+
+/**
+ * A low level function for preparing a DNS answer by parsing a dynlibmod_dnsmessage.
+ */
+int dynlibmod_set_return_msg(struct module_qstate* qstate, struct dynlibmod_dnsmessage *msg) {
+     sldns_buffer *qb = 0;
+     int res = 1;
+     size_t l;
+
+     if ((qb = sldns_buffer_new(LDNS_RR_BUF_SIZE)) == 0) return 0;
+
+     /* write header */
+     sldns_buffer_write_u16(qb, 0); /* ID */
+     sldns_buffer_write_u16(qb, 0); /* flags */
+     sldns_buffer_write_u16(qb, 1); /* qdcount */
+     sldns_buffer_write_u16(qb, 0); /* ancount */
+     sldns_buffer_write_u16(qb, 0); /* nscount */
+     sldns_buffer_write_u16(qb, 0); /* arcount */
+     if ((msg->flags&PKT_QR)) LDNS_QR_SET(sldns_buffer_begin(qb));
+     if ((msg->flags&PKT_AA)) LDNS_AA_SET(sldns_buffer_begin(qb));
+     if ((msg->flags&PKT_TC)) LDNS_TC_SET(sldns_buffer_begin(qb));
+     if ((msg->flags&PKT_RD)) LDNS_RD_SET(sldns_buffer_begin(qb));
+     if ((msg->flags&PKT_CD)) LDNS_CD_SET(sldns_buffer_begin(qb));
+     if ((msg->flags&PKT_RA)) LDNS_RA_SET(sldns_buffer_begin(qb));
+     if ((msg->flags&PKT_AD)) LDNS_AD_SET(sldns_buffer_begin(qb));
+
+     /* write the query */
+     l = sldns_buffer_remaining(qb);
+     if(sldns_str2wire_dname_buf(msg->rr_name, sldns_buffer_current(qb), &l) != 0) {
+             sldns_buffer_free(qb);
+             return 0;
+     }
+     sldns_buffer_skip(qb, l);
+     if (msg->rr_type == 0) { msg->rr_type = LDNS_RR_TYPE_A; }
+     if (msg->rr_class == 0) { msg->rr_class = LDNS_RR_CLASS_IN; }
+     sldns_buffer_write_u16(qb, msg->rr_type);
+     sldns_buffer_write_u16(qb, msg->rr_class);
+
+     /* write RR sections */
+     if(res && msg->sections[SECTION_QUESTION] && !_dynlibmod_pushRRList(qb, msg->sections[SECTION_QUESTION], msg->default_ttl, 1, LDNS_QDCOUNT_OFF))
+             res = 0;
+     if(res && msg->sections[SECTION_ANSWER] && !_dynlibmod_pushRRList(qb, msg->sections[SECTION_ANSWER], msg->default_ttl, 0, LDNS_ANCOUNT_OFF))
+             res = 0;
+     if(res && msg->sections[SECTION_AUTHORITY] && !_dynlibmod_pushRRList(qb, msg->sections[SECTION_AUTHORITY], msg->default_ttl, 0, LDNS_NSCOUNT_OFF))
+             res = 0;
+     if(res && msg->sections[SECTION_ADDITIONAL] && !_dynlibmod_pushRRList(qb, msg->sections[SECTION_ADDITIONAL], msg->default_ttl, 0, LDNS_ARCOUNT_OFF))
+             res = 0;
+
+     if (res) res = createResponse(qstate, qb);
+
+     if (qb) sldns_buffer_free(qb);
+     return res;
+}
+
diff --git a/dynlibmod/dnsmessage.h b/dynlibmod/dnsmessage.h
new file mode 100644 (file)
index 0000000..725c5e8
--- /dev/null
@@ -0,0 +1,58 @@
+/**
+ * @file Implements the dnsmessage api in C for unbound plugins using the dynlib module
+ */
+
+#ifndef DNSMESSAGE_H
+#define DNSMESSAGE_H
+
+#define PKT_QR 1      /* QueRy - query flag */
+#define PKT_AA 2      /* Authoritative Answer - server flag */
+#define PKT_TC 4      /* TrunCated - server flag */
+#define PKT_RD 8      /* Recursion Desired - query flag */
+#define PKT_CD 16     /* Checking Disabled - query flag */
+#define PKT_RA 32     /* Recursion Available - server flag */
+#define PKT_AD 64     /* Authenticated Data - server flag */
+
+enum enum_section {
+  SECTION_QUESTION = 0,
+  SECTION_ANSWER = 1,
+  SECTION_AUTHORITY = 2,
+  SECTION_ADDITIONAL = 3
+};
+
+/**
+ * Structure encapsulates a new DNS Message.
+ * Created by dnylibmod_dnsmessage, it's sections
+ * are filled out by dnlibmod_msg_append.
+ * Then it is finalized by dynlibmod_set_return_msg
+ */
+struct dynlibmod_dnsmessage;
+
+/**
+ * Allocate a new dnsmessage structure and store the details provided.  Must
+ * be freed by dynlibmod_dnsmessage_free after the call to dynlibmod_set_resturn_msg. (The message
+ * structure is no longer required at that point)
+ */
+struct dynlibmod_dnsmessage *dynlibmod_dnsmessage_new(char *rr_name, sldns_rr_type rr_type, sldns_rr_class rr_class, uint16_t flags, uint32_t default_ttl);
+
+/**
+ * Free a dnsmessage structure.
+ */
+void dynlibmod_dnsmessage_free(struct dynlibmod_dnsmessage *dnsmessage);
+
+/**
+ * Print an record to one of the 4 DNS sections.  Uses printf conventions.   See example code for usage.
+ */
+void dynlibmod_msg_append(struct dynlibmod_dnsmessage *msg, enum enum_section section, const char *format, ...);
+
+/**
+ * An alternative to dynlibmod_msg_append for use in creating wrapper functions
+ */
+void dynlibmod_vmsg_append(struct dynlibmod_dnsmessage *msg, enum enum_section section, const char *format, va_list args);
+
+/**
+ * Finalize the DNS message and store it in the qstate for return to unbound
+ */
+int dynlibmod_set_return_msg(struct module_qstate* qstate, struct dynlibmod_dnsmessage *msg);
+
+#endif /* DNSMESSAGE_H */
diff --git a/dynlibmod/examples/Makefile b/dynlibmod/examples/Makefile
new file mode 100644 (file)
index 0000000..82886e2
--- /dev/null
@@ -0,0 +1,16 @@
+CFLAGS=-I../.. -c -Wall -Werror -fpic -g -Wno-unused-variable
+
+all: resgen.so helloworld.so
+
+resgen.so: resgen.o
+       $(CC) -shared -Wall -Werror -g -o resgen.so resgen.o
+
+helloworld.so: helloworld.o
+       $(CC) -shared -Wall -Werror -g -o helloworld.so helloworld.o
+
+clean:
+       -rm -rf *.o *.so
+
+.PHONY : all clean
+
+
diff --git a/dynlibmod/examples/resgen.c b/dynlibmod/examples/resgen.c
new file mode 100644 (file)
index 0000000..92a3f30
--- /dev/null
@@ -0,0 +1,136 @@
+/**
+ * \file
+ *
+ * This is an example to show how dynamic libraries can be made to work with
+ * unbound. To build a .so file simply run:
+ *   gcc -I../.. -shared -Wall -Werror -fpic  -o helloworld.so helloworld.c
+ * And to build for windows, first make unbound with the --with-dynlibmod
+ * switch, then use this command:
+ *   x86_64-w64-mingw32-gcc -m64 -I../.. -shared -Wall -Werror -fpic
+ *      -o helloworld.dll helloworld.c -L../.. -l:libunbound.dll.a
+ * to cross-compile a 64-bit Windows DLL.  The libunbound.dll.a is produced
+ * by the compile step that makes unbound.exe and allows the dynlib dll to
+ * access definitions in unbound.exe.
+ */
+
+#include "../../config.h"
+#include "../../util/module.h"
+#include "../../sldns/parseutil.h"
+#include "../dynlibmod.h"
+#include "../../sldns/wire2str.h"
+#include "../../services/cache/dns.h"
+#include "../dnsmessage.h"
+
+
+/* Declare the EXPORT macro that expands to exporting the symbol for DLLs when
+ * compiling for Windows. All procedures marked with EXPORT in this example are
+ * called directly by the dynlib module and must be present for the module to
+ * load correctly. */
+#ifdef HAVE_WINDOWS_H
+#define EXPORT __declspec(dllexport)
+#else
+#define EXPORT
+#endif
+
+int endswith(char *str, const char *suffix, char **ptr) {
+    size_t lstr = strlen(str);
+    size_t lsuffix = strlen(suffix);
+
+    // String too long
+    if (lsuffix > lstr)
+        return 0;
+
+    int ret = memcmp(str + (lstr - lsuffix), suffix, lsuffix);
+
+    if (ret) // no match
+        return 0;
+
+    if (ptr)
+        *ptr = str + (lstr - lsuffix);
+
+    return 1;
+}
+
+/* Init is called when the module is first loaded. It should be used to set up
+ * the environment for this module and do any other initialisation required. */
+EXPORT int init(struct module_env* env, int id) {
+    log_info("dynlib: hello world from init");
+    return 1;
+}
+
+/* Deinit is run as the program is shutting down. It should be used to clean up
+ * the environment and any left over data. */
+EXPORT void deinit(struct module_env* env, int id) {
+    log_info("dynlib: hello world from deinit");
+}
+
+/* Operate is called every time a query passes by this module. The event can be
+ * used to determine which direction in the module chain it came from. */
+EXPORT void operate(struct module_qstate* qstate, enum module_ev event,
+                    int id, struct outbound_entry* entry) {
+    char *qname = sldns_wire2str_dname(qstate->qinfo.qname, qstate->qinfo.qname_len);
+    struct dynlibmod_dnsmessage *msg = NULL;
+
+    log_info("dynlib: hello world from operate");
+    log_info("dynlib: incoming query: %s %s(%d) %s(%d)",
+            qname,
+            sldns_lookup_by_id(sldns_rr_classes, qstate->qinfo.qclass)->name,
+            qstate->qinfo.qclass,
+            sldns_rr_descript(qstate->qinfo.qtype)->_name,
+            qstate->qinfo.qtype);
+
+    switch(event) {
+        case module_event_new:
+        case module_event_pass:
+            if (endswith(qname, ".resgentest.", NULL)) {
+                msg = dynlibmod_dnsmessage_new(qname, (sldns_rr_type) qstate->qinfo.qtype, LDNS_RR_CLASS_IN, PKT_QR | PKT_RA | PKT_AA, 0);
+                dynlibmod_msg_append(msg, SECTION_ANSWER, "%s 10 IN A 127.1.2.3", qname);
+                if (!dynlibmod_set_return_msg(qstate, msg)) {
+                    log_info("Failed to set return message");
+                    qstate->ext_state[id] = module_error;
+                    goto cleanup;
+                }
+
+                qstate->return_msg->rep->security = sec_status_indeterminate;
+                qstate->return_rcode = LDNS_RCODE_NOERROR;
+                qstate->ext_state[id] = module_finished;
+                goto cleanup;
+            }
+            qstate->ext_state[id] = module_wait_module;
+            goto cleanup;
+
+        case module_event_moddone:
+            qstate->ext_state[id] = module_finished;
+            goto cleanup;
+
+        default:
+            qstate->ext_state[id] = module_error;
+            goto cleanup;
+    }
+
+cleanup:
+    dynlibmod_dnsmessage_free(msg);
+    free(qname);
+}
+
+/* Inform super is called when a query is completed or errors out, but only if
+ * a sub-query has been registered to it by this module. Look at
+ * mesh_attach_sub in services/mesh.h to see how this is done. */
+EXPORT void inform_super(struct module_qstate* qstate, int id,
+                         struct module_qstate* super) {
+    log_info("dynlib: hello world from inform_super");
+}
+
+/* Clear is called once a query is complete and the response has been sent
+ * back. It is used to clear up any per-query allocations. */
+EXPORT void clear(struct module_qstate* qstate, int id) {
+    log_info("dynlib: hello world from clear");
+}
+
+/* Get mem is called when Unbound is printing performance information. This
+ * only happens explicitly and is only used to show memory usage to the user. */
+EXPORT size_t get_mem(struct module_env* env, int id) {
+    log_info("dynlib: hello world from get_mem");
+    return 0;
+}
+
diff --git a/dynlibmod/test-resgen.conf b/dynlibmod/test-resgen.conf
new file mode 100644 (file)
index 0000000..b183478
--- /dev/null
@@ -0,0 +1,16 @@
+# Example configuration file for resgen.py
+server:
+       verbosity: 1
+       interface: 0.0.0.0
+       do-daemonize: no
+       access-control: 0.0.0.0/0 allow
+       chroot: ""
+       username: ""
+       directory: ""
+       logfile: ""
+       pidfile: "unbound.pid"
+       module-config: "validator dynlib iterator"
+
+dynlib:
+       dynlib-file: "./examples/resgen.so"
+