enum {
/** Empty zone. No data in DB value after this byte. */
VAL_ZLAT_EMPTY = 1,
+ /** Redirect: anything beneath has the same data as apex (except NS+SOA). */
+ VAL_ZLAT_REDIRECT,
};
const uint8_t *data, const uint8_t *data_bound);
static int answer_zla_empty(struct kr_query *qry, knot_pkt_t *pkt,
knot_db_val_t zla_lf, knot_db_val_t val);
+static int answer_zla_redirect(struct kr_query *qry, knot_pkt_t *pkt, const char *ruleset_name,
+ knot_db_val_t zla_lf, knot_db_val_t val);
//TODO later, maybe. ATM it would be cumbersome to avoid void* arithmetics.
#pragma GCC diagnostic ignored "-Wpointer-arith"
*
* Note: key_data[KEY_DNAME_END_OFFSET] = '\0' even though
* not always used as a part of the key. */
-static inline uint8_t * key_dname_lf(const knot_dname_t *name, uint8_t *key_data)
+static inline uint8_t * key_dname_lf(const knot_dname_t *name, uint8_t key_data[KEY_MAXLEN])
{
return knot_dname_lf(name, key_data + KEY_RULESET_MAXLEN + 1)
+ 1/*drop length*/;
/* Iterate over all rulesets. */
while (rulesets.len > 0) {
+ const char * const ruleset_name = rulesets_str;
{ /* Write ruleset-specific prefix of the key. */
const size_t rsp_len = strnlen(rulesets_str, rulesets.len);
kr_require(rsp_len <= KEY_RULESET_MAXLEN - 1);
switch (ztype) {
case VAL_ZLAT_EMPTY:
return answer_zla_empty(qry, pkt, zla_lf, val);
+ case VAL_ZLAT_REDIRECT:
+ return answer_zla_redirect(qry, pkt, ruleset_name, zla_lf, val);
default:
return kr_error(EILSEQ);
}
}
-static knot_db_val_t local_data_key(const knot_rrset_t *rrs, uint8_t key_data[KEY_MAXLEN])
+static knot_db_val_t local_data_key(const knot_rrset_t *rrs, uint8_t key_data[KEY_MAXLEN],
+ const char *ruleset_name)
{
knot_db_val_t key;
key.data = key_dname_lf(rrs->owner, key_data);
key.data -= sizeof(KEY_EXACT_MATCH);
memcpy(key.data, &KEY_EXACT_MATCH, sizeof(KEY_EXACT_MATCH));
- const size_t rsp_len = strlen(RULESET_DEFAULT);
+ const size_t rsp_len = strlen(ruleset_name);
key.data -= rsp_len;
- memcpy(key.data, RULESET_DEFAULT, rsp_len);
+ memcpy(key.data, ruleset_name, rsp_len);
memcpy(key_data + KEY_DNAME_END_OFFSET + 2, &rrs->type, sizeof(rrs->type));
key.len = key_data + KEY_DNAME_END_OFFSET + 2 + sizeof(rrs->type)
{
// Construct the DB key.
uint8_t key_data[KEY_MAXLEN];
- knot_db_val_t key = local_data_key(rrs, key_data);
+ knot_db_val_t key = local_data_key(rrs, key_data, RULESET_DEFAULT);
// Allocate the data in DB.
const int rr_ssize = rdataset_dematerialize_size(&rrs->rrs);
int kr_rule_local_data_del(const knot_rrset_t *rrs, kr_rule_tags_t tags)
{
uint8_t key_data[KEY_MAXLEN];
- knot_db_val_t key = local_data_key(rrs, key_data);
+ knot_db_val_t key = local_data_key(rrs, key_data, RULESET_DEFAULT);
int ret = ruledb_op(remove, &key, 1);
if (ret != 1)
return ret;
return kr_ok();
}
-int kr_rule_local_data_emptyzone(const knot_dname_t *apex, kr_rule_tags_t tags)
+static int answer_zla_redirect(struct kr_query *qry, knot_pkt_t *pkt, const char *ruleset_name,
+ const knot_db_val_t zla_lf, const knot_db_val_t val_unused)
+{
+ if (kr_fails_assert(val_unused.len == 0)) {
+ kr_log_error(RULES, "ERROR: unused bytes: %zu\n", val_unused.len);
+ return kr_error(EILSEQ);
+ }
+ VERBOSE_MSG(qry, "=> redirecting by local data\n"); // lazy to get the zone name
+
+ knot_dname_t apex_name[KNOT_DNAME_MAXLEN];
+ int ret = knot_dname_lf2wire(apex_name, zla_lf.len, zla_lf.data);
+ CHECK_RET(ret);
+ const bool name_matches = knot_dname_is_equal(qry->sname, apex_name);
+ if (name_matches || qry->stype == KNOT_RRTYPE_NS || qry->stype == KNOT_RRTYPE_SOA)
+ goto nodata;
+
+ // Reconstruct the DB key from scratch.
+ knot_rrset_t rrs;
+ knot_rrset_init(&rrs, apex_name, qry->stype, 0, 0); // 0 are unused
+ uint8_t key_data[KEY_MAXLEN];
+ knot_db_val_t key = local_data_key(&rrs, key_data, ruleset_name);
+
+ knot_db_val_t val;
+ ret = ruledb_op(read, &key, &val, 1);
+ switch (ret) {
+ case -ENOENT: goto nodata;
+ case 0: break;
+ default: return ret;
+ }
+ if (kr_rule_consume_tags(&val, qry->request)) // found a match
+ return answer_exact_match(qry, pkt, qry->stype,
+ val.data, val.data + val.len);
+
+nodata: // Want NODATA answer (or NOERROR if it hits apex SOA).
+ // Start constructing the (pseudo-)packet.
+ ret = pkt_renew(pkt, qry->sname, qry->stype);
+ CHECK_RET(ret);
+ struct answer_rrset arrset;
+ memset(&arrset, 0, sizeof(arrset));
+ arrset.set.rr = knot_rrset_new(apex_name, KNOT_RRTYPE_SOA,
+ KNOT_CLASS_IN, RULE_TTL_DEFAULT, &pkt->mm);
+ if (kr_fails_assert(arrset.set.rr))
+ return kr_error(ENOMEM);
+ ret = knot_rrset_add_rdata(arrset.set.rr, soa_rdata,
+ sizeof(soa_rdata) - 1, &pkt->mm);
+ CHECK_RET(ret);
+ arrset.set.rank = KR_RANK_SECURE | KR_RANK_AUTH; // local data has high trust
+ arrset.set.expiring = false;
+
+ knot_wire_set_rcode(pkt->wire, KNOT_RCODE_NOERROR);
+ knot_section_t sec = name_matches && qry->stype == KNOT_RRTYPE_SOA
+ ? KNOT_ANSWER : KNOT_AUTHORITY;
+ ret = knot_pkt_begin(pkt, sec);
+ CHECK_RET(ret);
+
+ // Put links to the RR into the pkt.
+ ret = pkt_append(pkt, &arrset);
+ CHECK_RET(ret);
+
+ // Finishing touches.
+ qry->flags.EXPIRING = false;
+ qry->flags.CACHED = true;
+ qry->flags.NO_MINIMIZE = true;
+
+ VERBOSE_MSG(qry, "=> satisfied by local data (no data)\n");
+ return kr_ok();
+}
+
+static int insert_trivial_zone(val_zla_type_t ztype,
+ const knot_dname_t *apex, kr_rule_tags_t tags)
{
uint8_t key_data[KEY_MAXLEN];
knot_db_val_t key;
memcpy(key.data, RULESET_DEFAULT, rsp_len);
key.len = key_data + KEY_DNAME_END_OFFSET - (uint8_t *)key.data;
- val_zla_type_t ztype = VAL_ZLAT_EMPTY;
knot_db_val_t val = {
.data = NULL,
.len = sizeof(tags) + sizeof(ztype),
return ruledb_op(commit);
}
+int kr_rule_local_data_emptyzone(const knot_dname_t *apex, kr_rule_tags_t tags)
+{
+ return insert_trivial_zone(VAL_ZLAT_EMPTY, apex, tags);
+}
+int kr_rule_local_data_redirect(const knot_dname_t *apex, kr_rule_tags_t tags)
+{
+ return insert_trivial_zone(VAL_ZLAT_REDIRECT, apex, tags);
+}
+
#include "lib/rules/api.h"
#include "lib/utils.h"
+#define CHECK_RET(ret) do { \
+ if ((ret) < 0) { kr_assert(false); return kr_error((ret)); } \
+} while (false)
+
int rules_defaults_insert(void)
{
static const char * names[] = {
const knot_dname_t *dname =
knot_dname_from_str(name_buf, names[i], sizeof(name_buf));
int ret = kr_rule_local_data_emptyzone(dname, KR_RULE_TAGS_ALL);
- if (kr_fails_assert(!ret))
- return kr_error(ret);
+ CHECK_RET(ret);
/* The double conversion is perhaps a bit wasteful, but it should be rare. */
/* LATER: add extra info with explanation? policy module had an ADDITIONAL
* record with explanation, but perhaps extended errors are more suitable?
*/
}
+ knot_dname_t localhost_dname[] = "\x09localhost\0";
+ { // forward localhost
+ int ret = kr_rule_local_data_redirect(localhost_dname, KR_RULE_TAGS_ALL);
+ CHECK_RET(ret);
+
+ knot_rrset_t rr = {
+ .owner = localhost_dname,
+ .ttl = RULE_TTL_DEFAULT,
+ .rclass = KNOT_CLASS_IN,
+ .rrs = { 0 },
+ .additional = NULL,
+ };
+ rr.type = KNOT_RRTYPE_A;
+ ret = knot_rrset_add_rdata(&rr, (const uint8_t *)"\x7f\0\0\1", 4, NULL);
+ if (!ret) ret = kr_rule_local_data_ins(&rr, NULL, KR_RULE_TAGS_ALL);
+ knot_rdataset_clear(&rr.rrs, NULL);
+ CHECK_RET(ret);
+
+ rr.type = KNOT_RRTYPE_AAAA;
+ ret = knot_rrset_add_rdata(&rr,
+ (const uint8_t *)"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1",
+ 16, NULL);
+ if (!ret) ret = kr_rule_local_data_ins(&rr, NULL, KR_RULE_TAGS_ALL);
+ knot_rdataset_clear(&rr.rrs, NULL);
+ CHECK_RET(ret);
+
+ rr.type = KNOT_RRTYPE_NS;
+ ret = knot_rrset_add_rdata(&rr, localhost_dname, 1+9+1, NULL);
+ if (!ret) ret = kr_rule_local_data_ins(&rr, NULL, KR_RULE_TAGS_ALL);
+ knot_rdataset_clear(&rr.rrs, NULL);
+ CHECK_RET(ret);
+ }
+
{ // reverse localhost; LATER: the situation isn't ideal with NXDOMAIN + some exact matches
- knot_dname_t name_buf[KNOT_DNAME_MAXLEN];
knot_rrset_t rr = {
- .owner = knot_dname_from_str(name_buf,
- "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.",
- sizeof(name_buf)),
+ .owner = localhost_dname,
.ttl = RULE_TTL_DEFAULT,
.type = KNOT_RRTYPE_PTR,
.rclass = KNOT_CLASS_IN,
.rrs = { 0 },
.additional = NULL,
};
- int ret = knot_rrset_add_rdata(&rr, (const knot_dname_t *)"\x09localhost\0",
- 1+9+1, NULL);
+ int ret = knot_rrset_add_rdata(&rr, localhost_dname, 1+9+1, NULL);
if (!ret) ret = kr_rule_local_data_ins(&rr, NULL, KR_RULE_TAGS_ALL);
+ knot_dname_t name_buf[KNOT_DNAME_MAXLEN];
rr.owner = knot_dname_from_str(name_buf,
"1.0.0.127.in-addr.arpa.",
sizeof(name_buf));
if (!ret) ret = kr_rule_local_data_ins(&rr, NULL, KR_RULE_TAGS_ALL);
+ rr.owner = knot_dname_from_str(name_buf,
+ "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.",
+ sizeof(name_buf));
+ if (!ret) ret = kr_rule_local_data_ins(&rr, NULL, KR_RULE_TAGS_ALL);
+
knot_rdataset_clear(&rr.rrs, NULL);
- if (kr_fails_assert(!ret))
- return kr_error(ret);
+ CHECK_RET(ret);
}
return kr_ok();
local LOG_GRP_POLICY_TAG = ffi.string(ffi.C.kr_log_grp2name(ffi.C.LOG_GRP_POLICY))
local LOG_GRP_REQDBG_TAG = ffi.string(ffi.C.kr_log_grp2name(ffi.C.LOG_GRP_REQDBG))
-local todname = kres.str2dname -- not available during module load otherwise
-
-- Counter of unique rules
local nextid = 0
local function getruleid()
end
end
-local dname_localhost = todname('localhost.')
-
--- Rule for localhost. zone; see RFC6303, sec. 3
-local function localhost(_, req)
- local qry = req:current()
- local answer = req:ensure_answer()
- if answer == nil then return nil end
- ffi.C.kr_pkt_make_auth_header(answer)
-
- local is_exact = ffi.C.knot_dname_is_equal(qry.sname, dname_localhost)
-
- answer:rcode(kres.rcode.NOERROR)
- answer:begin(kres.section.ANSWER)
- if qry.stype == kres.type.AAAA then
- answer:put(qry.sname, 900, answer:qclass(), kres.type.AAAA,
- '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1')
- elseif qry.stype == kres.type.A then
- answer:put(qry.sname, 900, answer:qclass(), kres.type.A, '\127\0\0\1')
- elseif is_exact and qry.stype == kres.type.SOA then
- mkauth_soa(answer, dname_localhost)
- elseif is_exact and qry.stype == kres.type.NS then
- answer:put(dname_localhost, 900, answer:qclass(), kres.type.NS, dname_localhost)
- else
- answer:begin(kres.section.AUTHORITY)
- mkauth_soa(answer, dname_localhost)
- end
- return kres.DONE
-end
-
-- All requests
function policy.all(action)
return function(_, _) return action end
-- @var Default rules
policy.rules = {}
policy.postrules = {}
-policy.special_names = {
- -- XXX: beware of special_names_optim() when modifying these filters
- {
- cb=policy.suffix(localhost, {dname_localhost}),
- count=0
- },
-}
-
--- Return boolean; false = no special name may apply, true = some might apply.
--- The point is to *efficiently* filter almost all QNAMEs that do not apply.
-local function special_names_optim(req, sname)
- local qname_size = req.qsource.packet.qname_size
- if qname_size < 9 then return true end -- don't want to special-case bad array access
- local root = sname + qname_size - 1
- return
- -- .a???. or .t???.
- (root[-5] == 4 and (root[-4] == 97 or root[-4] == 116))
- -- .on???. or .in?????. or lo???. or *ost.
- or (root[-6] == 5 and root[-5] == 111 and root[-4] == 110)
- or (root[-8] == 7 and root[-7] == 105 and root[-6] == 110)
- or (root[-6] == 5 and root[-5] == 108 and root[-4] == 111)
- or (root[-3] == 111 and root[-2] == 115 and root[-1] == 116)
-end
-- Top-down policy list walk until we hit a match
-- the caller is responsible for reordering policy list
if bit.band(state, bit.bor(kres.FAIL, kres.DONE)) ~= 0 then return state end
local qry = req:initial() -- same as :current() but more descriptive
return policy.evaluate(policy.rules, req, qry, state)
- or (special_names_optim(req, qry.sname)
- and policy.evaluate(policy.special_names, req, qry, state))
or state
end,
finish = function(state, req)