From: David Hinkle Date: Fri, 12 Mar 2021 23:04:33 +0000 (+0000) Subject: dnsmessage api for dynlib X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=00cf7195f57b06967b2e27d9452f83ece9aed7de;p=thirdparty%2Funbound.git dnsmessage api for dynlib --- diff --git a/Makefile.in b/Makefile.in index b640af006..7f07dca51 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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 \ diff --git a/configure b/configure index b067e48fe..674ad9e42 100755 --- 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 index 000000000..a8b9e25fc --- /dev/null +++ b/dynlibmod/dnsmessage.c @@ -0,0 +1,218 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 index 000000000..725c5e88e --- /dev/null +++ b/dynlibmod/dnsmessage.h @@ -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 index 000000000..82886e2ea --- /dev/null +++ b/dynlibmod/examples/Makefile @@ -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 index 000000000..92a3f306a --- /dev/null +++ b/dynlibmod/examples/resgen.c @@ -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 index 000000000..b1834783a --- /dev/null +++ b/dynlibmod/test-resgen.conf @@ -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" +