summaryrefslogtreecommitdiffstats
path: root/src/dns.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/dns.c')
-rw-r--r--src/dns.c484
1 files changed, 484 insertions, 0 deletions
diff --git a/src/dns.c b/src/dns.c
new file mode 100644
index 0000000..548a1a9
--- /dev/null
+++ b/src/dns.c
@@ -0,0 +1,484 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+
+#include "dns.h"
+
+#include "log.h"
+#include "opt.h"
+#include "qtype.h"
+
+#include <ctype.h>
+#include <time.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+
+#ifdef HAVE_LDNS
+#include <ldns/ldns.h>
+#endif
+
+#define WHITESPACE " \t\n"
+
+#define MAX_RDATA_LENGTH 65535
+#define EDNSLEN 11
+
+const char* perf_dns_rcode_strings[] = {
+ "NOERROR", "FORMERR", "SERVFAIL", "NXDOMAIN",
+ "NOTIMP", "REFUSED", "YXDOMAIN", "YXRRSET",
+ "NXRRSET", "NOTAUTH", "NOTZONE", "rcode11",
+ "rcode12", "rcode13", "rcode14", "rcode15"
+};
+
+perf_result_t perf_dname_fromstring(const char* str, size_t len, perf_buffer_t* target)
+{
+ size_t label_len;
+ const char* orig_str = str;
+
+ if (perf_buffer_availablelength(target) < len) {
+ return PERF_R_NOSPACE;
+ }
+
+ while (len) {
+ for (label_len = 0; label_len < len; label_len++) {
+ if (*(str + label_len) == '.') {
+ break;
+ }
+ }
+ if (!label_len) {
+ // Just a dot
+ if (len > 1) {
+ // a dot but with labels after it
+ return PERF_R_FAILURE;
+ } else if (str != orig_str) {
+ // a dot but with labels before it
+ return PERF_R_FAILURE;
+ }
+ perf_buffer_putuint8(target, 0);
+ break;
+ }
+ if (label_len > 63) {
+ return PERF_R_FAILURE;
+ }
+ perf_buffer_putuint8(target, label_len);
+ perf_buffer_putmem(target, str, label_len);
+ str += label_len;
+ len -= label_len;
+ if (len < 2) {
+ // Last label/dot
+ perf_buffer_putuint8(target, 0);
+ break;
+ }
+ // advance past dot
+ str++;
+ len--;
+ }
+
+ return PERF_R_SUCCESS;
+}
+
+perf_result_t perf_qtype_fromstring(const char* str, size_t len, perf_buffer_t* target)
+{
+ const perf_qtype_t* q = qtype_table;
+
+ while (q->type) {
+ if (!strncasecmp(q->type, str, len)) {
+ perf_buffer_putuint16(target, q->value);
+ return PERF_R_SUCCESS;
+ }
+ q++;
+ }
+
+ return PERF_R_FAILURE;
+}
+
+static perf_result_t build_query(const perf_region_t* line, perf_buffer_t* msg)
+{
+ char * domain_str, *qtype_str;
+ size_t domain_len, qtype_len;
+ perf_result_t result;
+
+ domain_str = line->base;
+ domain_len = strcspn(line->base, WHITESPACE);
+
+ if (!domain_len) {
+ perf_log_warning("invalid query input format: %s", (char*)line->base);
+ return PERF_R_FAILURE;
+ }
+
+ qtype_str = line->base + domain_len;
+ while (isspace(*qtype_str))
+ qtype_str++;
+ qtype_len = strcspn(qtype_str, WHITESPACE);
+
+ /* Create the question section */
+ result = perf_dname_fromstring(domain_str, domain_len, msg);
+ if (result != PERF_R_SUCCESS) {
+ perf_log_warning("invalid domain name (or out of space): %.*s", (int)domain_len, domain_str);
+ return result;
+ }
+
+ if (!qtype_len) {
+ perf_log_warning("invalid query input format: %s", (char*)line->base);
+ return PERF_R_FAILURE;
+ }
+
+ result = perf_qtype_fromstring(qtype_str, qtype_len, msg);
+ if (result != PERF_R_SUCCESS) {
+ perf_log_warning("invalid qtype: %.*s", (int)qtype_len, qtype_str);
+ return result;
+ }
+
+ perf_buffer_putuint16(msg, 1); // class IN
+
+ return PERF_R_SUCCESS;
+}
+
+#ifdef HAVE_LDNS
+static bool token_equals(const perf_region_t* token, const char* str)
+{
+ return (strlen(str) == token->length && strncasecmp(str, token->base, token->length) == 0);
+}
+
+/*
+ * Reads one line containing an individual update for a dynamic update message.
+ */
+static perf_result_t
+read_update_line(char* str, const ldns_rdf* origin,
+ bool want_ttl, bool need_type, bool want_rdata, bool need_rdata,
+ ldns_rr** rr, const char** errstr)
+{
+ char tmp[256], *str2;
+ size_t len;
+
+ while (isspace(*str & 0xff))
+ str++;
+ str2 = str;
+
+ /*
+ * Read the owner name
+ */
+ len = strcspn(str, WHITESPACE);
+ if (len > sizeof(tmp) - 1) {
+ *errstr = "domain name too large";
+ return PERF_R_NOSPACE;
+ }
+ memcpy(tmp, str, len);
+ tmp[len] = 0;
+
+ ldns_rdf* owner;
+ if (!(owner = ldns_dname_new_frm_str(tmp))) {
+ *errstr = "invalid name or out of memory";
+ return PERF_R_FAILURE;
+ }
+ ldns_rr_set_owner(*rr, owner);
+ if (!ldns_dname_str_absolute(tmp) && origin) {
+ if (ldns_dname_cat(ldns_rr_owner(*rr), origin) != LDNS_STATUS_OK) {
+ return PERF_R_FAILURE;
+ }
+ }
+
+ str += len;
+ while (isspace(*str & 0xff))
+ str++;
+
+ /*
+ * Read the ttl
+ */
+ if (want_ttl) {
+ len = strcspn(str, WHITESPACE);
+ if (len > sizeof(tmp) - 1) {
+ *errstr = "TTL string too large";
+ return PERF_R_NOSPACE;
+ }
+ memcpy(tmp, str, len);
+ tmp[len] = 0;
+
+ char* endptr = 0;
+ unsigned long int u = strtoul(tmp, &endptr, 10);
+ if (*endptr || u == ULONG_MAX) {
+ *errstr = "TTL invalid";
+ return PERF_R_INVALIDUPDATE;
+ }
+
+ ldns_rr_set_ttl(*rr, u);
+
+ str += len;
+ while (isspace(*str & 0xff))
+ str++;
+ }
+
+ /*
+ * Read the type
+ */
+ len = strcspn(str, WHITESPACE);
+ if (!len) {
+ if (!need_type)
+ return PERF_R_SUCCESS;
+
+ *errstr = "TYPE required";
+ return PERF_R_INVALIDUPDATE;
+ }
+ if (len > sizeof(tmp) - 1) {
+ *errstr = "TYPE string too large";
+ return PERF_R_NOSPACE;
+ }
+ memcpy(tmp, str, len);
+ tmp[len] = 0;
+
+ ldns_rr_type type = ldns_get_rr_type_by_name(tmp);
+ if (!type) {
+ *errstr = "TYPE invalid";
+ return PERF_R_INVALIDUPDATE;
+ }
+ ldns_rr_set_type(*rr, type);
+
+ str += len;
+ while (isspace(*str & 0xff))
+ str++;
+
+ if (!want_rdata)
+ return PERF_R_SUCCESS;
+
+ /*
+ * Read the rdata
+ */
+ if (*str == 0) {
+ if (!need_rdata)
+ return PERF_R_SUCCESS;
+
+ *errstr = "RDATA required";
+ return PERF_R_INVALIDUPDATE;
+ }
+
+ // Need to recreate ldns_rr because there is no new_frm_str function to
+ // correctly parse RDATA (quotes etc) for a RDF
+ ldns_rr* rr2 = 0;
+ if (ldns_rr_new_frm_str(&rr2, str2, 0, origin, 0) != LDNS_STATUS_OK) {
+ *errstr = "invalid RDATA or out of memory";
+ return PERF_R_INVALIDUPDATE;
+ }
+
+ // Force set TTL since if its missing in the input it will get the default
+ // 3600 and not 0 as it should
+ ldns_rr_set_ttl(rr2, ldns_rr_ttl(*rr));
+
+ ldns_rr_free(*rr);
+ *rr = rr2;
+
+ return PERF_R_SUCCESS;
+}
+
+static void compression_free(ldns_rbnode_t* node, void* arg)
+{
+ (void)arg;
+ ldns_rdf_deep_free((ldns_rdf*)node->key);
+ LDNS_FREE(node);
+}
+
+/*
+ * Reads a complete dynamic update message and sends it.
+ */
+static perf_result_t build_update(const perf_region_t* record, perf_buffer_t* msg)
+{
+ perf_region_t input, token;
+ char * msgbase, *str;
+ bool is_update;
+ int updates = 0;
+ int prereqs = 0;
+ perf_result_t result = PERF_R_FAILURE;
+ ldns_rdf* origin = 0;
+ ldns_rr* rr = 0;
+ ldns_buffer* lmsg = 0;
+ ldns_rbtree_t compression;
+ const char* errstr;
+
+ input = *record;
+ msgbase = perf_buffer_base(msg);
+ ldns_rbtree_init(&compression, ldns_dname_compare_v);
+
+ // Fill LDNS buffer with current message (DNS headers)
+ if (!(lmsg = ldns_buffer_new(perf_buffer_length(msg)))) {
+ perf_log_fatal("unable to create LDNS buffer for DNS message");
+ goto done; // for scan-build / sonarcloud
+ }
+ ldns_buffer_write(lmsg, perf_buffer_base(msg), perf_buffer_usedlength(msg));
+
+ if (!(origin = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_DNAME, input.base))) {
+ perf_log_warning("Unable to parse domain name %s", (char*)input.base);
+ goto done;
+ }
+ if (ldns_dname2buffer_wire_compress(lmsg, origin, &compression) != LDNS_STATUS_OK) {
+ perf_log_warning("Unable to write domain name %s to wire format", (char*)input.base);
+ goto done;
+ }
+
+ ldns_buffer_write_u16(lmsg, 6); // SOA
+ ldns_buffer_write_u16(lmsg, 1); // IN
+
+ while (true) {
+ input.base += strlen(input.base) + 1;
+ if (input.base >= record->base + record->length) {
+ perf_log_warning("incomplete update: %s", (char*)record->base);
+ result = PERF_R_FAILURE;
+ goto done;
+ }
+
+ is_update = false;
+ token.base = input.base;
+ token.length = strcspn(token.base, WHITESPACE);
+ str = input.base + token.length;
+ errstr = 0;
+ if (token_equals(&token, "send")) {
+ break;
+ }
+
+ rr = ldns_rr_new();
+ ldns_rr_set_ttl(rr, 0);
+ ldns_rr_set_type(rr, LDNS_RR_TYPE_ANY);
+ ldns_rr_set_class(rr, LDNS_RR_CLASS_IN);
+
+ if (token_equals(&token, "add")) {
+ result = read_update_line(str, origin, true, true, true, true, &rr, &errstr);
+ ldns_rr_set_class(rr, LDNS_RR_CLASS_IN);
+ is_update = true;
+ } else if (token_equals(&token, "delete")) {
+ result = read_update_line(str, origin, false, false, true, false, &rr, &errstr);
+ if (ldns_rr_rd_count(rr)) {
+ ldns_rr_set_class(rr, LDNS_RR_CLASS_NONE);
+ } else {
+ ldns_rr_set_class(rr, LDNS_RR_CLASS_ANY);
+ }
+ is_update = true;
+ } else if (token_equals(&token, "require")) {
+ result = read_update_line(str, origin, false, false, true, false, &rr, &errstr);
+ if (ldns_rr_rd_count(rr)) {
+ ldns_rr_set_class(rr, LDNS_RR_CLASS_IN);
+ } else {
+ ldns_rr_set_class(rr, LDNS_RR_CLASS_ANY);
+ }
+ is_update = false;
+ } else if (token_equals(&token, "prohibit")) {
+ result = read_update_line(str, origin, false, false, false, false, &rr, &errstr);
+ ldns_rr_set_class(rr, LDNS_RR_CLASS_NONE);
+ is_update = false;
+ } else {
+ perf_log_warning("invalid update command: %s", (char*)input.base);
+ result = PERF_R_FAILURE;
+ }
+
+ if (result != PERF_R_SUCCESS) {
+ if (errstr) {
+ perf_log_warning("invalid update command, %s: %s", errstr, (char*)input.base);
+ } else if (result == PERF_R_INVALIDUPDATE) {
+ perf_log_warning("invalid update command: %s", (char*)input.base);
+ } else {
+ perf_log_warning("error processing update command: %s", (char*)input.base);
+ }
+ ldns_rr_free(rr);
+ goto done;
+ }
+
+ if (!is_update && updates > 0) {
+ perf_log_warning("prereqs must precede updates");
+ result = PERF_R_FAILURE;
+ ldns_rr_free(rr);
+ goto done;
+ }
+
+ if (ldns_rr2buffer_wire_compress(lmsg, rr, LDNS_SECTION_ANSWER, &compression) != LDNS_STATUS_OK) {
+ perf_log_warning("Unable to write update message to wire format");
+ ldns_rr_free(rr);
+ goto done;
+ }
+ ldns_rr_free(rr);
+
+ if (is_update)
+ updates++;
+ else
+ prereqs++;
+ }
+
+ if (ldns_buffer_position(lmsg) - perf_buffer_usedlength(msg) > perf_buffer_availablelength(msg)) {
+ perf_log_warning("out of space in message buffer");
+ result = PERF_R_NOSPACE;
+ goto done;
+ }
+ uint8_t* p = ldns_buffer_begin(lmsg) + perf_buffer_usedlength(msg);
+ perf_buffer_putmem(msg, p, ldns_buffer_position(lmsg) - perf_buffer_usedlength(msg));
+
+ msgbase[7] = prereqs; /* ANCOUNT = number of prereqs */
+ msgbase[9] = updates; /* AUCOUNT = number of updates */
+
+ result = PERF_R_SUCCESS;
+
+done:
+ ldns_buffer_free(lmsg);
+ ldns_rdf_deep_free(origin);
+ ldns_traverse_postorder(&compression, compression_free, 0);
+
+ return result;
+}
+#endif
+
+perf_result_t perf_dns_buildrequest(const perf_region_t* record, uint16_t qid,
+ bool edns, bool dnssec, bool is_update,
+ perf_tsigkey_t* tsigkey, perf_ednsoption_t* edns_option,
+ perf_buffer_t* msg)
+{
+ unsigned int flags;
+ perf_result_t result;
+
+ if (is_update)
+ flags = 5 << 11; // opcode UPDATE
+ else
+ flags = 0x0100U; // flag RD
+
+ /* Create the DNS packet header */
+ perf_buffer_putuint16(msg, qid);
+ perf_buffer_putuint16(msg, flags); /* flags */
+ perf_buffer_putuint16(msg, 1); /* qdcount */
+ perf_buffer_putuint16(msg, 0); /* ancount */
+ perf_buffer_putuint16(msg, 0); /* aucount */
+ perf_buffer_putuint16(msg, 0); /* arcount */
+
+ if (is_update) {
+#ifdef HAVE_LDNS
+ result = build_update(record, msg);
+#else
+ result = PERF_R_FAILURE;
+#endif
+ } else {
+ result = build_query(record, msg);
+ }
+
+ if (result == PERF_R_SUCCESS && edns) {
+ result = perf_add_edns(msg, dnssec, edns_option);
+ }
+
+ if (result == PERF_R_SUCCESS && tsigkey) {
+ result = perf_add_tsig(msg, tsigkey);
+ }
+
+ return result;
+}