diff --git a/CMakeLists.txt b/CMakeLists.txt index d668620..013ee39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,6 +84,7 @@ add_executable(rbldnsd rbldnsd_dnhash.c rbldnsd_dnhash_fixed.c rbldnsd_dnset.c + rbldnsd_entryparams.c rbldnsd_generic.c rbldnsd_ip4set.c rbldnsd_ip4trie.c diff --git a/rbldnsd.8 b/rbldnsd.8 index 4092758..f8ab39b 100644 --- a/rbldnsd.8 +++ b/rbldnsd.8 @@ -442,6 +442,43 @@ and usage examples. Set the base template for all individual TXT records. See section "Resulting A values and TXT templates" below for more information. +.SS "Per-entry parameters" +.PP +Some dataset types support optional per-entry parameters. Parameters are +introduced by a \fB@\fR token following the entry value and consist of +key/value pairs separated by \fB:\fR or \fB;\fR. +.PP +Supported dataset types: +.IP \(bu +\fBip4trie\fR +.IP \(bu +\fBip6trie\fR +.IP \(bu +\fBdnhash\fR +.PP +Examples: +.nf + 1.2.3.0/24 :127.0.0.2:Listed @ delay=2s + example.tld TXT "Listed" @ ts=1730000000;delay=60s +.fi +.PP +The following built-in parameters are currently recognized: +.IP "\fBdelay=\fR\fIduration\fR" +Delay sending the reply for a matching entry. The value uses the same +syntax as \fBparse_time()\fR (e.g. \fB60\fR, \fB60s\fR, \fB10m\fR, \fB1h\fR). +.IP "\fBts=\fR\fIunix_timestamp\fR" +UNIX timestamp in seconds. When used together with \fBdelay\fR, the entry +is suppressed until the cooling time is over, i.e. the entry is returned only +when \fBnow >= ts + delay\fR. +.IP "\fBkey=require\fR" +Return the entry only for \fIkeyed\fR requests. A request is considered keyed +when the zone has an \fBaclkey\fR dataset configured and a valid key label is +present in the queried name (\fBaclkey\fR removes that label before the actual +dataset lookup). +.IP "\fBkey=nodelay\fR" +For keyed requests, bypass both reply delaying (\fBdelay\fR without \fBts\fR) +and cooldown suppression (\fBts\fR+\fBdelay\fR) for the matching entry. + .SS "ip4set Dataset" .PP A set of IP addresses or CIDR address ranges, together with A and diff --git a/rbldnsd.c b/rbldnsd.c index f9863b9..0774918 100644 --- a/rbldnsd.c +++ b/rbldnsd.c @@ -1232,6 +1232,149 @@ static int do_reload(int do_fork, struct ev_loop *loop) { #define MSGVEC_LEN 1 #endif +struct delayed_reply { + ev_tstamp at; + int fd; + struct sockaddr_storage peer; + socklen_t peerlen; + unsigned char *buf; + size_t len; +}; + +static struct delayed_reply *delayed_heap; +static unsigned delayed_heap_len; +static unsigned delayed_heap_cap; +static size_t delayed_bytes; +static ev_timer delayed_timer; +static struct ev_loop *delayed_loop; + +#define DELAYED_MAX_ITEMS 8192 +#define DELAYED_MAX_BYTES (32u * 1024u * 1024u) + +static inline void delayed_swap(unsigned a, unsigned b) { + struct delayed_reply tmp = delayed_heap[a]; + delayed_heap[a] = delayed_heap[b]; + delayed_heap[b] = tmp; +} + +static void delayed_sift_up(unsigned idx) { + while (idx > 0) { + unsigned parent = (idx - 1) / 2; + if (delayed_heap[parent].at <= delayed_heap[idx].at) { + break; + } + delayed_swap(parent, idx); + idx = parent; + } +} + +static void delayed_sift_down(unsigned idx) { + for (;;) { + unsigned left = idx * 2 + 1; + unsigned right = left + 1; + unsigned smallest = idx; + if (left < delayed_heap_len && delayed_heap[left].at < delayed_heap[smallest].at) + smallest = left; + if (right < delayed_heap_len && delayed_heap[right].at < delayed_heap[smallest].at) + smallest = right; + if (smallest == idx) + break; + delayed_swap(idx, smallest); + idx = smallest; + } +} + +static void delayed_schedule_timer(struct ev_loop *loop) { + if (delayed_heap_len == 0) { + if (ev_is_active(&delayed_timer)) { + ev_timer_stop(loop, &delayed_timer); + } + return; + } + + ev_tstamp now = ev_now(loop); + ev_tstamp after = delayed_heap[0].at > now ? (delayed_heap[0].at - now) : 0.0; + ev_timer_set(&delayed_timer, after, 0.0); + if (!ev_is_active(&delayed_timer)) { + ev_timer_start(loop, &delayed_timer); + } + else { + ev_timer_again(loop, &delayed_timer); + } +} + +static void delayed_timer_cb(struct ev_loop *loop, ev_timer *w, int revents) { + (void)w; + (void)revents; + + ev_tstamp now = ev_now(loop); + while (delayed_heap_len > 0 && delayed_heap[0].at <= now) { + struct delayed_reply r = delayed_heap[0]; + delayed_heap[0] = delayed_heap[--delayed_heap_len]; + if (delayed_heap_len > 0) { + delayed_sift_down(0); + } + + if (r.buf && r.len) { + (void)sendto(r.fd, r.buf, r.len, 0, (struct sockaddr *)&r.peer, r.peerlen); + delayed_bytes -= r.len; + free(r.buf); + } + } + + delayed_schedule_timer(loop); +} + +static void delayed_init(struct ev_loop *loop) { + delayed_loop = loop; + ev_timer_init(&delayed_timer, delayed_timer_cb, 0.0, 0.0); +} + +static int delayed_push(int fd, const struct sockaddr *peer, socklen_t peerlen, + const unsigned char *buf, size_t len, unsigned delay_ms) { + if (delay_ms == 0 || !buf || len == 0) { + return 0; + } + if (delayed_heap_len >= DELAYED_MAX_ITEMS || delayed_bytes + len > DELAYED_MAX_BYTES) { + return 0; + } + + if (delayed_heap_len == delayed_heap_cap) { + unsigned new_cap = delayed_heap_cap ? delayed_heap_cap * 2 : 128; + if (new_cap > DELAYED_MAX_ITEMS) { + new_cap = DELAYED_MAX_ITEMS; + } + struct delayed_reply *n = realloc(delayed_heap, sizeof(*n) * new_cap); + if (!n) { + return 0; + } + delayed_heap = n; + delayed_heap_cap = new_cap; + } + + unsigned char *copy = malloc(len); + if (!copy) { + return 0; + } + memcpy(copy, buf, len); + + struct delayed_reply *r = &delayed_heap[delayed_heap_len]; + memset(r, 0, sizeof(*r)); + r->fd = fd; + r->peerlen = peerlen; + memcpy(&r->peer, peer, peerlen); + r->buf = copy; + r->len = len; + r->at = ev_now(delayed_loop) + ((ev_tstamp)delay_ms / 1000.0); + + delayed_bytes += len; + delayed_sift_up(delayed_heap_len); + delayed_heap_len++; + + delayed_schedule_timer(delayed_loop); + return 1; +} + static int request(int fd) { int q; #ifndef NO_IPv6 @@ -1280,6 +1423,7 @@ static int request(int fd) { #endif pkt[i].p_peerlen = MSG_FIELD(msg[i], msg_namelen); pkt[i].p_peer = MSG_FIELD(msg[i], msg_name); + pkt[i].p_delay_ms = 0; replies_lengths[i] = replypacket(&pkt[i], q, zonelist, &qi); if (flog) { @@ -1289,6 +1433,15 @@ static int request(int fd) { int cur_rep = 0; for (int i = 0; i < lim; i ++) { + if (replies_lengths[i] > 0 && pkt[i].p_delay_ms > 0) { + if (delayed_push(fd, (const struct sockaddr *)&peer_sa[i], + MSG_FIELD(msg[i], msg_namelen), + pkt[i].p_buf, (size_t)replies_lengths[i], + pkt[i].p_delay_ms)) { + replies_lengths[i] = 0; + } + } + if (replies_lengths[i] > 0) { iovs[cur_rep].iov_base = pkt[i].p_buf; iovs[cur_rep].iov_len = replies_lengths[i]; @@ -1547,6 +1700,7 @@ int main(int argc, char **argv) { } init(argc, argv, loop); + delayed_init(loop); setup_signals(loop); reopenlog(); can_reload = 1; diff --git a/rbldnsd.h b/rbldnsd.h index b66b402..a864bf9 100644 --- a/rbldnsd.h +++ b/rbldnsd.h @@ -37,6 +37,7 @@ struct dataset; struct dsdata; struct dhdata; struct dsctx; +struct dnsqinfo; struct sockaddr; struct ev_loop; struct ev_stat; @@ -50,8 +51,51 @@ struct dnspacket { /* private structure */ const struct dataset *p_substds; const struct sockaddr *p_peer;/* address of the requesting client */ unsigned p_peerlen; + unsigned p_delay_ms; /* optional delay requested by entry params */ }; +struct kv_pair { + const char *k; + const char *v; +}; + +struct kv_params { + unsigned n; + char *storage; + struct kv_pair kv[]; +}; + +struct entry_meta { + const char *rr; + const struct kv_params *params; +}; + +struct entry_action { + int allow; + unsigned delay_ms; + unsigned flags; +}; + +#define ENTRY_ACTION_STOP 0x01u +#define ENTRY_ACTION_NODELAY 0x02u + +typedef int (*entry_params_handler_t)(const struct sockaddr *requestor, + const struct dataset *ds, + const struct dnsqinfo *qinfo, + const struct kv_params *params, + struct entry_action *action); + +int rbldnsd_register_entry_params_handler(entry_params_handler_t cb); +int rbldnsd_apply_entry_params(const struct sockaddr *requestor, + const struct dataset *ds, + const struct dnsqinfo *qinfo, + const struct kv_params *params, + struct entry_action *action); + +char *rbldnsd_split_entry_params(char *s, char **params_out); +const struct kv_params *rbldnsd_parse_kv_params(struct mempool *mp, + struct dsctx *dsc, + const char *s); struct dnsquery { /* q */ unsigned q_type; /* query RR type */ unsigned q_class; /* query class */ diff --git a/rbldnsd_dnhash.c b/rbldnsd_dnhash.c index c126741..54ced52 100644 --- a/rbldnsd_dnhash.c +++ b/rbldnsd_dnhash.c @@ -41,6 +41,7 @@ struct key { struct entry { const char *rr; /* A and TXT RRs */ + const struct kv_params *params; }; #if defined(__SSE4_2__) && defined(__x86_64) && defined(__GNUC__) @@ -479,6 +480,7 @@ ds_dnhash_addent(khash_t(dnhash) *h, struct dnhash_bloom *bloom, const unsigned char *ldn, const char *rr, + const struct kv_params *params, unsigned dnlen) { struct entry *e; khiter_t k; @@ -495,6 +497,7 @@ ds_dnhash_addent(khash_t(dnhash) *h, e = &kh_value(h, k); e->rr = rr; + e->params = params; if (bloom && ret > 0) { dnhash_bloom_add(bloom, ldn, dnlen); @@ -508,6 +511,7 @@ ds_dnhash_line(struct dataset *ds, char *s, struct dsctx *dsc) { struct dsdata *dsd = ds->ds_dsd; unsigned char dn[DNS_MAXDN]; const char *rr; + const struct kv_params *params = NULL; unsigned char *ldn; unsigned dnlen, size; int not, iswild, isplain; @@ -553,6 +557,11 @@ ds_dnhash_line(struct dataset *ds, char *s, struct dsctx *dsc) { else { /* else parse rest */ SKIPSPACE(s); + + char *params_s = NULL; + rbldnsd_split_entry_params(s, ¶ms_s); + params = rbldnsd_parse_kv_params(ds->ds_mp, dsc, params_s); + if (!*s || ISCOMMENT(*s)) { /* use default if none given */ rr = dsd->def_rr; @@ -581,13 +590,13 @@ ds_dnhash_line(struct dataset *ds, char *s, struct dsctx *dsc) { } if (!ds_dnhash_addent(dsd->wild[dnlab - 1], dsd->wild_bloom[dnlab - 1], - ldn, rr, dnlen - 1)) { + ldn, rr, params, dnlen - 1)) { return 0; } } if (isplain) { - if (!ds_dnhash_addent(dsd->direct, dsd->direct_bloom, ldn, rr, dnlen - 1)) { + if (!ds_dnhash_addent(dsd->direct, dsd->direct_bloom, ldn, rr, params, dnlen - 1)) { return 0; } } @@ -713,6 +722,19 @@ ds_dnhash_query(const struct dataset *ds, const struct dnsqinfo *qi, dns_dntop(pkey->ldn + 1, name, sizeof(name)); } + if (e->rr) { + struct entry_action act; + act.allow = 1; + act.delay_ms = 0; + act.flags = 0; + rbldnsd_apply_entry_params(pkt->p_peer, ds, qi, e->params, &act); + if (!act.allow) { + return 0; + } + if (act.delay_ms > pkt->p_delay_ms) { + pkt->p_delay_ms = act.delay_ms; + } + } addrr_a_txt(pkt, qi->qi_tflag, e->rr, name, ds); return NSQUERY_FOUND; @@ -751,6 +773,18 @@ ds_dnhash_query(const struct dataset *ds, const struct dnsqinfo *qi, } if (e->rr) { + struct entry_action act; + act.allow = 1; + act.delay_ms = 0; + act.flags = 0; + rbldnsd_apply_entry_params(pkt->p_peer, ds, qi, e->params, &act); + if (!act.allow) { + return 0; + } + if (act.delay_ms > pkt->p_delay_ms) { + pkt->p_delay_ms = act.delay_ms; + } + addrr_a_txt(pkt, qi->qi_tflag, e->rr, name, ds); return NSQUERY_FOUND; diff --git a/rbldnsd_entryparams.c b/rbldnsd_entryparams.c new file mode 100644 index 0000000..190a425 --- /dev/null +++ b/rbldnsd_entryparams.c @@ -0,0 +1,349 @@ +/* + * Entry params parsing + plugin callbacks. + */ + +#include +#include +#include +#include +#include +#include "rbldnsd.h" + +#define MAX_ENTRY_PARAM_HANDLERS 32 + +static entry_params_handler_t handlers[MAX_ENTRY_PARAM_HANDLERS]; +static unsigned nhandlers; + +int rbldnsd_register_entry_params_handler(entry_params_handler_t cb) { + if (!cb) { + return -1; + } + if (nhandlers >= MAX_ENTRY_PARAM_HANDLERS) { + return -1; + } + handlers[nhandlers++] = cb; + return 0; +} + +int rbldnsd_apply_entry_params(const struct sockaddr *requestor, + const struct dataset *ds, + const struct dnsqinfo *qinfo, + const struct kv_params *params, + struct entry_action *action) { + if (!action) { + return -1; + } + + if (!params || params->n == 0 || nhandlers == 0) { + return 0; + } + + for (unsigned i = 0; i < nhandlers; i++) { + if (!handlers[i]) { + continue; + } + (void)handlers[i](requestor, ds, qinfo, params, action); + if (!action->allow) { + break; + } + if (action->flags & ENTRY_ACTION_STOP) { + break; + } + } + + return 0; +} + +static inline int +parse_time_seconds(const char *s, unsigned *out) +{ + if (!s || !*s || !out) { + return 0; + } + + char buf[64]; + size_t n = strlen(s); + if (n >= sizeof(buf) - 2) { + return 0; + } + memcpy(buf, s, n); + buf[n] = ' '; + buf[n + 1] = '\0'; + + unsigned sec = 0; + char *p = buf; + char *end = parse_time(p, &sec); + if (!end) { + return 0; + } + while (*end && ISSPACE(*end)) { + end++; + } + if (*end != '\0') { + return 0; + } + + *out = sec; + return 1; +} + +static inline int +parse_u64(const char *s, unsigned long long *out) +{ + if (!s || !*s || !out) { + return 0; + } + errno = 0; + char *end = NULL; + unsigned long long v = strtoull(s, &end, 10); + if (errno != 0 || end == s) { + return 0; + } + while (*end && ISSPACE(*end)) { + end++; + } + if (*end != '\0') { + return 0; + } + *out = v; + return 1; +} + +static int +rbldnsd_builtin_entry_params_handler(const struct sockaddr *UNUSED requestor, + const struct dataset *UNUSED ds, + const struct dnsqinfo *qinfo, + const struct kv_params *params, + struct entry_action *action) +{ + int is_keyed = qinfo && (qinfo->qi_tflag & NSQUERY_KEY); + + int require_key = 0; + int nodelay = 0; + unsigned delay_sec = 0; + int have_delay = 0; + unsigned long long ts = 0; + int have_ts = 0; + + for (unsigned i = 0; params && i < params->n; i++) { + const char *k = params->kv[i].k; + const char *v = params->kv[i].v; + if (!k) { + continue; + } + + if (strcmp(k, "key") == 0 && v) { + if (strcmp(v, "require") == 0) { + require_key = 1; + } + else if (strcmp(v, "nodelay") == 0) { + nodelay = 1; + } + } + else if (strcmp(k, "delay") == 0 && v) { + if (parse_time_seconds(v, &delay_sec)) { + have_delay = 1; + } + } + else if (strcmp(k, "ts") == 0 && v) { + if (parse_u64(v, &ts)) { + have_ts = 1; + } + } + } + + if (require_key && !is_keyed) { + action->allow = 0; + action->flags |= ENTRY_ACTION_STOP; + return 0; + } + + if (nodelay && is_keyed) { + action->delay_ms = 0; + action->flags |= ENTRY_ACTION_NODELAY; + action->flags |= ENTRY_ACTION_STOP; + return 0; + } + + if (have_ts && have_delay) { + unsigned long long ready = ts + (unsigned long long)delay_sec; + unsigned long long now = (unsigned long long)time(NULL); + if (now < ready) { + action->allow = 0; + action->flags |= ENTRY_ACTION_STOP; + } + return 0; + } + + if (have_delay && !have_ts) { + unsigned long long ms = (unsigned long long)delay_sec * 1000ULL; + if (ms > UINT_MAX) { + ms = UINT_MAX; + } + if ((unsigned)ms > action->delay_ms) { + action->delay_ms = (unsigned)ms; + } + } + + return 0; +} + +void __attribute__((constructor)) +rbldnsd_builtin_entry_params_init(void) +{ + (void)rbldnsd_register_entry_params_handler(rbldnsd_builtin_entry_params_handler); +} + +static inline int is_namech(char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '.'; +} + +char *rbldnsd_split_entry_params(char *s, char **params_out) { + if (params_out) { + *params_out = NULL; + } + if (!s) { + return NULL; + } + + char *p = s; + char prev = '\0'; + while (*p) { + if (*p == '@' && (p == s || ISSPACE(prev))) { + char *at = p; + /* trim whitespace before @ */ + while (at > s && ISSPACE(at[-1])) { + at[-1] = '\0'; + at--; + } + *p++ = '\0'; + SKIPSPACE(p); + if (params_out) { + *params_out = p; + } + return s; + } + prev = *p; + p++; + } + + return s; +} + +const struct kv_params *rbldnsd_parse_kv_params(struct mempool *mp, + struct dsctx *dsc, + const char *s) { + if (!s) { + return NULL; + } + while (ISSPACE(*s)) { + s++; + } + if (!*s || ISCOMMENT(*s)) { + return NULL; + } + + char *buf = mp_strdup(mp, s); + if (!buf) { + return NULL; + } + + /* Strip trailing comment if present */ + for (char *c = buf; *c; c++) { + if (ISCOMMENT(*c) && (c == buf || ISSPACE(c[-1]))) { + *c = '\0'; + break; + } + } + + /* First pass: count pairs */ + unsigned npairs = 0; + { + char *p = buf; + while (*p) { + while (*p == ':' || *p == ';' || ISSPACE(*p)) { + p++; + } + if (!*p) { + break; + } + npairs++; + while (*p && *p != ':' && *p != ';') { + p++; + } + } + } + + if (npairs == 0) { + return NULL; + } + + struct kv_params *params = + mp_alloc(mp, sizeof(*params) + sizeof(params->kv[0]) * npairs, 1); + if (!params) { + return NULL; + } + params->n = 0; + params->storage = buf; + + /* Second pass: split into key/value */ + char *p = buf; + while (*p) { + while (*p == ':' || *p == ';' || ISSPACE(*p)) { + p++; + } + if (!*p) { + break; + } + + char *tok = p; + while (*p && *p != ':' && *p != ';') { + p++; + } + if (*p == ':' || *p == ';') { + *p++ = '\0'; + } + + /* trim token */ + while (ISSPACE(*tok)) { + tok++; + } + char *end = tok + strlen(tok); + while (end > tok && ISSPACE(end[-1])) { + end[-1] = '\0'; + end--; + } + if (!*tok) { + continue; + } + + char *eq = strchr(tok, '='); + if (eq) { + *eq++ = '\0'; + while (ISSPACE(*eq)) { + eq++; + } + } + + for (char *k = tok; *k; k++) { + if (!is_namech(*k)) { + dswarn(dsc, "invalid entry params key"); + tok = NULL; + break; + } + } + if (!tok) { + continue; + } + + params->kv[params->n].k = tok; + params->kv[params->n].v = eq && *eq ? eq : NULL; + params->n++; + } + + if (params->n == 0) { + return NULL; + } + return params; +} diff --git a/rbldnsd_ip4trie.c b/rbldnsd_ip4trie.c index a73d9bd..4033a0b 100644 --- a/rbldnsd_ip4trie.c +++ b/rbldnsd_ip4trie.c @@ -37,6 +37,8 @@ ds_ip4trie_line(struct dataset *ds, char *s, struct dsctx *dsc) int bits; const char *rr; unsigned rrl; + char *params_s = NULL; + const struct kv_params *params = NULL; int not, ipv6 = 0; @@ -98,6 +100,8 @@ ds_ip4trie_line(struct dataset *ds, char *s, struct dsctx *dsc) rr = NULL; else { SKIPSPACE(s); + rbldnsd_split_entry_params(s, ¶ms_s); + params = rbldnsd_parse_kv_params(ds->ds_mp, dsc, params_s); if (!*s || ISCOMMENT(*s)) rr = dsd->def_rr; else if (!(rrl = parse_a_txt(s, &rr, dsd->def_rr, dsc))) @@ -106,17 +110,24 @@ ds_ip4trie_line(struct dataset *ds, char *s, struct dsctx *dsc) return 0; } + struct entry_meta *meta = mp_alloc(ds->ds_mp, sizeof(*meta), 1); + if (!meta) { + return 0; + } + meta->rr = rr; + meta->params = params; + int ret = -1; if (ipv6) { - ret = btrie_add_prefix(dsd->btrie, ipv6_addr, bits, rr); + ret = btrie_add_prefix(dsd->btrie, ipv6_addr, bits, meta); } else { memset(ipv6_addr, 0, 10); ipv6_addr[10] = 0xffu; ipv6_addr[11] = 0xffu; ip4unpack(ipv6_addr + 12, a); - ret = btrie_add_prefix(dsd->btrie, ipv6_addr, 96 + bits, rr); + ret = btrie_add_prefix(dsd->btrie, ipv6_addr, 96 + bits, meta); } switch(ret) { @@ -151,6 +162,7 @@ static void ds_ip4trie_finish(struct dataset *ds, struct dsctx *dsc) { static int ds_ip4trie_query(const struct dataset *ds, const struct dnsqinfo *qi, struct dnspacket *pkt) { + const struct entry_meta *meta; const char *rr; btrie_oct_t addr_bytes[IP6ADDR_FULL]; @@ -162,19 +174,33 @@ ds_ip4trie_query(const struct dataset *ds, const struct dnsqinfo *qi, addr_bytes[10] = 0xffu; addr_bytes[11] = 0xffu; ip4unpack(addr_bytes + 12, qi->qi_ip4); - rr = btrie_lookup(ds->ds_dsd->btrie, addr_bytes, 8 * IP6ADDR_FULL); + meta = btrie_lookup(ds->ds_dsd->btrie, addr_bytes, 8 * IP6ADDR_FULL); } else if (qi->qi_ip6valid) { check_query_overwrites(qi); - rr = btrie_lookup(ds->ds_dsd->btrie, qi->qi_ip6, 8 * IP6ADDR_FULL); + meta = btrie_lookup(ds->ds_dsd->btrie, qi->qi_ip6, 8 * IP6ADDR_FULL); } else { return 0; } - if (!rr) + if (!meta || !meta->rr) + return 0; + + struct entry_action act; + act.allow = 1; + act.delay_ms = 0; + act.flags = 0; + rbldnsd_apply_entry_params(pkt->p_peer, ds, qi, meta->params, &act); + if (!act.allow) { return 0; + } + if (act.delay_ms > pkt->p_delay_ms) { + pkt->p_delay_ms = act.delay_ms; + } + + rr = meta->rr; const char *subst = NULL; @@ -225,6 +251,8 @@ dump_cb(const btrie_oct_t *prefix, unsigned len, const void *data, int post, { struct dump_context *ctx = user_data; ip4addr_t addr; + const struct entry_meta *meta; + const char *rr; if (len > 32) return; /* paranoia (or ipv6 for now, gah) */ @@ -253,14 +281,17 @@ dump_cb(const btrie_oct_t *prefix, unsigned len, const void *data, int post, data = ctx->parent_data[ctx->depth]; } - if (data != ctx->prev_rr) { + meta = data; + rr = meta ? meta->rr : NULL; + + if (rr != ctx->prev_rr) { if (addr != ctx->prev_addr) { if (ctx->prev_rr) dump_ip4range(ctx->prev_addr, addr - 1, ctx->prev_rr, ctx->ds, ctx->f); ctx->prev_addr = addr; } /* else addr unchanged => zero-length range, ignore */ - ctx->prev_rr = data; + ctx->prev_rr = rr; } /* else rr unchanged => merge current range with previous */ } diff --git a/rbldnsd_ip6trie.c b/rbldnsd_ip6trie.c index 81f6402..f08c58e 100644 --- a/rbldnsd_ip6trie.c +++ b/rbldnsd_ip6trie.c @@ -39,6 +39,8 @@ ds_ip6trie_line(struct dataset *ds, char *s, struct dsctx *dsc) unsigned rrl; int bits, excl, non_zero_host; ip6oct_t addr[IP6ADDR_FULL]; + char *params_s = NULL; + const struct kv_params *params = NULL; /* "::" can not be a valid start to a default RR setting ("invalid A * RR") but it can be a valid beginning to an ip6 address @@ -72,14 +74,25 @@ ds_ip6trie_line(struct dataset *ds, char *s, struct dsctx *dsc) SKIPSPACE(s); if (excl) rr = NULL; - else if (!*s || ISCOMMENT(*s)) - rr = dsd->def_rr; - else if (!(rrl = parse_a_txt(s, &rr, dsd->def_rr, dsc))) - return 1; - else if (!(rr = mp_dmemdup(ds->ds_mp, rr, rrl))) + else { + rbldnsd_split_entry_params(s, ¶ms_s); + params = rbldnsd_parse_kv_params(ds->ds_mp, dsc, params_s); + if (!*s || ISCOMMENT(*s)) + rr = dsd->def_rr; + else if (!(rrl = parse_a_txt(s, &rr, dsd->def_rr, dsc))) + return 1; + else if (!(rr = mp_dmemdup(ds->ds_mp, rr, rrl))) + return 0; + } + + struct entry_meta *meta = mp_alloc(ds->ds_mp, sizeof(*meta), 1); + if (!meta) { return 0; + } + meta->rr = rr; + meta->params = params; - switch(btrie_add_prefix(dsd->btrie, addr, bits, rr)) { + switch(btrie_add_prefix(dsd->btrie, addr, bits, meta)) { case BTRIE_OKAY: return 1; case BTRIE_DUPLICATE_PREFIX: @@ -103,16 +116,31 @@ ds_ip6trie_query(const struct dataset *ds, const struct dnsqinfo *qi, struct dnspacket *pkt) { const char *subst = NULL; + const struct entry_meta *meta; const char *rr; if (!qi->qi_ip6valid) return 0; check_query_overwrites(qi); - rr = btrie_lookup(ds->ds_dsd->btrie, qi->qi_ip6, 8 * IP6ADDR_FULL); + meta = btrie_lookup(ds->ds_dsd->btrie, qi->qi_ip6, 8 * IP6ADDR_FULL); - if (!rr) + if (!meta || !meta->rr) return 0; + struct entry_action act; + act.allow = 1; + act.delay_ms = 0; + act.flags = 0; + rbldnsd_apply_entry_params(pkt->p_peer, ds, qi, meta->params, &act); + if (!act.allow) { + return 0; + } + if (act.delay_ms > pkt->p_delay_ms) { + pkt->p_delay_ms = act.delay_ms; + } + + rr = meta->rr; + if (qi->qi_tflag & NSQUERY_TXT) subst = ip6atos(qi->qi_ip6, IP6ADDR_FULL); addrr_a_txt(pkt, qi->qi_tflag, rr, subst, ds); @@ -154,6 +182,8 @@ dump_cb(const btrie_oct_t *prefix, unsigned len, const void *data, int post, struct dump_context *ctx = user_data; unsigned nb = (len + 7) / 8; ip6oct_t addr[IP6ADDR_FULL]; + const struct entry_meta *meta; + const char *rr; if (nb > IP6ADDR_FULL) return; /* paranoia */ @@ -183,14 +213,17 @@ dump_cb(const btrie_oct_t *prefix, unsigned len, const void *data, int post, data = ctx->parent_data[ctx->depth]; } - if (data != ctx->prev_rr) { + meta = data; + rr = meta ? meta->rr : NULL; + + if (rr != ctx->prev_rr) { if (memcmp(addr, ctx->prev_addr, IP6ADDR_FULL) != 0) { if (ctx->prev_rr) dump_ip6range(ctx->prev_addr, addr, ctx->prev_rr, ctx->ds, ctx->f); memcpy(ctx->prev_addr, addr, IP6ADDR_FULL); } /* else addr unchanged => zero-length range, ignore */ - ctx->prev_rr = data; + ctx->prev_rr = rr; } /* else rr unchanged => merge current range with previous */ } diff --git a/test/functional/cases/005_ENTRY_PARAMS.robot b/test/functional/cases/005_ENTRY_PARAMS.robot new file mode 100644 index 0000000..70a6643 --- /dev/null +++ b/test/functional/cases/005_ENTRY_PARAMS.robot @@ -0,0 +1,64 @@ +*** Settings *** +Test Teardown Rbldnsd Teardown +Library ${RBLDNSD_TESTDIR}/lib/rbldnsd.py +Resource ${RBLDNSD_TESTDIR}/lib/rbldnsd.robot +Variables ${RBLDNSD_TESTDIR}/lib/vars.py + +*** Keywords *** +Entry Params Setup + [Arguments] ${dnhash} ${aclkey} + Prepare Temporary Directory + Create File ${RBLDNSD_TMPDIR}/dnhash ${DUMMY_ZONE_HEADER}${dnhash} + Create File ${RBLDNSD_TMPDIR}/aclkey ${DUMMY_ZONE_HEADER}${aclkey} + @{RBLDNSD_ZONES} = Create List + ... ${SOA}:dnhash:${RBLDNSD_TMPDIR}/dnhash + ... ${SOA}:aclkey:${RBLDNSD_TMPDIR}/aclkey + Set Test Variable ${RBLDNSD_ZONES} + Run Rbldnsd + +Cooldown Setup + ${now} = Evaluate int(__import__('time').time()) + ${past} = Evaluate ${now} - 120 + ${zone} = Catenate SEPARATOR=\n + ... cooldown.tld COOLDOWN @ ts=${now};delay=60s + ... ready.tld READY @ ts=${past};delay=60s + Entry Params Setup ${zone} :pass\nsecret :pass + +Key NoDelay Setup + ${now} = Evaluate int(__import__('time').time()) + ${zone} = Catenate SEPARATOR=\n + ... nodelay.tld NODELAY @ ts=${now};delay=3600s;key=nodelay + Entry Params Setup ${zone} :pass\nsecret :pass + +*** Test Cases *** +TEST ENTRY PARAMS COOLDOWN + [Setup] Cooldown Setup + + Query Rbldnsd cooldown.tld.${SOA} + Expect No Query Result + + Query Rbldnsd ready.tld.${SOA} + Expect Query Status NOERROR + Expect Query Result READY + +TEST ENTRY PARAMS KEY REQUIRE + [Setup] Entry Params Setup + ... needkey.tld NEEDKEY @ key=require + ... :pass\nsecret :pass + + Query Rbldnsd needkey.tld.${SOA} + Expect No Query Result + + Query Rbldnsd needkey.tld.secret.${SOA} + Expect Query Status NOERROR + Expect Query Result NEEDKEY + +TEST ENTRY PARAMS KEY NODELAY + [Setup] Key NoDelay Setup + + Query Rbldnsd nodelay.tld.${SOA} + Expect No Query Result + + Query Rbldnsd nodelay.tld.secret.${SOA} + Expect Query Status NOERROR + Expect Query Result NODELAY