Commit 08daba32 authored by Marek Vavrusa's avatar Marek Vavrusa

synth: auto reverse records synthesis

- no proofs yet
- no forward records
parent 1666cd1f
......@@ -219,6 +219,8 @@ libknotd_la_SOURCES = \
knot/nameserver/nsec_proofs.h \
knot/nameserver/process_query.c \
knot/nameserver/process_query.h \
knot/nameserver/synth_record.c \
knot/nameserver/synth_record.h \
knot/nameserver/update.c \
knot/nameserver/update.h \
knot/other/debug.h \
......
......@@ -132,6 +132,9 @@ transfers { lval.t = yytext; return TRANSFERS; }
dnssec-enable { lval.t = yytext; return DNSSEC_ENABLE; }
dnssec-keydir { lval.t = yytext; return DNSSEC_KEYDIR; }
signature-lifetime { lval.t = yytext; return SIGNATURE_LIFETIME; }
synth { lval.t = yytext; return SYNTH; }
forward { lval.t = yytext; return FORWARD; }
reverse { lval.t = yytext; return REVERSE; }
interfaces { lval.t = yytext; return INTERFACES; }
address { lval.t = yytext; return ADDRESS; }
......
......@@ -35,6 +35,7 @@
#include "libknot/binary.h"
#include "libknot/edns.h"
#include "knot/server/rrl.h"
#include "knot/nameserver/synth_record.h"
#include "knot/conf/conf.h"
#include "knot/conf/libknotd_la-cf-parse.h" /* Automake generated header. */
......@@ -277,6 +278,47 @@ static void conf_acl_item(void *scanner, char *item)
free(item);
}
static void synth_tpl_create(void *scanner)
{
synth_template_t *tpl = malloc(sizeof(synth_template_t));
if (tpl == NULL) {
cf_error(scanner, "out of memory");
return;
}
memset(tpl, 0, sizeof(synth_template_t));
add_tail(&this_zone->synth_templates, &tpl->node);
}
static void synth_tpl_define(void *scanner, enum synth_template_type template_type, char* format)
{
synth_template_t *tpl = TAIL(this_zone->synth_templates);
tpl->type = template_type;
tpl->format = format;
}
static void synth_tpl_set_addr(void *scanner, int family, char* addr, int netblock)
{
synth_template_t *tpl = TAIL(this_zone->synth_templates);
sockaddr_set(&tpl->subnet.ss, family, addr, 0);
int prefix_max = IPV4_PREFIXLEN;
if (family == AF_INET6) {
prefix_max = IPV6_PREFIXLEN;
}
SET_NUM(tpl->subnet.prefix, netblock, 0, prefix_max, "prefix length");
free(addr);
}
static void synth_tpl_set_ttl(void *scanner, uint32_t ttl)
{
synth_template_t *tpl = TAIL(this_zone->synth_templates);
SET_NUM(tpl->ttl, ttl, 0, UINT32_MAX, "ttl");
}
static int conf_key_exists(void *scanner, char *item)
{
/* Find existing node in keys. */
......@@ -477,6 +519,9 @@ static void ident_auto(int tok, conf_t *conf, bool val)
%token <tok> SIGNATURE_LIFETIME
%token <tok> SERIAL_POLICY
%token <tok> SERIAL_POLICY_VAL
%token <tok> SYNTH
%token <tok> REVERSE
%token <tok> FORWARD
%token <tok> INTERFACES ADDRESS PORT
%token <tok> IPA
......@@ -833,6 +878,22 @@ zone_acl:
}
;
zone_synth_ttl:
| NUM { synth_tpl_set_ttl(scanner, $1.i); }
;
zone_synth_addr:
IPA '/' NUM { synth_tpl_set_addr(scanner, AF_INET, $1.t, $3.i); }
| IPA6 '/' NUM { synth_tpl_set_addr(scanner, AF_INET6, $1.t, $3.i); }
;
zone_synth:
| FORWARD TEXT zone_synth_addr zone_synth_ttl { synth_tpl_define(scanner, SYNTH_FORWARD, $2.t); }
| REVERSE TEXT zone_synth_addr zone_synth_ttl { synth_tpl_define(scanner, SYNTH_REVERSE, $2.t); }
;
zone_synth_start: SYNTH { synth_tpl_create(scanner); }
zone_start:
| USER { conf_zone_start(scanner, strdup($1.t)); }
| REMOTES { conf_zone_start(scanner, strdup($1.t)); }
......@@ -897,6 +958,7 @@ zone:
| zone SERIAL_POLICY SERIAL_POLICY_VAL ';' {
this_zone->serial_policy = $3.i;
}
| zone zone_synth_start zone_synth ';'
;
zones:
......
......@@ -962,6 +962,9 @@ void conf_init_zone(conf_zone_t *zone)
init_list(&zone->acl.notify_in);
init_list(&zone->acl.notify_out);
init_list(&zone->acl.update_in);
// Initialize synthesis templates
init_list(&zone->synth_templates);
}
void conf_free_zone(conf_zone_t *zone)
......
......@@ -136,6 +136,8 @@ typedef struct conf_zone_t {
list_t notify_out; /*!< Remotes accepted for notify-out.*/
list_t update_in; /*!< Remotes accepted for DDNS.*/
} acl;
list_t synth_templates;
} conf_zone_t;
/*!
......
#include "knot/nameserver/synth_record.h"
#include "knot/nameserver/internet.h"
#include "common/descriptor.h"
#define ARPA_ZONE_LABELS 2
#define IP4_ARPA_NAME (const uint8_t *)("\x7""in-addr""\x4""arpa""\x0")
#define IP6_ARPA_NAME (const uint8_t *)("\x3""ip6""\x4""arpa""\x0")
/*! \brief Parse address from reverse query QNAME and return address family. */
static int reverse_addr_parse(struct query_data *qdata, char *addr_str)
{
/* QNAME required format is [address].[subnet/zone]
* f.e. [1.0...0].[h.g.f.e.0.0.0.0.d.c.b.a.ip6.arpa] represents
* [abcd:0:efgh::1] */
const knot_dname_t* label = knot_pkt_qname(qdata->query);
const uint8_t *query_wire = qdata->query->wire;
/* Check if we have at least 2 last labels for arpa zone. */
int label_count = knot_dname_labels(label, query_wire);
if (label_count < ARPA_ZONE_LABELS) {
return AF_UNSPEC;
}
/* Push labels on stack for reverse walkthrough. */
const uint8_t* label_stack[KNOT_DNAME_MAXLABELS];
const uint8_t** sp = label_stack;
while(label_count > ARPA_ZONE_LABELS) {
*sp++ = label;
label = knot_wire_next_label(label, query_wire);
--label_count;
}
/* Check remaining suffix if we're matching IPv6/IPv4 */
int family = AF_UNSPEC;
char sep = '.';
int sep_frequency = 1;
if (knot_dname_is_equal(label, IP4_ARPA_NAME)) {
family = AF_INET;
} else if (knot_dname_is_equal(label, IP6_ARPA_NAME)) {
/* Conversion from dotted form to hex. */
family = AF_INET6;
sep = ':';
sep_frequency = 4;
} else {
return AF_UNSPEC;
}
/* Write formatted address string. */
label_count = 0;
char *dst = addr_str;
while(sp != label_stack) {
label = *--sp;
/* Write separator for each Nth label. */
if (label_count == sep_frequency) {
*dst = sep;
dst += 1;
label_count = 0;
}
/* Write label */
memcpy(dst, label + 1, label[0]);
dst += label[0];
label_count += 1;
}
return family;
}
static knot_dname_t *synth_ptrname(const char *addr_str, synth_template_t *tpl)
{
/* PTR right-hand value is [prefix][addresbs][suffix] */
char ptrname[KNOT_DNAME_MAXLEN] = {'\0'};
ssize_t written = 0;
ssize_t to_write = 0;
/* Tokenize result string. */
const char *format = tpl->format;
const char *sub = NULL;
while ((sub = strchr(format, '%')) != NULL) {
/* Find substitution closure. */
const char *sub_end = strchr(sub + 1, '%');
if (sub_end == NULL) {
return NULL; /* Unpaired substitution. */
}
/* Write prefix string. */
to_write = sub - format;
if (written + to_write < sizeof(ptrname)) {
memcpy(ptrname + written, format, to_write);
written += to_write;
}
/* Write substitution. */
to_write = strlen(addr_str);
if (written + to_write < sizeof(ptrname)) {
memcpy(ptrname + written, addr_str, to_write);
for (int i = 0; i < to_write; ++i) {
if (ptrname[written + i] == '.' ||
ptrname[written + i] == ':') {
ptrname[written + i] = '-';
}
}
written += to_write;
}
format = sub_end + 1;
}
/* Write remainder. */
to_write = strlen(format);
if (written + to_write < sizeof(ptrname)) {
memcpy(ptrname + written, format, to_write);
written += to_write;
}
/* Convert to domain name. */
return knot_dname_from_str(ptrname);
}
static int reverse_match(synth_template_t *tpl, knot_pkt_t *pkt, struct query_data *qdata)
{
/* Parse address from query name. */
char addr_str[SOCKADDR_STRLEN] = { '\0' };
int family = reverse_addr_parse(qdata, addr_str);
if (family == AF_UNSPEC) {
qdata->rcode = KNOT_RCODE_NXDOMAIN; /* Invalid address in our authority. */
return NS_PROC_FAIL;
}
/* Match against template netblock. */
struct sockaddr_storage query_addr;
int ret = sockaddr_set(&query_addr, family, addr_str, 0);
if (ret == KNOT_EOK) {
ret = netblock_match(&tpl->subnet, &query_addr);
}
if (ret != 0) {
return NS_PROC_NOOP; /* Not applicable. */
}
/* Synthetize PTR record. */
knot_dname_t* qname = knot_dname_copy(knot_pkt_qname(qdata->query));
knot_rrset_t *rr = knot_rrset_new(qname, KNOT_RRTYPE_PTR, KNOT_CLASS_IN, &pkt->mm);
if (rr == NULL) {
knot_dname_free(&qname);
qdata->rcode = KNOT_RCODE_SERVFAIL;
return NS_PROC_FAIL;
}
/* Synthetize PTR record data. */
knot_dname_t *ptrname = synth_ptrname(addr_str, tpl);
if (ptrname == NULL) {
qdata->rcode = KNOT_RCODE_SERVFAIL;
return NS_PROC_FAIL;
}
knot_rrset_add_rr(rr, ptrname, knot_dname_size(ptrname), tpl->ttl, &pkt->mm);
/*! \todo Minimal TTL if not configured, after SOA record API cleanup. */
/* Create empty response with PTR record in AN. */
knot_pkt_init_response(pkt, qdata->query);
if (knot_pkt_put(pkt, COMPR_HINT_QNAME, rr, KNOT_PF_FREE) != KNOT_EOK) {
return NS_PROC_FAIL;
}
return NS_PROC_DONE;
}
static int forward_match(synth_template_t *tpl, knot_pkt_t *pkt, struct query_data *qdata)
{
return NS_PROC_FAIL;
}
/*! \brief Check if query fits the template requirements. */
static int template_match(synth_template_t *tpl, knot_pkt_t *pkt, struct query_data *qdata)
{
switch(tpl->type) {
case SYNTH_REVERSE: return reverse_match(tpl, pkt, qdata);
case SYNTH_FORWARD: return forward_match(tpl, pkt, qdata);
default: return NS_PROC_NOOP;
}
}
bool synth_answer_possible(struct query_data *qdata)
{
/*! \note This might be used for synth responses in general (like CH stub),
* then requirements should be in the template. */
/* Synthetic response is possible if we have non-empty
* list of synth templates and name resolution fails. */
return qdata->packet_type == KNOT_QUERY_NORMAL &&
qdata->rcode == KNOT_RCODE_NXDOMAIN &&
qdata->zone && !EMPTY_LIST(qdata->zone->conf->synth_templates);
}
int synth_answer(knot_pkt_t *pkt, struct query_data *qdata)
{
if (pkt == NULL || qdata == NULL || qdata->zone == NULL) {
return NS_PROC_NOOP;
}
/* Check valid zone. */
NS_NEED_ZONE(qdata, KNOT_RCODE_REFUSED);
/* Scan template list. */
conf_zone_t *zone_config = qdata->zone->conf;
synth_template_t *tpl = NULL;
WALK_LIST(tpl, zone_config->synth_templates) {
/* Check if template fits. */
int next_state = template_match(tpl, pkt, qdata);
if (next_state != NS_PROC_NOOP) {
return next_state; /* Template matched. */
}
}
/* Cannot synthetize answer. */
return NS_PROC_FAIL;
}
/*!
* \file synth_record.h
*
* \author Marek Vavrusa <marek.vavrusa@nic.cz>
*
* \brief Synthetic records
*
* \addtogroup query_processing
* @{
*/
/* Copyright (C) 2013 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _SYNTH_RECORD_H
#include "knot/nameserver/process_query.h"
#include "knot/updates/acl.h"
#include "common/lists.h"
/*! \brief Supported answer synthesis template types. */
enum synth_template_type {
SYNTH_FORWARD,
SYNTH_REVERSE
};
/*!
* \brief Synthetic response template.
*/
typedef struct synth_template {
node_t node;
enum synth_template_type type;
char *format;
uint32_t ttl;
netblock_t subnet;
} synth_template_t;
/*!
* \brief Return true if it is possible to synthetize response.
* \param qdata
*/
bool synth_answer_possible(struct query_data *qdata);
/*!
* \brief Attempt to synthetize response.
* \param pkt
* \param qdata
* \return EOK if success, else error code
*/
int synth_answer(knot_pkt_t *pkt, struct query_data *qdata);
#define _SYNTH_RECORD_H
#endif /* _SYNTH_RECORD_H */
/*! @} */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment