Description: based on the included patch contrib/fastrpz.patch Author: fastrpz@farsightsecurity.com --- diff --git a/Makefile.in b/Makefile.in index bac212df..4824927f 100644 --- a/Makefile.in +++ b/Makefile.in @@ -23,6 +23,8 @@ CHECKLOCK_SRC=testcode/checklocks.c CHECKLOCK_OBJ=@CHECKLOCK_OBJ@ DNSTAP_SRC=@DNSTAP_SRC@ DNSTAP_OBJ=@DNSTAP_OBJ@ +FASTRPZ_SRC=@FASTRPZ_SRC@ +FASTRPZ_OBJ=@FASTRPZ_OBJ@ DNSCRYPT_SRC=@DNSCRYPT_SRC@ DNSCRYPT_OBJ=@DNSCRYPT_OBJ@ WITH_DYNLIBMODULE=@WITH_DYNLIBMODULE@ @@ -134,7 +136,7 @@ validator/val_sigcrypt.c validator/val_utils.c dns64/dns64.c \ edns-subnet/edns-subnet.c edns-subnet/subnetmod.c \ edns-subnet/addrtree.c edns-subnet/subnet-whitelist.c \ cachedb/cachedb.c cachedb/redis.c respip/respip.c $(CHECKLOCK_SRC) \ -$(DNSTAP_SRC) $(DNSCRYPT_SRC) $(IPSECMOD_SRC) $(IPSET_SRC) +$(DNSTAP_SRC) $(FASTRPZ_SRC) $(DNSCRYPT_SRC) $(IPSECMOD_SRC) $(IPSET_SRC) COMMON_OBJ_WITHOUT_NETCALL=dns.lo infra.lo rrset.lo dname.lo msgencode.lo \ as112.lo msgparse.lo msgreply.lo packed_rrset.lo iterator.lo iter_delegpt.lo \ iter_donotq.lo iter_fwd.lo iter_hints.lo iter_priv.lo iter_resptype.lo \ @@ -147,7 +149,7 @@ autotrust.lo val_anchor.lo rpz.lo \ validator.lo val_kcache.lo val_kentry.lo val_neg.lo val_nsec3.lo val_nsec.lo \ val_secalgo.lo val_sigcrypt.lo val_utils.lo dns64.lo cachedb.lo redis.lo authzone.lo \ $(SUBNET_OBJ) $(PYTHONMOD_OBJ) $(CHECKLOCK_OBJ) $(DNSTAP_OBJ) $(DNSCRYPT_OBJ) \ -$(IPSECMOD_OBJ) $(IPSET_OBJ) $(DYNLIBMOD_OBJ) respip.lo +$(FASTRPZ_OBJ) $(IPSECMOD_OBJ) $(IPSET_OBJ) $(DYNLIBMOD_OBJ) respip.lo COMMON_OBJ_WITHOUT_UB_EVENT=$(COMMON_OBJ_WITHOUT_NETCALL) netevent.lo listen_dnsport.lo \ outside_network.lo COMMON_OBJ=$(COMMON_OBJ_WITHOUT_UB_EVENT) ub_event.lo @@ -428,6 +430,11 @@ dnscrypt.lo dnscrypt.o: $(srcdir)/dnscrypt/dnscrypt.c config.h \ $(srcdir)/util/config_file.h $(srcdir)/util/log.h \ $(srcdir)/util/netevent.h +# fastrpz +rpz.lo rpz.o: $(srcdir)/fastrpz/rpz.c config.h fastrpz/rpz.h fastrpz/librpz.h \ + $(srcdir)/util/config_file.h $(srcdir)/daemon/daemon.h \ + $(srcdir)/util/log.h + # Python Module pythonmod.lo pythonmod.o: $(srcdir)/pythonmod/pythonmod.c config.h \ pythonmod/interface.h \ diff --git a/config.h.in b/config.h.in index f7a4095e..d5a4fa01 100644 --- a/config.h.in +++ b/config.h.in @@ -1364,4 +1364,11 @@ void *unbound_stat_realloc_log(void *ptr, size_t size, const char* file, /** the version of unbound-control that this software implements */ #define UNBOUND_CONTROL_VERSION 1 - +/* have __attribute__s used in librpz.h */ +#undef LIBRPZ_HAVE_ATTR +/** fastrpz librpz.so */ +#undef FASTRPZ_LIBRPZ_PATH +/** 0=no fastrpz 1=static link 2=dlopen() */ +#undef FASTRPZ_LIB_OPEN +/** turn on fastrpz response policy zones */ +#undef ENABLE_FASTRPZ diff --git a/configure.ac b/configure.ac index 5c373d9d..e45abd89 100644 --- a/configure.ac +++ b/configure.ac @@ -6,6 +6,7 @@ sinclude(ax_pthread.m4) sinclude(acx_python.m4) sinclude(ac_pkg_swig.m4) sinclude(dnstap/dnstap.m4) +sinclude(fastrpz/rpz.m4) sinclude(dnscrypt/dnscrypt.m4) # must be numbers. ac_defun because of later processing @@ -1819,6 +1820,9 @@ case "$enable_explicit_port_randomisation" in esac +# check for Fastrpz with fastrpz/rpz.m4 +ck_FASTRPZ + AC_MSG_CHECKING([if ${MAKE:-make} supports $< with implicit rule in scope]) # on openBSD, the implicit rule make $< work. # on Solaris, it does not work ($? is changed sources, $^ lists dependencies). diff --git a/daemon/daemon.c b/daemon/daemon.c index 5d427925..f89f1437 100644 --- a/daemon/daemon.c +++ b/daemon/daemon.c @@ -91,6 +91,9 @@ #include "sldns/keyraw.h" #include "respip/respip.h" #include +#ifdef ENABLE_FASTRPZ +#include "fastrpz/rpz.h" +#endif #ifdef HAVE_SYSTEMD #include @@ -456,6 +459,14 @@ daemon_create_workers(struct daemon* daemon) fatal_exit("dt_create failed"); #else fatal_exit("dnstap enabled in config but not built with dnstap support"); +#endif + } + if(daemon->cfg->rpz_enable) { +#ifdef ENABLE_FASTRPZ + rpz_init(&daemon->rpz_clist, &daemon->rpz_client, daemon->cfg); +#else + fatal_exit("fastrpz enabled in config" + " but not built with fastrpz"); #endif } for(i=0; inum; i++) { @@ -729,6 +740,9 @@ daemon_cleanup(struct daemon* daemon) #ifdef USE_DNSCRYPT dnsc_delete(daemon->dnscenv); daemon->dnscenv = NULL; +#endif +#ifdef ENABLE_FASTRPZ + rpz_delete(&daemon->rpz_clist, &daemon->rpz_client); #endif daemon->cfg = NULL; } diff --git a/daemon/daemon.h b/daemon/daemon.h index 3effbafb..4d4c34da 100644 --- a/daemon/daemon.h +++ b/daemon/daemon.h @@ -138,6 +138,11 @@ struct daemon { /** the dnscrypt environment */ struct dnsc_env* dnscenv; #endif +#ifdef ENABLE_FASTRPZ + /** global opaque rpz handles */ + struct librpz_clist *rpz_clist; + struct librpz_client *rpz_client; +#endif }; /** diff --git a/daemon/worker.c b/daemon/worker.c index 23e3244c..b63d49b7 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -76,6 +76,9 @@ #include "libunbound/context.h" #include "libunbound/libworker.h" #include "sldns/sbuffer.h" +#ifdef ENABLE_FASTRPZ +#include "fastrpz/rpz.h" +#endif #include "sldns/wire2str.h" #include "util/shm_side/shm_main.h" #include "dnscrypt/dnscrypt.h" @@ -535,8 +538,27 @@ answer_norec_from_cache(struct worker* worker, struct query_info* qinfo, /* not secure */ secure = 0; break; +#ifdef ENABLE_FASTRPZ + case sec_status_rpz_rewritten: + case sec_status_rpz_drop: + fatal_exit("impossible cached RPZ sec_status"); + break; +#endif } } +#ifdef ENABLE_FASTRPZ + if(repinfo->rpz) { + /* Scan the cached answer for RPZ hits. + * ret=1 use cache entry + * ret=-1 rewritten response already sent or dropped + * ret=0 deny a cached entry exists + */ + int ret = rpz_worker_cache(worker, msg->rep, qinfo, + id, flags, edns, repinfo); + if(ret != 1) + return ret; + } +#endif /* return this delegation from the cache */ edns_bak = *edns; edns->edns_version = EDNS_ADVERTISED_VERSION; @@ -711,6 +733,23 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo, *is_secure_answer = 0; } } else *is_secure_answer = 0; +#ifdef ENABLE_FASTRPZ + if(repinfo->rpz) { + /* Scan the cached answer for RPZ hits. + * ret=1 use cache entry + * ret=-1 rewritten response already sent or dropped + * ret=0 deny a cached entry exists + */ + int ret = rpz_worker_cache(worker, rep, qinfo, id, flags, edns, + repinfo); + if(ret != 1) { + rrset_array_unlock_touch(worker->env.rrset_cache, + worker->scratchpad, rep->ref, + rep->rrset_count); + return ret; + } + } +#endif edns_bak = *edns; edns->edns_version = EDNS_ADVERTISED_VERSION; @@ -1436,6 +1475,15 @@ worker_handle_request(struct comm_point* c, void* arg, int error, log_addr(VERB_ALGO, "refused nonrec (cache snoop) query from", &repinfo->addr, repinfo->addrlen); goto send_reply; +#ifdef ENABLE_FASTRPZ + } else { + /* Start to rewrite for response policy zones. + * This can hit a qname trigger and be done. */ + if(rpz_start(worker, &qinfo, repinfo, &edns)) { + regional_free_all(worker->scratchpad); + return 0; + } +#endif } /* If we've found a local alias, replace the qname with the alias @@ -1486,12 +1534,21 @@ lookup_cache: h = query_info_hash(lookup_qinfo, sldns_buffer_read_u16_at(c->buffer, 2)); if((e=slabhash_lookup(worker->env.msg_cache, h, lookup_qinfo, 0))) { /* answer from cache - we have acquired a readlock on it */ - if(answer_from_cache(worker, &qinfo, + ret = answer_from_cache(worker, &qinfo, cinfo, &need_drop, &is_expired_answer, &is_secure_answer, &alias_rrset, &partial_rep, (struct reply_info*)e->data, *(uint16_t*)(void *)sldns_buffer_begin(c->buffer), sldns_buffer_read_u16_at(c->buffer, 2), repinfo, - &edns)) { + &edns); +#ifdef ENABLE_FASTRPZ + if(ret < 0) { + /* RPZ already dropped or sent a response. */ + lock_rw_unlock(&e->lock); + regional_free_all(worker->scratchpad); + return 0; + } +#endif + if(ret) { /* prefetch it if the prefetch TTL expired. * Note that if there is more than one pass * its qname must be that used for cache @@ -1548,11 +1605,19 @@ lookup_cache: lock_rw_unlock(&e->lock); } if(!LDNS_RD_WIRE(sldns_buffer_begin(c->buffer))) { - if(answer_norec_from_cache(worker, &qinfo, + ret = answer_norec_from_cache(worker, &qinfo, *(uint16_t*)(void *)sldns_buffer_begin(c->buffer), sldns_buffer_read_u16_at(c->buffer, 2), repinfo, - &edns)) { + &edns); + if(ret) { regional_free_all(worker->scratchpad); +#ifdef ENABLE_FASTRPZ + if(ret < 0) { + /* RPZ already dropped + * or sent a response. */ + return 0; + } +#endif goto send_reply; } verbose(VERB_ALGO, "answer norec from cache -- " diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index cd43f04e..b92a1af8 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -1878,6 +1878,81 @@ List domain for which the AAAA records are ignored and the A record is used by dns64 processing instead. Can be entered multiple times, list a new domain for which it applies, one per line. Applies also to names underneath the name given. +.SS "Response Policy Zone Rewriting" +.LP +Response policy zone rewriting is controlled with the +.B rpz +clause. +It must contain a +.B rpz\-enable: +option, and one or more +.B rpz\-zone: +options. +It will usually also contain +.B rpz\-option: +clauses with general rewriting options or specifying dnsrpzd parameters. +Beneath the surface, the text in +.B rpz\-zone: \fI<"domain">\fR +is converted to \fI"zone domain\\n"\fR and added to the configuration string +given to +\fIlibrpz\fR(3). +The text in +.B rpz-option \fI<"text">\fR +is also added to that configuration string. +.LP +If using chroot, then the chroot directory must contain the \fIdnsrpzd\fR(3) +command and the shared libraries that it uses. +Those can be found with the \fIldd\fR(1) command. +.LP +Resolver zone and rewriting options and response policy zone triggers and +actions are described in \fIlibrpz\fR(3). +The separate control file that specifies the policy zones maintained by +the dnsrpzd daemon is described in \fIdnsrpzd\fR(8). +.LP +Many installations need a local whitelist that exempts local +domains from rewriting. +Whitelist records can be in zones transferred by dnsrpzd from +authorities or in a local zone file. +.TP +.B rpz-enable: \fI +enables Fastrpz. +If not enabled, the other options in the +.B rpz: +clause are ignored. +.TP +.B rpz-zone: \fI<"zone and options"> +specifies a policy zone and optional per-zone rewriting parameters. +.TP +.B rpz-option: \fI<"option"> +specifies general Fastrpz options. +.LP +Fastrpz is available only on POSIX compliant UNIX-like systems with the +\fImmap\fR(2) system call. +.LP +Fastrpz in Unbound differs from rpz and fastrpz in BIND by +.RS 3 +.HP 4 +RPZ-CLIENT-IP triggers can only be used in the first policy zone +specified with +.B rpz-zone: +.HP +Policy zone rewriting is disabled by the DO bit in DNS requests +even when no DNSSEC signatures are supplied by authorities. +.HP +Unbound local zones are not subject to rpz rewriting. +.HP +Like Fastrpz with BIND but unlike classic BIND rpz, +the ADDITIONAL sections of rewritten responses contain the SOA record from +the policy zone used to rewrite the response. +.RE +.P +.nf +# example Fastrpz settings for use with chroot on Freebsd +rpz: + rpz-zone: "rpz.example.org" + rpz-zone: "other.rpz.example.org ip-as-ns yes" + rpz-option: "dnsrpzd ./dnsrpzd" +.fi .SS "DNSCrypt Options" .LP The diff --git a/fastrpz/librpz.h b/fastrpz/librpz.h new file mode 100644 index 00000000..645279d1 --- /dev/null +++ b/fastrpz/librpz.h @@ -0,0 +1,957 @@ +/* + * Define the interface from a DNS resolver to the Response Policy Zone + * library, librpz. + * + * This file should be included only the interface functions between the + * resolver and librpz to avoid name space pollution. + * + * Copyright (c) 2016-2017 Farsight Security, Inc. + * + * 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. + * + * Fastrpz version 1.2.10 + */ + +#ifndef LIBRPZ_H +#define LIBRPZ_H + +#include +#include +#include +#include +#include +#include + + +/* + * Allow either ordinary or dlopen() linking. + */ +#ifdef LIBRPZ_INTERNAL +#define LIBDEF(t,s) extern t s; +#define LIBDEF_F(f) LIBDEF(librpz_##f##_t, librpz_##f) +#else +#define LIBDEF(t,s) +#define LIBDEF_F(f) +#endif + +/* + * Response Policy Zone triggers. + * Comparisons of trigger precedences require + * LIBRPZ_TRIG_CLIENT_IP < LIBRPZ_TRIG_QNAME < LIBRPZ_TRIG_IP + * < LIBRPZ_TRIG_NSDNAME < LIBRPZ_TRIG_NSIP} + */ +typedef enum { + LIBRPZ_TRIG_BAD =0, + LIBRPZ_TRIG_CLIENT_IP =1, + LIBRPZ_TRIG_QNAME =2, + LIBRPZ_TRIG_IP =3, + LIBRPZ_TRIG_NSDNAME =4, + LIBRPZ_TRIG_NSIP =5 +} librpz_trig_t; +#define LIBRPZ_TRIG_SIZE 3 /* sizeof librpz_trig_t in bits */ +typedef uint8_t librpz_tbit_t; /* one bit for each of the TRIGS_NUM + * trigger types */ + + +/* + * Response Policy Zone Actions or policies + */ +typedef enum { + LIBRPZ_POLICY_UNDEFINED =0, /* an empty entry or no decision yet */ + LIBRPZ_POLICY_DELETED =1, /* placeholder for a deleted policy */ + + LIBRPZ_POLICY_PASSTHRU =2, /* 'passthru': do not rewrite */ + LIBRPZ_POLICY_DROP =3, /* 'drop': do not respond */ + LIBRPZ_POLICY_TCP_ONLY =4, /* 'tcp-only': answer UDP with TC=1 */ + LIBRPZ_POLICY_NXDOMAIN =5, /* 'nxdomain': answer with NXDOMAIN */ + LIBRPZ_POLICY_NODATA =6, /* 'nodata': answer with ANCOUNT=0 */ + LIBRPZ_POLICY_RECORD =7, /* rewrite with the policy's RR */ + + /* only in client configurations to override the zone */ + LIBRPZ_POLICY_GIVEN, /* 'given': what policy record says */ + LIBRPZ_POLICY_DISABLED, /* at most log */ + LIBRPZ_POLICY_CNAME, /* answer with 'cname x' */ +} librpz_policy_t; +#define LIBRPZ_POLICY_BITS 4 + +/* + * Special policies that appear as targets of CNAMEs + * NXDOMAIN is signaled by a CNAME with a "." target. + * NODATA is signaled by a CNAME with a "*." target. + */ +#define LIBRPZ_RPZ_PREFIX "rpz-" +#define LIBRPZ_RPZ_PASSTHRU LIBRPZ_RPZ_PREFIX"passthru" +#define LIBRPZ_RPZ_DROP LIBRPZ_RPZ_PREFIX"drop" +#define LIBRPZ_RPZ_TCP_ONLY LIBRPZ_RPZ_PREFIX"tcp-only" + + +typedef uint16_t librpz_dznum_t; /* dnsrpzd zone # in [0,DZNUM_MAX] */ +typedef uint8_t librpz_cznum_t; /* client zone # in [0,CZNUM_MAX] */ + + +/* + * CIDR block + */ +typedef struct librpz_prefix { + union { + struct in_addr in; + struct in6_addr in6; + } addr; + uint8_t family; + uint8_t len; +} librpz_prefix_t; + +/* + * A domain + */ +typedef uint8_t librpz_dsize_t; +typedef struct librpz_domain { + librpz_dsize_t size; /* of only .d */ + uint8_t d[0]; /* variable length wire format */ +} librpz_domain_t; + +/* + * A maximal domain buffer + */ +typedef struct librpz_domain_buf { + librpz_dsize_t size; + uint8_t d[NS_MAXCDNAME]; +} librpz_domain_buf_t; + +/* + * A resource record without the owner name. + * C compilers say that sizeof(librpz_rr_t)=12 instead of 10. + */ +typedef struct { + uint16_t type; /* network byte order */ + uint16_t class; /* network byte order */ + uint32_t ttl; /* network byte order */ + uint16_t rdlength; /* network byte order */ + uint8_t rdata[0]; /* variable length */ +} librpz_rr_t; + +/* + * The database file might be mapped with different starting addresses + * by concurrent clients (resolvers), and so all pointers are offsets. + */ +typedef uint32_t librpz_idx_t; +#define LIBRPZ_IDX_NULL 0 +#define LIBRPZ_IDX_MIN 1 +#define LIBRPZ_IDX_BAD ((librpz_idx_t)-1) +/** + * Partial decoded results of a set of RPZ queries for a single DNS response + * or interation through the mapped file. + */ +typedef int16_t librpz_result_id_t; +typedef struct librpz_result { + librpz_idx_t next_rr; + librpz_result_id_t hit_id; /* trigger ID from resolver */ + librpz_policy_t zpolicy; /* policy from zone */ + librpz_policy_t policy; /* adjusted by client configuration */ + librpz_dznum_t dznum; /* dnsrpzd zone number */ + librpz_cznum_t cznum; /* librpz client zone number */ + librpz_trig_t trig:LIBRPZ_TRIG_SIZE; + bool log:1; /* log rewrite given librpz_log_level */ +} librpz_result_t; + + +/** + * librpz trace or log levels. + */ +typedef enum { + LIBRPZ_LOG_FATAL =0, /* always print fatal errors */ + LIBRPZ_LOG_ERROR =1, /* errors have this level */ + LIBRPZ_LOG_TRACE1 =2, /* big events such as dnsrpzd starts */ + LIBRPZ_LOG_TRACE2 =3, /* smaller dnsrpzd zone transfers */ + LIBRPZ_LOG_TRACE3 =4, /* librpz hits */ + LIBRPZ_LOG_TRACE4 =5, /* librpz lookups */ + LIBRPZ_LOG_INVALID =999, +} librpz_log_level_t; +typedef librpz_log_level_t (librpz_log_level_val_t)(librpz_log_level_t level); +LIBDEF_F(log_level_val) + +/** + * Logging function that can be supplied by the resolver. + * @param level is one of librpz_log_level_t + * @param ctx is for use by the resolver's logging system. + * NULL mean a context-free message. + */ +typedef void(librpz_log_fnc_t)(librpz_log_level_t level, void *ctx, + const char *buf); + +/** + * Point librpz logging functions to the resolver's choice. + */ +typedef void (librpz_set_log_t)(librpz_log_fnc_t *new_log, const char *prog_nm); +LIBDEF_F(set_log) + + +/** + * librpz error messages are put in these buffers. + * Use a structure intead of naked char* to let the compiler check the length. + * A function defined with "foo(char buf[120])" can be called with + * "char sbuf[2]; foo(sbuf)" and suffer a buffer overrun. + */ +typedef struct { + char c[120]; +} librpz_emsg_t; + + +#ifdef LIBRPZ_HAVE_ATTR +#define LIBRPZ_UNUSED __attribute__((unused)) +#define LIBRPZ_PF(f,l) __attribute__((format(printf,f,l))) +#define LIBRPZ_NORET __attribute__((__noreturn__)) +#else +#define LIBRPZ_UNUSED +#define LIBRPZ_PF(f,l) +#define LIBRPZ_NORET +#endif + +#ifdef HAVE_BUILTIN_EXPECT +#define LIBRPZ_LIKELY(c) __builtin_expect(!!(c), 1) +#define LIBRPZ_UNLIKELY(c) __builtin_expect(!!(c), 0) +#else +#define LIBRPZ_LIKELY(c) (c) +#define LIBRPZ_UNLIKELY(c) (c) +#endif + +typedef bool (librpz_parse_log_opt_t)(librpz_emsg_t *emsg, const char *arg); +LIBDEF_F(parse_log_opt) + +typedef void (librpz_vpemsg_t)(librpz_emsg_t *emsg, + const char *p, va_list args); +LIBDEF_F(vpemsg) +typedef void (librpz_pemsg_t)(librpz_emsg_t *emsg, + const char *p, ...) LIBRPZ_PF(2,3); +LIBDEF_F(pemsg) + +typedef void (librpz_vlog_t)(librpz_log_level_t level, void *ctx, + const char *p, va_list args); +LIBDEF_F(vlog) +typedef void (librpz_log_t)(librpz_log_level_t level, void *ctx, + const char *p, ...) LIBRPZ_PF(3,4); +LIBDEF_F(log) + +typedef void (librpz_fatal_t)(int ex_code, + const char *p, ...) LIBRPZ_PF(2,3); +extern void librpz_fatal(int ex_code, + const char *p, ...) LIBRPZ_PF(2,3) LIBRPZ_NORET; + +typedef void (librpz_rpz_assert_t)(const char *file, unsigned line, + const char *p, ...) LIBRPZ_PF(3,4); +extern void librpz_rpz_assert(const char *file, unsigned line, + const char *p, ...) LIBRPZ_PF(3,4) LIBRPZ_NORET; + +typedef void (librpz_rpz_vassert_t)(const char *file, uint line, + const char *p, va_list args); +extern void librpz_rpz_vassert(const char *file, uint line, + const char *p, va_list args) LIBRPZ_NORET; + + +/* + * As far as clients are concerned, all relative pointers or indexes in a + * version of the mapped file except trie node parent pointers remain valid + * forever. A client must release a version so that it can be garbage + * collected by the file system. When dnsrpzd needs to expand the file, + * it copies the old file to a new, larger file. Clients can continue + * using the old file. + * + * Versions can also appear in a single file. Old nodes and trie values + * within the file are not destroyed until all clients using the version + * that contained the old values release the version. + * + * A client is marked as using version by connecting to the deamon. It is + * marked as using all subsequent versions. A client releases all versions + * by closing the connection or a range of versions by updating is slot + * in the shared memory version table. + * + * As far as clients are concerned, there are the following possible librpz + * failures: + * - malloc() or other fatal internal librpz problems indicated by + * a failing return from a librpz function + * All operations will fail until client handle is destroyed and + * recreated with librpz_client_detach() and librpz_client_create(). + * - corrupt database detected by librpz code, corrupt database detected + * by dnsrpzd, or disconnection from the daemon. + * Current operations will fail. + * + * Clients assume that the file has already been unlinked before + * the corrupt flag is set so that they do not race with the server + * over the corruption of a single file. A client that finds the + * corrupt set knows that dnsrpzd has already crashed with + * abort() and is restarting. The client can re-connect to dnsrpzd + * and retransmit its configuration, backing off as usual if anything + * goes wrong. + * + * Searchs of the database by a client do not need locks against dnsrpzd or + * other clients, but a lock is used to protect changes to the connection + * by competing threads in the client. The client provides fuctions + * to serialize the conncurrent use of any single client handle. + * Functions that do nothing are appropriate for applications that are + * not "threaded" or that do not share client handles among threads. + * Otherwise, functions must be provided to librpz_clientcreate(). + * Something like the following works with pthreads: + * + * static void + * lock(void *mutex) { assert(pthread_mutex_lock(mutex) == 0); } + * + * static void + * unlock(void *mutex) { assert(pthread_mutex_unlock(mutex) == 0); } + * + * static void + * mutex_destroy(void *mutex) { assert(pthread_mutex_destroy(mutex) == 0); } + * + * + * + * At every instant, all of the data and pointers in the mapped file are valid. + * Changes to trie node or other data are always made so that it and + * all pointers in and to it remain valid for a time. Old versions are + * eventually discarded. + * + * Dnsrpzd periodically defines a new version by setting asside all changes + * made since the previous version was defined. Subsequent changes + * made (only!) by dnsrpzd will be part of the next version. + * + * To discard an old version, dnsrpzd must know that all clients have stopped + * using that version. Clients do that by using part of the mapped file + * to tell dnsrpzd the oldest version that each client is using. + * Dnsrpzd assigns each connecting client an entry in the cversions array + * in the mapped file. The client puts version numbers into that entry + * to signal to dnsrpzd which versions that can be discarded. + * Dnsrpzd is free, as far as that client is concerned, to discard all + * numerically smaller versions. A client can disclaim all versions with + * the version number VERSIONS_ALL or 0. + * + * The race between a client changing its entry and dnsrpzd discarding a + * version is resolved by allowing dnsrpzd to discard all versions + * smaller or equal to the client's version number. If dnsrpzd is in + * the midst of discarding or about to discard version N when the + * client asserts N, no harm is done. The client depends only on + * the consistency of version N+1. + * + * This version mechanism depends in part on not being exercised too frequently + * Version numbers are 32 bits long and dnsrpzd creates new versions + * at most once every 30 seconds. + */ + + +/* + * Lock functions for concurrent use of a single librpz_client_t client handle. + */ +typedef void(librpz_mutex_t)(void *mutex); + +/* + * List of connections to dnsrpzd daemons. + */ +typedef struct librpz_clist librpz_clist_t; + +/* + * Client's handle on dnsrpzd. + */ +typedef struct librpz_client librpz_client_t; + +/** + * Create the list of connections to the dnsrpzd daemon. + * @param[out] emsg: error message + * @param lock: start exclusive access to the client handle + * @param unlock: end exclusive access to the client handle + * @param mutex_destroy: release the lock + * @param mutex: pointer to the lock for the client handle + * @param log_ctx: NULL or resolver's context log messages + */ +typedef librpz_clist_t *(librpz_clist_create_t)(librpz_emsg_t *emsg, + librpz_mutex_t *lock, + librpz_mutex_t *unlock, + librpz_mutex_t *mutex_destroy, + void *mutex, void *log_ctx); +LIBDEF_F(clist_create) + + +/** + * Release the list of dnsrpzd connections. + */ +typedef void (librpz_clist_detach_t)(librpz_clist_t **clistp); +LIBDEF_F(clist_detach) + +/** + * Create a librpz client handle. + * @param[out] emsg: error message + * @param: list of dnsrpzd connections + * @param cstr: string of configuration settings separated by ';' or '\n' + * @param use_expired: true to not ignore expired zones + * @return client handle or NULL if the handle could not be created + */ +typedef librpz_client_t *(librpz_client_create_t)(librpz_emsg_t *emsg, + librpz_clist_t *clist, + const char *cstr, + bool use_expired); +LIBDEF_F(client_create) + +/** + * Start (if necessary) dnsrpzd and connect to it. + * @param[out] emsg: error message + * @param client handle + * @param optional: true if it is ok if starting the daemon is not allowed + */ +typedef bool (librpz_connect_t)(librpz_emsg_t *emsg, librpz_client_t *client, + bool optional); +LIBDEF_F(connect) + +/** + * Start to destroy a librpz client handle. + * It will not be destroyed until the last set of RPZ queries represented + * by a librpz_rsp_t ends. + * @param client handle to be released + * @return false on error + */ +typedef void (librpz_client_detach_t)(librpz_client_t **clientp); +LIBDEF_F(client_detach) + +/** + * State for a set of RPZ queries for a single DNS response + * or for listing the database. + */ +typedef struct librpz_rsp librpz_rsp_t; + +/** + * Start a set of RPZ queries for a single DNS response. + * @param[out] emsg: error message for false return or *rspp=NULL + * @param[out] rspp created context or NULL + * @param[out] min_ns_dotsp: NULL or pointer to configured MIN-NS-DOTS value + * @param client state + * @param have_rd: RD=1 in the DNS request + * @param have_do: DO=1 in the DNS request + * @return false on error + */ +typedef bool (librpz_rsp_create_t)(librpz_emsg_t *emsg, librpz_rsp_t **rspp, + int *min_ns_dotsp, librpz_client_t *client, + bool have_rd, bool have_do); +LIBDEF_F(rsp_create) + +/** + * Finish RPZ work for a DNS response. + */ +typedef void (librpz_rsp_detach_t)(librpz_rsp_t **rspp); +LIBDEF_F(rsp_detach) + +/** + * Get the final, accumulated result of a set of RPZ queries. + * Yield LIBRPZ_POLICY_UNDEFINED if + * - there were no hits, + * - there was a dispositive hit, be we have not recursed and are required + * to recurse so that evil DNS authories will not know we are using RPZ + * - we have a hit and have recursed, but later data such as NSIP could + * override + * @param[out] emsg + * @param[out] result describes the hit + * or result->policy=LIBRPZ_POLICY_UNDEFINED without a hit + * @param[out] result: current policy rewrite values + * @param recursed: recursion has now been done even if it was not done + * when the hit was found + * @param[in,out] rsp state from librpz_itr_start() + * @return false on error + */ +typedef bool (librpz_rsp_result_t)(librpz_emsg_t *emsg, librpz_result_t *result, + bool recursed, const librpz_rsp_t *rsp); +LIBDEF_F(rsp_result) + +/** + * Might looking for a trigger be worthwhile? + * @param trig: look for this type of trigger + * @param ipv6: true if trig is LIBRPZ_TRIG_CLIENT_IP, LIBRPZ_TRIG_IP, + * or LIBRPZ_TRIG_NSIP and the IP address is IPv6 + * @return: true if looking could be worthwhile + */ +typedef bool (librpz_have_trig_t)(librpz_trig_t trig, bool ipv6, + const librpz_rsp_t *rsp); +LIBDEF_F(have_trig) + +/** + * Might looking for NSDNAME and NSIP triggers be worthwhile? + * @return: true if looking could be worthwhile + */ +typedef bool (librpz_have_ns_trig_t)(const librpz_rsp_t *rsp); +LIBDEF_F(have_ns_trig) + +/** + * Convert the found client IP trie key to a CIDR block + * @param[out] emsg + * @param[out] prefix trigger + * @param[in,out] rsp state from librpz_itr_start() + * @return false on error + */ +typedef bool (librpz_rsp_clientip_prefix_t)(librpz_emsg_t *emsg, + librpz_prefix_t *prefix, + librpz_rsp_t *rsp); +LIBDEF_F(rsp_clientip_prefix) + +/** + * Compute the owner name of the found or result trie key, usually to log it. + * An IP address key might be returned as 8.0.0.0.127.rpz-client-ip. + * example.com. might be a qname trigger. example.com.rpz-nsdname. could + * be an NSDNAME trigger. + * @param[out] emsg + * @param[out] owner domain + * @param[in,out] rsp state from librpz_itr_start() + * @return false on error + */ +typedef bool (librpz_rsp_domain_t)(librpz_emsg_t *emsg, + librpz_domain_buf_t *owner, + librpz_rsp_t *rsp); +LIBDEF_F(rsp_domain) + +/** + * Get the next RR of the LIBRPZ_POLICY_RECORD result after an initial use of + * librpz_rsp_result() or librpz_itr_node() or after a previous use of + * librpz_rsp_rr(). The RR is in uncompressed wire format including type, + * class, ttl and length in network byte order. + * @param[out] emsg + * @param[out] typep: optional host byte order record type or ns_t_invalid (0) + * @param[out] classp: class such as ns_c_in + * @param[out] ttlp: TTL + * @param[out] rrp: optionall malloc() buffer containting the next RR or + * NULL after the last RR + * @param[out] result: current policy rewrite values + * @param qname: used construct a wildcard CNAME + * @param qname_size + * @param[in,out] rsp state from librpz_itr_start() + * @return false on error + */ +typedef bool (librpz_rsp_rr_t)(librpz_emsg_t *emsg, uint16_t *typep, + uint16_t *classp, uint32_t *ttlp, + librpz_rr_t **rrp, librpz_result_t *result, + const uint8_t *qname, size_t qname_size, + librpz_rsp_t *rsp); +LIBDEF_F(rsp_rr) + +/** + * Get the next RR of the LIBRPZ_POLICY_RECORD result. + * @param[out] emsg + * @param[out] ttlp: TTL + * @param[out] rrp: malloc() buffer with SOA RR without owner name + * @param[out] result: current policy rewrite values + * @param[out] origin: SOA owner name + * @param[out] origin_size + * @param[in,out] rsp state from librpz_itr_start() + * @return false on error + */ +typedef bool (librpz_rsp_soa_t)(librpz_emsg_t *emsg, uint32_t *ttlp, + librpz_rr_t **rrp, librpz_domain_buf_t *origin, + librpz_result_t *result, librpz_rsp_t *rsp); +LIBDEF_F(rsp_soa) + +/** + * Get the SOA serial number for a policy zone to compare with a known value + * to check whether a zone tranfer is complete. + */ +typedef bool (librpz_soa_serial_t)(librpz_emsg_t *emsg, uint32_t *serialp, + const char *domain_nm, librpz_rsp_t *rsp); +LIBDEF_F(soa_serial) + +/** + * Save the current policy checking state. + * @param[out] emsg + * @param[in,out] rsp state from librpz_itr_start() + * @return false on error + */ +typedef bool (librpz_rsp_push_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp); +LIBDEF_F(rsp_push) +#define LIBRPZ_RSP_STACK_DEPTH 3 + +/** + * Restore the previous policy checking state. + * @param[out] emsg + * @param[out] result: NULL or restored policy rewrite values + * @param[in,out] rsp state from librpz_itr_start() + * @return false on error + */ +typedef bool (librpz_rsp_pop_t)(librpz_emsg_t *emsg, librpz_result_t *result, + librpz_rsp_t *rsp); +LIBDEF_F(rsp_pop) + +/** + * Discard the most recently save policy checking state. + * @param[out] emsg + * @param[out] result: NULL or restored policy rewrite values + * @return false on error + */ +typedef bool (librpz_rsp_pop_discard_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp); +LIBDEF_F(rsp_pop_discard) + +/** + * Disable a zone. + * @param[out] emsg + * @param znum + * @param[in,out] rsp state from librpz_itr_start() + * @return false on error + */ +typedef bool (librpz_rsp_forget_zone_t)(librpz_emsg_t *emsg, + librpz_cznum_t znum, librpz_rsp_t *rsp); +LIBDEF_F(rsp_forget_zone) + +/** + * Apply RPZ to an IP address. + * @param[out] emsg + * @param addr: address to check + * @param ipv6: true for 16 byte IPv6 instead of 4 byte IPv4 + * @param trig LIBRPZ_TRIG_CLIENT_IP, LIBRPZ_TRIG_IP, or LIBRPZ_TRIG_NSIP + * @param hit_id: caller chosen + * @param recursed: recursion has been done + * @param[in,out] rsp state from librpz_itr_start() + * @return false on error + */ +typedef bool (librpz_ck_ip_t)(librpz_emsg_t *emsg, + const void *addr, uint family, + librpz_trig_t trig, librpz_result_id_t hit_id, + bool recursed, librpz_rsp_t *rsp); +LIBDEF_F(ck_ip) + +/** + * Apply RPZ to a wire-format domain. + * @param[out] emsg + * @param domain in wire format + * @param domain_size + * @param trig LIBRPZ_TRIG_QNAME or LIBRPZ_TRIG_NSDNAME + * @param hit_id: caller chosen + * @param recursed: recursion has been done + * @param[in,out] rsp state from librpz_itr_start() + * @return false on error + */ +typedef bool (librpz_ck_domain_t)(librpz_emsg_t *emsg, + const uint8_t *domain, size_t domain_size, + librpz_trig_t trig, librpz_result_id_t hit_id, + bool recursed, librpz_rsp_t *rsp); +LIBDEF_F(ck_domain) + +/** + * Ask dnsrpzd to refresh a zone. + * @param[out] emsg error message + * @param librpz_domain_t domain to refresh + * @param client context + * @return false after error + */ +typedef bool (librpz_zone_refresh_t)(librpz_emsg_t *emsg, const char *domain, + librpz_rsp_t *rsp); +LIBDEF_F(zone_refresh) + +/** + * Get a string describing the the databasse + * @param license: include the license + * @param cfiles: include the configuration file names + * @param listens: include the local notify IP addresses + * @param[out] emsg error message if the result is null + * @param client context + * @return malloc'ed string or NULL after error + */ +typedef char *(librpz_db_info_t)(librpz_emsg_t *emsg, + bool license, bool cfiles, bool listens, + librpz_rsp_t *rsp); +LIBDEF_F(db_info) + +/** + * Start a context for listing the nodes and/or zones in the mapped file + * @param[out] emsg: error message for false return or *rspp=NULL + * @param[out[ rspp created context or NULL + * @param client context + * @return false after error + */ +typedef bool (librpz_itr_start_t)(librpz_emsg_t *emsg, librpz_rsp_t **rspp, + librpz_client_t *client); +LIBDEF_F(itr_start) + +/** + * Get mapped file memory allocation statistics. + * @param[out] emsg: error message + * @param rsp state from librpz_itr_start() + * @return malloc'ed string or NULL after error + */ +typedef char *(librpz_mf_stats_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp); +LIBDEF_F(mf_stats) + +/** + * Get versions currently used by clients. + * @param[out] emsg: error message + * @param[in,out] rsp: state from librpz_itr_start() + * @return malloc'ed string or NULL after error + */ +typedef char *(librpz_vers_stats_t)(librpz_emsg_t *emsg, librpz_rsp_t *rsp); +LIBDEF_F(vers_stats) + +/** + * Allocate a string describing the next zone or "" after the last zone. + * @param[out] emsg + * @param all_zones to list all instead of only requested zones + * @param[in,out] rsp state from librpz_rsp_start() + * @return malloc'ed string or NULL after error + */ +typedef char *(librpz_itr_zone_t)(librpz_emsg_t *emsg, bool all_zones, + librpz_rsp_t *rsp); +LIBDEF_F(itr_zone) + +/** + * Describe the next trie node while dumping the database. + * @param[out] emsg + * @param[out] result describes node + * or result->policy=LIBRPZ_POLICY_UNDEFINED after the last node. + * @param all_zones to list all instead of only requested zones + * @param[in,out] rsp state from librpz_itr_start() + * @return: false on error + */ +typedef bool (librpz_itr_node_t)(librpz_emsg_t *emsg, librpz_result_t *result, + bool all_zones, librpz_rsp_t *rsp); +LIBDEF_F(itr_node) + +/** + * RPZ policy to string with a backup buffer of POLICY2STR_SIZE size + */ +typedef const char *(librpz_policy2str_t)(librpz_policy_t policy, + char *buf, size_t buf_size); +#define POLICY2STR_SIZE sizeof("policy xxxxxx") +LIBDEF_F(policy2str) + +/** + * Trigger type to string. + */ +typedef const char *(librpz_trig2str_t)(librpz_trig_t trig); +LIBDEF_F(trig2str) + +/** + * Convert a number of seconds to a zone file duration string + */ +typedef const char *(librpz_secs2str_t)(time_t secs, + char *buf, size_t buf_size); +#define SECS2STR_SIZE sizeof("1234567w7d24h59m59s") +LIBDEF_F(secs2str) + +/** + * Parse a duration with 's', 'm', 'h', 'd', and 'w' units. + */ +typedef bool (librpz_str2secs_t)(librpz_emsg_t *emsg, time_t *val, + const char *str0); +LIBDEF_F(str2secs) + +/** + * Translate selected rtypes to strings + */ +typedef const char *(librpz_rtype2str_t)(uint type, char *buf, size_t buf_size); +#define RTYPE2STR_SIZE sizeof("type xxxxx") +LIBDEF_F(rtype2str) + +/** + * Local version of ns_name_ntop() for portability. + */ +typedef int (librpz_domain_ntop_t)(const u_char *src, char *dst, size_t dstsiz); +LIBDEF_F(domain_ntop) + +/** + * Local version of ns_name_pton(). + */ +typedef int (librpz_domain_pton2_t)(const char *src, u_char *dst, size_t dstsiz, + size_t *dstlen, bool lower); +LIBDEF_F(domain_pton2) + +typedef union socku socku_t; +typedef socku_t *(librpz_mk_inet_su_t)(socku_t *su, const struct in_addr *addrp, + in_port_t port); +LIBDEF_F(mk_inet_su) + +typedef socku_t *(librpz_mk_inet6_su_t)(socku_t *su, const + struct in6_addr *addrp, + uint32_t scope_id, in_port_t port); +LIBDEF_F(mk_inet6_su) + +typedef bool (librpz_str2su_t)(socku_t *sup, const char *str); +LIBDEF_F(str2su) + +typedef char *(librpz_su2str_t)(char *str, size_t str_len, const socku_t *su); +LIBDEF_F(su2str) +#define SU2STR_SIZE (INET6_ADDRSTRLEN+1+6+1) + + +/** + * default path to dnsrpzd + */ +const char *librpz_dnsrpzd_path; + + +#undef LIBDEF + +/* + * This is the dlopen() interface to librpz. + */ +typedef const struct { + const char *dnsrpzd_path; + const char *version; + librpz_parse_log_opt_t *parse_log_opt; + librpz_log_level_val_t *log_level_val; + librpz_set_log_t *set_log; + librpz_vpemsg_t *vpemsg; + librpz_pemsg_t *pemsg; + librpz_vlog_t *vlog; + librpz_log_t *log; + librpz_fatal_t *fatal LIBRPZ_NORET; + librpz_rpz_assert_t *rpz_assert LIBRPZ_NORET; + librpz_rpz_vassert_t *rpz_vassert LIBRPZ_NORET; + librpz_clist_create_t *clist_create; + librpz_clist_detach_t *clist_detach; + librpz_client_create_t *client_create; + librpz_connect_t *connect; + librpz_client_detach_t *client_detach; + librpz_rsp_create_t *rsp_create; + librpz_rsp_detach_t *rsp_detach; + librpz_rsp_result_t *rsp_result; + librpz_have_trig_t *have_trig; + librpz_have_ns_trig_t *have_ns_trig; + librpz_rsp_clientip_prefix_t *rsp_clientip_prefix; + librpz_rsp_domain_t *rsp_domain; + librpz_rsp_rr_t *rsp_rr; + librpz_rsp_soa_t *rsp_soa; + librpz_soa_serial_t *soa_serial; + librpz_rsp_push_t *rsp_push; + librpz_rsp_pop_t *rsp_pop; + librpz_rsp_pop_discard_t *rsp_pop_discard; + librpz_rsp_forget_zone_t *rsp_forget_zone; + librpz_ck_ip_t *ck_ip; + librpz_ck_domain_t *ck_domain; + librpz_zone_refresh_t *zone_refresh; + librpz_db_info_t *db_info; + librpz_itr_start_t *itr_start; + librpz_mf_stats_t *mf_stats; + librpz_vers_stats_t *vers_stats; + librpz_itr_zone_t *itr_zone; + librpz_itr_node_t *itr_node; + librpz_policy2str_t *policy2str; + librpz_trig2str_t *trig2str; + librpz_secs2str_t *secs2str; + librpz_str2secs_t *str2secs; + librpz_rtype2str_t *rtype2str; + librpz_domain_ntop_t *domain_ntop; + librpz_domain_pton2_t *domain_pton2; + librpz_mk_inet_su_t *mk_inet_su; + librpz_mk_inet6_su_t *mk_inet6_su; + librpz_str2su_t *str2su; + librpz_su2str_t *su2str; +} librpz_0_t; +extern librpz_0_t librpz_def_0; + +/* + * Future versions can be upward compatible by defining LIBRPZ_DEF as + * librpz_X_t. + */ +#define LIBRPZ_DEF librpz_def_0 +#define LIBRPZ_DEF_STR "librpz_def_0" + +typedef librpz_0_t librpz_t; +extern librpz_t *librpz; + + +#if LIBRPZ_LIB_OPEN == 2 +#include + +/** + * link-load librpz + * @param[out] emsg: error message + * @param[in,out] dl_handle: NULL or pointer to new dlopen handle + * @param[in] path: librpz.so path + * @return address of interface structure or NULL on failure + */ +static inline librpz_t * +librpz_lib_open(librpz_emsg_t *emsg, void **dl_handle, const char *path) +{ + void *handle; + librpz_t *new_librpz; + + emsg->c[0] = '\0'; + + /* + * Close a previously opened handle on librpz.so. + */ + if (dl_handle != NULL && *dl_handle != NULL) { + if (dlclose(*dl_handle) != 0) { + snprintf(emsg->c, sizeof(librpz_emsg_t), + "dlopen(NULL): %s", dlerror()); + return (NULL); + } + *dl_handle = NULL; + } + + /* + * First try the main executable of the process in case it was + * linked to librpz. + * Do not worry if we cannot search the main executable of the process. + */ + handle = dlopen(NULL, RTLD_NOW | RTLD_LOCAL); + if (handle != NULL) { + new_librpz = dlsym(handle, LIBRPZ_DEF_STR); + if (new_librpz != NULL) { + if (dl_handle != NULL) + *dl_handle = handle; + return (new_librpz); + } + if (dlclose(handle) != 0) { + snprintf(emsg->c, sizeof(librpz_emsg_t), + "dlsym(NULL, "LIBRPZ_DEF_STR"): %s", + dlerror()); + return (NULL); + } + } + + if (path == NULL || path[0] == '\0') { + snprintf(emsg->c, sizeof(librpz_emsg_t), + "librpz not linked and no dlopen() path provided"); + return (NULL); + } + + handle = dlopen(path, RTLD_NOW | RTLD_LOCAL); + if (handle == NULL) { + snprintf(emsg->c, sizeof(librpz_emsg_t), "dlopen(%s): %s", + path, dlerror()); + return (NULL); + } + new_librpz = dlsym(handle, LIBRPZ_DEF_STR); + if (new_librpz != NULL) { + if (dl_handle != NULL) + *dl_handle = handle; + return (new_librpz); + } + snprintf(emsg->c, sizeof(librpz_emsg_t), + "dlsym(%s, "LIBRPZ_DEF_STR"): %s", + path, dlerror()); + dlclose(handle); + return (NULL); +} + +#elif defined(LIBRPZ_LIB_OPEN) + +/* + * Statically link to the librpz.so DSO on systems without dlopen() + */ +static inline librpz_t * +librpz_lib_open(librpz_emsg_t *emsg, void **dl_handle, const char *path) +{ + (void)(path); + + if (dl_handle != NULL) + *dl_handle = NULL; + +#if LIBRPZ_LIB_OPEN == 1 + emsg->c[0] = '\0'; + return (&LIBRPZ_DEF); +#else + snprintf(emsg->c, sizeof(librpz_emsg_t), + "librpz not available via ./configure"); + return (NULL); +#endif /* LIBRPZ_LIB_OPEN */ +} +#endif /* LIBRPZ_LIB_OPEN */ + +#endif /* LIBRPZ_H */ diff --git a/fastrpz/rpz.c b/fastrpz/rpz.c new file mode 100644 index 00000000..c5ab7801 --- /dev/null +++ b/fastrpz/rpz.c @@ -0,0 +1,1352 @@ +/* + * fastrpz/rpz.c - interface to the fastrpz response policy zone library + * + * Optimize no-rewrite cases for speed but optimize rewriting for + * simplicity and size. + */ + +#include "config.h" + +#ifdef ENABLE_FASTRPZ +#include "daemon/daemon.h" +#define LIBRPZ_LIB_OPEN FASTRPZ_LIB_OPEN +#include "fastrpz/rpz.h" +#include "daemon/worker.h" +#include "iterator/iter_delegpt.h" +#include "iterator/iter_utils.h" +#include "iterator/iterator.h" +#include "util/data/dname.h" +#include "util/data/msgencode.h" +#include "util/data/msgparse.h" +#include "util/data/msgreply.h" +#include "util/log.h" +#include "util/netevent.h" +#include "util/net_help.h" +#include "util/regional.h" +#include "util/storage/slabhash.h" +#include "services/cache/dns.h" +#include "services/cache/rrset.h" +#include "services/mesh.h" +#include "sldns/sbuffer.h" +#include "sldns/rrdef.h" + + +typedef enum state { + /* No more rewriting */ + st_off = 1, + /* Send SERVFAIL */ + st_servfail, + /* No dispositive hit yet */ + st_unknown, + /* Let the iterator resolve a CNAME or get a delegation point. */ + st_iterate, + /* Let the iterator resolve NS to check NSIP or NSDNAME triggers. */ + st_ck_ns, + /* We have an answer */ + st_rewritten, +} st_t; + + +/* RPZ state pointed to by struct comm_reply */ +typedef struct commreply_rpz { + /* librpz state */ + librpz_rsp_t* rsp; + /* ID for log messages */ + int log_id; + + /* from configuration */ + int min_ns_dots; + + /* Running in the iterator */ + bool iterating; + + /* current and previous state and librpz result */ + st_t st; + st_t saved_st[LIBRPZ_RSP_STACK_DEPTH-1]; + librpz_result_t result; + + /* Stop adding CNAMEs to the prepend list before this owner name. */ + librpz_domain_buf_t cname_hit; + /* It is not the first CNAME */ + bool cname_hit_2nd; + librpz_result_id_t hit_id; +} commreply_rpz_t; + + +/* Generate an ID for log messages. */ +static int log_id; + +librpz_t *librpz; + + +static void LIBRPZ_NORET +rpz_assert(const char *s) +{ + fatal_exit("%s", s); + exit(1); +} +#define RPZ_ASSERT(c) ((c) ? (void)0 : rpz_assert(#c), (void)0) + +/* + * librpz client handle locking + */ +static void +lock_destroy(void* mutex) +{ + lock_basic_destroy(mutex); + free(mutex); +} + +static void +lock(void* mutex) +{ + lock_basic_lock(mutex); +} + +static void +unlock(void* mutex) +{ + lock_basic_unlock(mutex); +} + + +static void +log_fnc(librpz_log_level_t level, void* ATTR_UNUSED(ctx), const char* buf) +{ + /* Setting librpz_log_level overrides the unbound "verbose" level. */ + if(level > LIBRPZ_LOG_TRACE1 && + level <= librpz->log_level_val(LIBRPZ_LOG_INVALID)) + level = LIBRPZ_LOG_TRACE1; + + switch(level) { + case LIBRPZ_LOG_FATAL: + case LIBRPZ_LOG_ERROR: /* errors */ + default: + log_err("rpz: %s", buf); + break; + + case LIBRPZ_LOG_TRACE1: /* big events such as dnsrpzd starts */ + verbose(VERB_OPS, "rpz: %s", buf); + break; + + case LIBRPZ_LOG_TRACE2: /* smaller dnsrpzd zone transfers */ + verbose(VERB_DETAIL, "rpz: %s", buf); + break; + + case LIBRPZ_LOG_TRACE3: /* librpz hits */ + verbose(VERB_QUERY, "rpz: %s", buf); + break; + + case LIBRPZ_LOG_TRACE4: /* librpz lookups */ + verbose(VERB_CLIENT, "rpz: %s", buf); + break; + } +} + + +/* Release the librpz version. */ +static void +rpz_off(commreply_rpz_t* rpz, st_t st) +{ + if(!rpz) + return; + rpz->st = st; + librpz->rsp_detach(&rpz->rsp); +} + + +static void LIBRPZ_PF(2,3) +log_fail(commreply_rpz_t* rpz, const char* p, ...) +{ + va_list args; + + if(rpz->st == st_servfail) + return; + + va_start(args, p); + librpz->vlog(LIBRPZ_LOG_ERROR, rpz, p, args); + va_end(args); + if(!rpz) + return; + rpz_off(rpz, st_servfail); +} + + +/* Announce a rewrite. */ +static void +log_rewrite(uint8_t* qname, librpz_policy_t policy, const char* msg, + commreply_rpz_t* rpz) +{ + char policy_buf[POLICY2STR_SIZE]; + char qname_nm[LDNS_MAX_DOMAINLEN+1]; + librpz_domain_buf_t tdomain; + char tdomain_nm[LDNS_MAX_DOMAINLEN+1]; + librpz_emsg_t emsg; + + if(rpz->st == st_servfail || !rpz->result.log) + return; + if(librpz->log_level_val(LIBRPZ_LOG_INVALID) < LIBRPZ_LOG_TRACE1) + return; + + dname_str(qname, qname_nm); + + if(!librpz->rsp_domain(&emsg, &tdomain, rpz->rsp)) { + librpz->log(LIBRPZ_LOG_ERROR, rpz, "%s", emsg.c); + return; + } + dname_str(tdomain.d, tdomain_nm); + + librpz->log(LIBRPZ_LOG_TRACE3, rpz, "%srewriting %s via %s %s to %s", + msg, qname_nm, tdomain_nm, + librpz->trig2str(rpz->result.trig), + librpz->policy2str(policy, policy_buf, + sizeof(policy_buf))); +} + + +/* Connect to and start dnsrpzd if necessary for the unbound daemon. + * Require "rpz-conf: path" to specify the rpz configuration file. + * The unbound server directory name is the default rpz working + * directory. If unbound uses chroot, then the dnsrpzd working + * directory must be in the chroot tree. + * The database and socket are closed and re-opened. + */ +void +rpz_init(librpz_clist_t** pclist, librpz_client_t** pclient, + const struct config_file* cfg) +{ + lock_basic_type* mutex; + librpz_emsg_t emsg; + + if(!librpz) { + librpz = librpz_lib_open(&emsg, NULL, FASTRPZ_LIBRPZ_PATH); + if(!librpz) + fatal_exit("rpz: %s", emsg.c); + } + + librpz->set_log(&log_fnc, NULL); + + if(!cfg->rpz_cstr) + fatal_exit("rpz: rpz-zone: not set"); + + librpz->client_detach(pclient); + librpz->clist_detach(pclist); + + mutex = malloc(sizeof(*mutex)); + if(!mutex) + fatal_exit("rpz: no memory for lock"); + lock_basic_init(mutex); + + *pclist = librpz->clist_create(&emsg, &lock, &unlock, &lock_destroy, + mutex, NULL); + if(!pclist) + fatal_exit("rpz: %s", emsg.c); + + *pclient = librpz->client_create(&emsg, *pclist, cfg->rpz_cstr, false); + if(!*pclient) + fatal_exit("rpz: %s", emsg.c); + + if(!librpz->connect(&emsg, *pclient, true)) + fatal_exit("rpz: %s", emsg.c); + + verbose(VERB_OPS, "rpz: librpz version %s", librpz->version); +} + + +/* Stop using librpz on behalf of a worker thread. */ +void +rpz_delete(librpz_clist_t** pclist, librpz_client_t** pclient) +{ + if(librpz) { + librpz->client_detach(pclient); + librpz->clist_detach(pclist); + } +} + + +/* Release the librpz resources held for a DNS client request. */ +void +rpz_end(struct comm_reply* commreply) +{ + if(!commreply->rpz) + return; + rpz_off(commreply->rpz, commreply->rpz->st); + free(commreply->rpz); + commreply->rpz = NULL; +} + + +static bool +push_st(commreply_rpz_t* rpz) +{ + librpz_emsg_t emsg; + + if(rpz->st == st_off || rpz->st == st_servfail) { + librpz->log(LIBRPZ_LOG_ERROR, rpz, + "state %d in push_st()", rpz->st); + return false; + } + if(!librpz->rsp_push(&emsg, rpz->rsp)) + log_fail(rpz, "%s", emsg.c); + memmove(&rpz->saved_st[1], &rpz->saved_st[0], + sizeof(rpz->saved_st) - sizeof(rpz->saved_st[0])); + rpz->saved_st[0] = rpz->st; + return rpz->st != st_servfail; +} + + +static bool +pop_st(commreply_rpz_t* rpz) +{ + librpz_emsg_t emsg; + + if(rpz->rsp && !librpz->rsp_pop(&emsg, &rpz->result, rpz->rsp)) + log_fail(rpz, "%s", emsg.c); + if(rpz->st != st_servfail) + rpz->st = rpz->saved_st[0]; + memmove(&rpz->saved_st[0], &rpz->saved_st[1], + sizeof(rpz->saved_st) - sizeof(rpz->saved_st[0])); + return rpz->st != st_servfail; +} + +static bool +pop_discard_st(commreply_rpz_t* rpz) +{ + librpz_emsg_t emsg; + + if(rpz->rsp && !librpz->rsp_pop_discard(&emsg, rpz->rsp)) + log_fail(rpz, "%s", emsg.c); + memmove(&rpz->saved_st[0], &rpz->saved_st[1], + sizeof(rpz->saved_st) - sizeof(rpz->saved_st[0])); + return rpz->st != st_servfail; +} + +/* Check a rewrite attempt for errors and a disabled zone. */ +static bool /* true=repeat the check */ +ck_after(uint8_t* qname, bool recursed, librpz_trig_t trig, + commreply_rpz_t* rpz) +{ + librpz_emsg_t emsg; + + if(rpz->st == st_servfail) + return false; + + if(!librpz->rsp_result(&emsg, &rpz->result, recursed, rpz->rsp)) { + log_fail(rpz, "%s", emsg.c); + return false; + } + + if(rpz->result.policy == LIBRPZ_POLICY_DISABLED) { + /* Log the hit on the disabled zone, do not try the zone again, + * and restore the state from before the check to forget the hit + * before trying again. */ + log_rewrite(qname, rpz->result.zpolicy, "disabled ", rpz); + if(!librpz->rsp_forget_zone(&emsg, rpz->result.cznum, rpz->rsp)) + log_fail(rpz, "%s", emsg.c); + return pop_st(rpz); + } + + /* Complain about and forget client-IP address hit that is not + * dispositive. Client-IP triggers have the highest priority + * within a policy zone, but can be overridden by any hit in a policy + * earlier in the client's (resolver's) list of zones, including + * policies that cannot be hit until after recursion. If we allowed + * client-IP triggers in secondary zones, then than two DNS requests + * that differ only in DNS client-IP addresses could properly + * have differing results. The Unbound iterator treats identical + * DNS requests the same regardless of DNS client-IP address. + * struct query_info would need to be modified to have an optional + * librpz_prefix_t containing the prefix of the client-IP address hit + * from librpz->rsp_clientip_prefix(). Adding to struct query_info + * would require finding and changing the many and obscure places + * including the Unbound tests to memset(0) the struct query_info + * that they create. */ + if(trig == LIBRPZ_TRIG_CLIENT_IP) { + if(rpz->result.cznum != 0) { + log_rewrite(qname, rpz->result.policy, + "ignore secondary ", rpz); + if(!pop_st(rpz)) + log_fail(rpz, "%s", emsg.c); + return (false); + } + } + + /* Forget the state from before the check and keep the new state + * if we do not have a hit on a disabled policy zone. */ + pop_discard_st(rpz); + return false; +} + + +/* Get the next RR from the policy record. */ +static bool +next_rr(librpz_rr_t** rrp, const uint8_t* qname, size_t qname_len, + commreply_rpz_t* rpz) +{ + librpz_emsg_t emsg; + + if(!librpz->rsp_rr(&emsg, NULL, NULL, NULL, rrp, &rpz->result, + qname, qname_len, rpz->rsp)) { + log_fail(rpz, "%s", emsg.c); + *rrp = NULL; + return false; + } + return true; +} + + +static bool /* false=fatal error to be logged */ +add_rr(struct sldns_buffer* pkt, const uint8_t* owner, size_t owner_len, + librpz_rr_t* rr, commreply_rpz_t* rpz) +{ + size_t rdlength; + + rdlength = ntohs(rr->rdlength); + + if(!sldns_buffer_available(pkt, owner_len + 10 + rdlength)) { + log_fail(rpz, "comm_reply buffer exhausted"); + free(rr); + return false; + } + sldns_buffer_write(pkt, owner, owner_len); + /* sizeof(librpz_rr_t)=12 instead of 10 */ + sldns_buffer_write(pkt, rr, 10 + rdlength); + return true; +} + + +/* Convert a fake incoming DNS message to an Unbound struct dns_msg */ +static void +pkt2dns_msg(struct dns_msg** dnsmsg, struct sldns_buffer* pkt, + commreply_rpz_t* rpz, struct regional* region) +{ + struct msg_parse* msgparse; + + msgparse = regional_alloc(region, sizeof(*msgparse)); + if(!msgparse) { + log_fail(rpz, "out of memory for msgparse"); + *dnsmsg = NULL; + return; + } + memset(msgparse, 0, sizeof(*msgparse)); + if(parse_packet(pkt, msgparse, region) != LDNS_RCODE_NOERROR) { + log_fail(rpz, "packet parse error"); + *dnsmsg = NULL; + return; + } + *dnsmsg = dns_alloc_msg(pkt, msgparse, region); + if(!*dnsmsg) { + log_fail(rpz, "dns_alloc_msg() failed"); + *dnsmsg = NULL; + return; + } + (*dnsmsg)->rep->security = sec_status_rpz_rewritten; +} + + +static bool /* false=SERVFAIL */ +ck_ip_rrset(const void* vdata, int family, librpz_trig_t trig, + uint8_t* qname, commreply_rpz_t* rpz) +{ + const struct packed_rrset_data* data; + uint rr_n; + size_t len; + librpz_emsg_t emsg; + + data = vdata; + + /* Loop to ignore disabled zones. */ + do { + if(!push_st(rpz)) + return false; + for(rr_n = 0; rr_n < data->count; ++rr_n) { + len = data->rr_len[rr_n]; + /* Skip bogus including negative placeholding rdata. */ + if((family == AF_INET && + len != sizeof(struct in_addr)+2) || + (family == AF_INET6 && + len != sizeof(struct in6_addr)+2)) + continue; + if(!librpz->ck_ip(&emsg, data->rr_data[rr_n]+2, + family, trig, rpz->hit_id, true, + rpz->rsp)) { + log_fail(rpz, "%s", emsg.c); + return false; + } + } + } while(ck_after(qname, true, trig, rpz)); + return rpz->st != st_servfail; +} + + +static bool /* false=SERVFAIL */ +ck_dname(uint8_t* dname, size_t dname_size, librpz_trig_t trig, + uint8_t* qname, bool recursed, commreply_rpz_t* rpz) +{ + librpz_emsg_t emsg; + + /* Refuse to check the root. */ + if(dname_is_root(dname)) + return rpz->st != st_servfail; + + /* Loop to ignore disabled zones. */ + do { + if(!push_st(rpz)) + return false; + if(!librpz->ck_domain(&emsg, dname, dname_size, trig, + rpz->hit_id, recursed, rpz->rsp)) { + log_fail(rpz, "%s", emsg.c); + return false; + } + } while(ck_after(qname, recursed, trig, rpz)); + + return rpz->st != st_servfail; +} + + +/* Check the IPv4 or IPv6 addresses for one NS name. */ +static bool /* false=st_servfail */ +ck_1nsip(uint8_t* nsname, size_t nsname_size, int family, int qtype, + bool* have_ns, commreply_rpz_t* rpz, struct module_env* env) +{ + struct ub_packed_rrset_key* akey; + + akey = rrset_cache_lookup(env->rrset_cache, nsname, nsname_size, + qtype, LDNS_RR_CLASS_IN, 0, 0, 0); + if(akey) { + *have_ns = true; + + if(!ck_ip_rrset(akey->entry.data, family, LIBRPZ_TRIG_NSIP, + nsname, rpz)) { + lock_rw_unlock(&akey->entry.lock); + return false; + } + lock_rw_unlock(&akey->entry.lock); + } + return true; +} + + +static bool /* false=st_servfail */ +ck_qname(uint8_t* qname, size_t qname_len, + bool recursed, /* recursion done */ + bool wait_ns, /* willing to iterate for NS data */ + commreply_rpz_t* rpz, struct module_env* env) +{ + uint8_t* dname; + size_t dname_size; + int cur_lab; + struct ub_packed_rrset_key* nskey; + const struct packed_rrset_data* nsdata; + uint8_t* nsname; + size_t nsname_size; + uint rr_n; + bool have_ns, tried_ns; + + if(!ck_dname(qname, qname_len, LIBRPZ_TRIG_QNAME, qname, false, rpz)) + return false; + + /* Do not waste time looking for NSDNAME and NSIP hits when there + * are no currently relevant triggers. */ + if(!librpz->have_ns_trig(rpz->rsp)) + return true; + + have_ns = false; + tried_ns = false; + dname = qname; + dname_size = qname_len; + for(cur_lab = dname_count_labels(dname) - 2; + cur_lab > rpz->min_ns_dots; + --cur_lab) { + tried_ns = true; + dname_remove_label(&dname, &dname_size); + nskey = rrset_cache_lookup(env->rrset_cache, dname, dname_size, + LDNS_RR_TYPE_NS, LDNS_RR_CLASS_IN, + 0, 0, 0); + if(!nskey) + continue; + + nsdata = (const struct packed_rrset_data*)nskey->entry.data; + for(rr_n = 0; + rr_n < nsdata->count && rpz->st == st_unknown; + ++rr_n) { + nsname = nsdata->rr_data[rr_n]+2; + nsname_size = nsdata->rr_len[rr_n]; + if(nsname_size <= 2) + continue; + nsname_size -= 2; + if(!ck_dname(nsname, nsname_size, LIBRPZ_TRIG_NSDNAME, + qname, recursed, rpz)) + return false; + if(!ck_1nsip(nsname, nsname_size, AF_INET, + LDNS_RR_TYPE_A, &have_ns, rpz, env)) + return false; + if(!ck_1nsip(nsname, nsname_size, AF_INET6, + LDNS_RR_TYPE_AAAA, &have_ns, rpz, env)) + return false; + } + lock_rw_unlock(&nskey->entry.lock); + } + + /* If we failed to find NS records, then stop building the response + * before a CNAME with this owner name. */ + if(!have_ns && tried_ns && (!recursed || wait_ns)) { + rpz->cname_hit.size = qname_len; + RPZ_ASSERT(rpz->cname_hit.size <= sizeof(rpz->cname_hit.d)); + memcpy(rpz->cname_hit.d, qname, qname_len); + rpz->result.hit_id = rpz->hit_id; + rpz->st = st_ck_ns; + } + return true; +} + + +/* + * Are we ready to rewrite the response? + */ +static bool /* true=send rewritten response */ +ck_result(uint8_t* qname, bool recursed, + commreply_rpz_t* rpz, const struct comm_point* commpoint) +{ + librpz_emsg_t emsg; + + switch(rpz->st) { + case st_off: + case st_servfail: + case st_rewritten: + return false; + case st_unknown: + break; + case st_iterate: + return false; + case st_ck_ns: + /* An NSDNAME or NSIP check failed for lack of cached data. */ + return false; + default: + fatal_exit("impossible RPZ state %d in rpz_worker_cache()", + rpz->st); + } + + /* Wait for a trigger. */ + if(rpz->result.policy == LIBRPZ_POLICY_UNDEFINED) { + if(recursed && + rpz->result.zpolicy != LIBRPZ_POLICY_UNDEFINED && + !librpz->rsp_result(&emsg, &rpz->result, true, rpz->rsp)) { + log_fail(rpz, "%s", emsg.c); + return false; + } + if(rpz->result.policy == LIBRPZ_POLICY_UNDEFINED) + return false; + } + + if(rpz->result.policy == LIBRPZ_POLICY_PASSTHRU) { + log_rewrite(qname, rpz->result.policy, "", rpz); + rpz_off(rpz, st_off); + return false; + } + + /* The TCP-only policy answers UDP requests with truncated responses. */ + if(rpz->result.policy == LIBRPZ_POLICY_TCP_ONLY && + commpoint->type == comm_tcp) { + rpz_off(rpz, st_off); + return false; + } + + return true; +} + + +/* + * Convert an RPZ hit to a struct dns_msg + */ +static void +get_result_msg(struct dns_msg** dnsmsg, struct query_info* qinfo, + uint16_t id, uint16_t flags, bool recursed, commreply_rpz_t* rpz, + struct comm_point* commpoint, struct regional* region) +{ + librpz_rr_t* rr; + librpz_domain_buf_t origin; + struct sldns_buffer* pkt; + uint16_t num_rrs; + librpz_emsg_t emsg; + + *dnsmsg = NULL; + if(!ck_result(qinfo->qname, recursed, rpz, commpoint)) + return; + + rpz->st = st_rewritten; + + if(rpz->result.policy == LIBRPZ_POLICY_DROP) { + log_rewrite(qinfo->qname, rpz->result.policy, "", rpz); + /* Make a fake cached message to carry + * sec_status_rpz_drop and be dropped. */ + error_encode(commpoint->buffer, LDNS_RCODE_NOERROR, + qinfo, id, flags, NULL); + pkt2dns_msg(dnsmsg, commpoint->buffer, rpz, region); + (*dnsmsg)->rep->security = sec_status_rpz_drop; + return; + } + + /* Create a DNS message of the RPZ data. + * In many cases that message could be sent directly to the DNS client, + * but sometimes iteration must be used to resolve a CNAME. + * This need not be fast, because rewriting responses should be rare. + * Therefore, use the simpler but slower tactic of generating a + * parsed version of the message. */ + + flags &= ~BIT_AA; + flags |= BIT_QR | BIT_RA; + rr = NULL; + + /* The TCP-only policy answers UDP requests with truncated responses. */ + if(rpz->result.policy == LIBRPZ_POLICY_TCP_ONLY) { + flags |= BIT_TC; + + } else if(rpz->result.policy == LIBRPZ_POLICY_NXDOMAIN) { + flags |= LDNS_RCODE_NXDOMAIN; + + } else if(rpz->result.policy == LIBRPZ_POLICY_CNAME) { + if(!rpz->iterating && + qinfo->qtype != LDNS_RR_TYPE_CNAME) { + /* The new DNS message would be a CNAME and + * the external request was not for a CNAME. + * The worker must punt to the iterator so that + * the iterator can resolve the CNAME. */ + rpz->st = st_iterate; + return; + } + next_rr(&rr, qinfo->qname, qinfo->qname_len, rpz); + + } else if(rpz->result.policy == LIBRPZ_POLICY_RECORD || + rpz->result.policy == LIBRPZ_POLICY_NODATA) { + next_rr(&rr, qinfo->qname, qinfo->qname_len, rpz); + /* Punt to the iterator if the new DNS message would + * be a CNAME that must be resolved. */ + if(!rpz->iterating && + qinfo->qtype != LDNS_RR_TYPE_CNAME && + rr && rr->type == ntohs(LDNS_RR_TYPE_CNAME)) { + free(rr); + rpz->st = st_iterate; + return; + } + } + log_rewrite(qinfo->qname, rpz->result.policy, "", rpz); + + /* Make a buffer containing a DNS message with the RPZ data. */ + pkt = commpoint->buffer; + sldns_buffer_clear(pkt); + if(sldns_buffer_remaining(pkt) < LDNS_HEADER_SIZE) { + log_fail(rpz, "comm_reply buffer too small for header"); + if(rr) + free(rr); + return; + } + + /* Install ID, flags, QDCOUNT=1, ANCOUNT=# of RPZ RRs, NSCOUNT=0, + * and ARCOUNT=1 for the RPZ SOA. */ + sldns_buffer_write_u16(pkt, id); + sldns_buffer_write_u16(pkt, flags); + sldns_buffer_write_u16(pkt, 1); /* QDCOUNT */ + sldns_buffer_write_u16(pkt, 0); /* ANCOUNT will be set later */ + sldns_buffer_write_u16(pkt, 0); /* NSCOUNT */ + sldns_buffer_write_u16(pkt, 1); /* ARCOUNT */ + + /* Install the question with the LDNS_RR_CLASS_RPZ bit to + * to distinguish this supposed cache entry from the real deal. */ + sldns_buffer_write(pkt, qinfo->qname, qinfo->qname_len); + sldns_buffer_write_u16(pkt, qinfo->qtype); + sldns_buffer_write_u16(pkt, LDNS_RR_CLASS_IN); + + /* Install the RPZ RRs in the answer section */ + num_rrs = 0; + while(rr) { + /* Include only the requested RRs. */ + if(qinfo->qtype == LDNS_RR_TYPE_ANY || + rr->type == htons(qinfo->qtype) || + rr->type == htons(LDNS_RR_TYPE_CNAME)) { + if(!add_rr(pkt, qinfo->qname, qinfo->qname_len, + rr, rpz)) + return; + + ++num_rrs; + } + free(rr); + + next_rr(&rr, qinfo->qname, qinfo->qname_len, rpz); + } + /* Finish ANCOUNT. */ + if(num_rrs != 0) + sldns_buffer_write_u16_at(pkt, 6, num_rrs); + + /* All rewritten responses have an identifying SOA record in the + * additional section. */ + if(!librpz->rsp_soa(&emsg, NULL, &rr, &origin, + &rpz->result, rpz->rsp)) { + log_fail(rpz, "no soa"); + return; + } + if(!add_rr(pkt, origin.d, origin.size, rr, rpz)) + return; + free(rr); + + /* Create a dns_msg representation of the fake incoming message. */ + sldns_buffer_flip(pkt); + pkt2dns_msg(dnsmsg, pkt, rpz, region); +} + + +/* Check the RRs in the ANSWER section of a reply_info. */ +static void +ck_reply(struct reply_info* reply, uint8_t* qname, bool wait_ns, + commreply_rpz_t* rpz, struct module_env* env) +{ + struct ub_packed_rrset_key* rrset; + enum sldns_enum_rr_type type; + uint rrset_n; + + /* Check the RRs in the ANSWER section. */ + rpz->cname_hit.size = 0; + rpz->cname_hit_2nd = false; + for(rrset_n = 0; rrset_n < reply->an_numrrsets; ++rrset_n) { + /* Check all of the RRs before deciding. */ + if(rpz->st != st_unknown) + return; + + rrset = reply->rrsets[rrset_n]; + if(ntohs(rrset->rk.rrset_class) != LDNS_RR_CLASS_IN) + continue; + type = ntohs(rrset->rk.type); + + if(type == LDNS_RR_TYPE_A) { + if(!ck_ip_rrset(rrset->entry.data, AF_INET, + LIBRPZ_TRIG_IP, qname, rpz)) + break; + + } else if(type == LDNS_RR_TYPE_AAAA) { + if(!ck_ip_rrset(rrset->entry.data, AF_INET6, + LIBRPZ_TRIG_IP, qname, rpz)) + break; + + } else if(type == LDNS_RR_TYPE_CNAME) { + /* Check CNAME owners unless we already have a hit. */ + ++rpz->hit_id; + if(!ck_qname(rrset->rk.dname, rrset->rk.dname_len, + true, wait_ns, rpz, env)) + break; + + /* Do not worry about the CNAME if it did not hit, + * but note the miss so that it can be prepended + * if we do hit. */ + if(rpz->result.hit_id != rpz->hit_id) { + rpz->cname_hit_2nd = true; + continue; + } + + /* Stop after hitting a CNAME. + * The iterator must be used to include CNAMEs before + * the CNAME that hit in the rewritten response. */ + rpz->cname_hit.size = rrset->rk.dname_len; + RPZ_ASSERT(rpz->cname_hit.size <= sizeof(rpz->cname_hit.d)); + memcpy(rpz->cname_hit.d, rrset->rk.dname, + rpz->cname_hit.size); + break; + } + } +} + + +static void +worker_servfail(struct worker* worker, struct query_info* qinfo, + uint16_t id, uint16_t flags, struct comm_reply* commreply) +{ + error_encode(commreply->c->buffer, LDNS_RCODE_SERVFAIL, + qinfo, id, flags, NULL); + regional_free_all(worker->scratchpad); + comm_point_send_reply(commreply); +} + + +/* Send an RPZ answer before the iterator has started. + * @return: 1=continue normal unbound processing + * 0=punt to the iterator + * -1=rewritten response already sent or dropped. */ +static int +worker_send(struct dns_msg* dnsmsg, struct worker* worker, + struct query_info* qinfo, uint16_t id, uint16_t flags, + struct edns_data* edns, struct comm_reply* commreply) +{ + switch (commreply->rpz->st) { + case st_off: + return 1; + case st_servfail: + worker_servfail(worker, qinfo, id, flags, commreply); + return -1; + case st_unknown: + return 1; + case st_iterate: + case st_ck_ns: + return 0; /* punt to the iterator */ + case st_rewritten: + break; + default: + fatal_exit("impossible RPZ state %d in worker_send()", + commreply->rpz->st); + } + + if(dnsmsg->rep->security == sec_status_rpz_drop) { + regional_free_all(worker->scratchpad); + comm_point_drop_reply(commreply); + return -1; + } + + edns->edns_version = EDNS_ADVERTISED_VERSION; + edns->udp_size = EDNS_ADVERTISED_SIZE; + edns->ext_rcode = 0; + edns->bits = 0; /* rewritten response cannot verify. */ + if(!reply_info_answer_encode(qinfo, dnsmsg->rep, + id, flags | BIT_QR, + commreply->c->buffer, 0, 1, + worker->scratchpad, + edns->udp_size, edns, 0, 0)) { + worker_servfail(worker, qinfo, id, flags, commreply); + } else { + regional_free_all(worker->scratchpad); + comm_point_send_reply(commreply); + } + return -1; +} + + +/* Set commreply to an RPZ context if the response might be rewritten. + * Try to answer now with a hit allowed before recursion (iteration). */ +bool /* true=response sent or dropped */ +rpz_start(struct worker* worker, struct query_info* qinfo, + struct comm_reply* commreply, struct edns_data* edns) +{ + commreply_rpz_t* rpz; + uint16_t id, flags; + struct dns_msg* dnsmsg; + int family; + const void* addr; + librpz_emsg_t emsg; + + /* Quit if rpz not configured. */ + if(!worker->daemon->rpz_client) + return false; + + /* Rewrite only the Internet class */ + if(qinfo->qclass != LDNS_RR_CLASS_IN) + return false; + + rpz = commreply->rpz; + RPZ_ASSERT(!rpz); + + dnsmsg = NULL; + id = htons(sldns_buffer_read_u16_at(commreply->c->buffer, 0)); + flags = sldns_buffer_read_u16_at(commreply->c->buffer, 2); + + rpz = malloc(sizeof(*rpz)); + if(!rpz) { + librpz->log(LIBRPZ_LOG_ERROR, NULL, "no memory for rpz"); + return 0 > worker_send(dnsmsg, worker, qinfo, + id, flags, edns, commreply); + } + memset(rpz, 0, sizeof(*rpz)); + rpz->st = st_unknown; + commreply->rpz = rpz; + + /* Make a new ID for log messages */ + rpz->log_id = __sync_add_and_fetch(&log_id, 1); + + /* Get access to the librpz data. */ + if(!librpz->rsp_create(&emsg, &rpz->rsp, &rpz->min_ns_dots, + worker->daemon->rpz_client, + (flags & BIT_RD) != 0, + (edns->bits & EDNS_DO) != 0)) { + log_fail(rpz, "%s", emsg.c); + return false; + } + /* Quit if benign reasons prevent rewriting. */ + if(!rpz->rsp) { + rpz->st = st_off; + librpz->log(LIBRPZ_LOG_TRACE1, rpz, "%s", emsg.c); + return false; + } + + /* Check the client IP address. + * Do not use commreply->srctype because it is often 0. */ + family = ((struct sockaddr*)&commreply->addr)->sa_family; + switch(family) { + case AF_INET: + addr = &((struct sockaddr_in*)&commreply->addr)->sin_addr; + break; + case AF_INET6: + addr = &((struct sockaddr_in6*)&commreply->addr)->sin6_addr; + break; + default: + /* Maybe the client is on a UNIX domain socket. */ + librpz->log(LIBRPZ_LOG_TRACE2, rpz, + "unknown client address family %d", family); + addr = NULL; + break; + } + /* Loop to ignore disabled zones. */ + while(addr) { + if(!push_st(rpz)) + break; + if(!librpz->ck_ip(&emsg, addr, family, LIBRPZ_TRIG_CLIENT_IP, + rpz->hit_id, true, rpz->rsp)) { + log_fail(rpz, "%s", emsg.c); + break; + } + if(!ck_after(qinfo->qname, false, LIBRPZ_TRIG_CLIENT_IP, rpz)) + break; + } + if(rpz->st == st_servfail) + return 0 > worker_send(dnsmsg, worker, qinfo, + id, flags, edns, commreply); + + /* Check the QNAME and possibly replace a client-IP hit. */ + ck_qname(qinfo->qname, qinfo->qname_len, false, true, + rpz, &worker->env); + + get_result_msg(&dnsmsg, qinfo, id, flags, false, + rpz, commreply->c, worker->scratchpad); + return 0 > worker_send(dnsmsg, worker, qinfo, + id, flags, edns, commreply); +} + + +/* Check a cached reply before iteration. + * @return: 1=use cache entry + * 0=deny a cached entry exists in order to punt to the iterator + * -1=rewritten response already sent or dropped */ +int +rpz_worker_cache(struct worker* worker, struct reply_info* reply, + struct query_info* qinfo, uint16_t id, uint16_t flags, + struct edns_data* edns, struct comm_reply* commreply) +{ + commreply_rpz_t* rpz; + struct dns_msg* dnsmsg; + st_t new_st; + librpz_rr_t* rr; + + dnsmsg = NULL; + + rpz = commreply->rpz; + switch(rpz->st) { + case st_off: + return 1; /* Send the cache entry. */ + case st_servfail: + return worker_send(dnsmsg, worker, qinfo, id, flags, + edns, commreply); + case st_unknown: + break; + case st_iterate: + case st_ck_ns: + return 0; /* Punt to the iterator. */ + case st_rewritten: + default: + fatal_exit("impossible RPZ state %d in rpz_worker_cache()", + rpz->st); + } + + /* Check the RRs in the ANSWER section. */ + if(!push_st(rpz)) + return worker_send(dnsmsg, worker, qinfo, id, flags, edns, + commreply); + + ck_reply(reply, qinfo->qname, true, rpz, &worker->env); + if(!ck_result(qinfo->qname, true, rpz, commreply->c)) + return worker_send(dnsmsg, worker, qinfo, id, flags, edns, + commreply); + + if(rpz->cname_hit.size != 0) { + /* Punt to the iterator if leading CNAMEs must be + * included in the rewritten response. */ + rpz->cname_hit.size = 0; + new_st = st_iterate; + + } else if(rpz->result.policy == LIBRPZ_POLICY_CNAME) { + /* Punt if the rewritten response is to a CNAME. */ + new_st = st_iterate; + + } else { + if(rpz->result.policy == LIBRPZ_POLICY_RECORD) { + next_rr(&rr, qinfo->qname, qinfo->qname_len, rpz); + if(rr) { + /* Punt we are rewriting to a CNAME. */ + if(rr->type == ntohs(LDNS_RR_TYPE_CNAME)) { + free(rr); + rpz->st = st_iterate; + } else { + free(rr); + } + } + } + get_result_msg(&dnsmsg, qinfo, id, flags, true, + rpz, commreply->c, worker->scratchpad); + new_st = rpz->st; + } + + switch(new_st) { + case st_off: + case st_servfail: + break; + case st_unknown: + pop_discard_st(rpz); + break; + case st_iterate: + case st_ck_ns: + if(pop_st(rpz)) + rpz->st = new_st; + break; + case st_rewritten: + pop_discard_st(rpz); + break; + default: + fatal_exit("impossible RPZ state %d in rpz_worker_cache()", + rpz->st); + } + + return worker_send(dnsmsg, worker, qinfo, id, flags, edns, commreply); +} + + +/* Check a cache hit or miss for the iterator. + * A cache miss can already have a QNAME hit that was ignored before checking + * the iterator because of "QNAME-WAIT-RECURSE yes". + * Cache hits are treated like responses from authorities. */ +bool /* false=SERVFAIL */ +rpz_iter_cache(struct dns_msg** msg, enum response_type* type, + struct module_qstate* qstate, struct iter_qstate* iq) +{ + struct comm_reply* commreply; + commreply_rpz_t* rpz; + struct dns_msg* dnsmsg; + + commreply = &qstate->mesh_info->reply_list->query_reply; + rpz = commreply->rpz; + + rpz->iterating = true; + + switch(rpz->st) { + case st_off: + iq->rpz_rewritten = 1; /* RPZ has nothing to say. */ + return true; + case st_servfail: + return false; + case st_unknown: + break; + case st_iterate: + case st_ck_ns: + rpz->st = st_unknown; + if(!ck_qname(iq->qchase.qname, iq->qchase.qname_len, + *msg != NULL, true, rpz, qstate->env)) + return false; + /* If we must recurse regardless and if NSIP/NSDNAME + * checking failed, then delay in the hope that + * recursion will also get NS data. */ + if(rpz->st == st_ck_ns) + return true; + break; + case st_rewritten: + default: + fatal_exit("impossible RPZ state %d in rpz_iter_cache()", + rpz->st); + } + + push_st(rpz); + + /* Check the cache hit. */ + if(*msg) + ck_reply((*msg)->rep, iq->qchase.qname, true, rpz, qstate->env); + + /* The DNS ID does not matter, because the generated dns_msg + * is nominally from an authority and not to the DNS client. */ + get_result_msg(&dnsmsg, &iq->qchase, 1, qstate->query_flags, true, + rpz, commreply->c, qstate->region); + + switch(rpz->st) { + case st_off: + iq->rpz_rewritten = 1; /* RPZ has nothing to say. */ + return true; + case st_servfail: + return false; + case st_unknown: + /* RPZ has nothing to say yet. Maybe there will be a hit + * later in the CNAME chain. */ + return pop_discard_st(rpz); + case st_ck_ns: + /* Try to get NS data for a CNAME found by ck_reply() */ + *type = RESPONSE_TYPE_CNAME; + return pop_discard_st(rpz); + case st_iterate: + default: + fatal_exit("impossible RPZ state %d in rpz_iter_cache()", + rpz->st); + case st_rewritten: + break; + } + + if(*msg && rpz->cname_hit.size != 0 && rpz->cname_hit_2nd) { + /* We hit a CNAME owner in the cached msg after not hitting one + * or more CNAME owners. We need to add those leading CNAMEs + * to the prepend list. Tell the iterator to treat the cached + * message as a RESPONSE_TYPE_CNAME even if it contains answers. + * handle_cname_response() will stop prepending CNAMEs before + * the triggering CNAME. handle_cname_response() will cause + * a restart to resolve the target of the preceding CNAME, + * which is the same as the hit CNAME owner. */ + rpz->st = st_unknown; + *type = RESPONSE_TYPE_CNAME; + return pop_discard_st(rpz); + } + + *msg = dnsmsg; + iq->rpz_security = dnsmsg->rep->security; + + if(dnsmsg && dnsmsg->rep->an_numrrsets != 0 && + dnsmsg->rep->rrsets[0]->rk.type == htons(LDNS_RR_TYPE_CNAME)) { + /* The cached msg triggered a rule that rewrites to a + * CNAME that must be resolved. + * We have a replacement dns_msg with that CNAME and also + * an SOA RR in the ADDITIONAL section that the iterator + * will lose as it adds the CNAME to the prepend list. + * Save the SOA RR in iq->rpz_soa. */ + iq->rpz_soa = dnsmsg->rep->rrsets[1]; + iq->rpz_rewritten = 1; + *type = RESPONSE_TYPE_CNAME; + return true; + } + + /* Otherwise we have rewritten to zero or more non-CNAME RRs. + * (DNAMEs are not supported.) + * Tell the iterator to send the rewritten message. */ + *type = RESPONSE_TYPE_ANSWER; + iq->rpz_rewritten = 1; + return true; +} + + +/* Check a RESPONSE_TYPE_ANSWER response from an authority in the iterator. */ +rpz_iter_resp_t +rpz_iter_resp(struct module_qstate* qstate, struct iter_qstate* iq, + struct dns_msg** resp, bool* is_cname) +{ + struct comm_reply* commreply; + commreply_rpz_t* rpz; + struct reply_info* rep; + + *is_cname = false; + + commreply = &qstate->mesh_info->reply_list->query_reply; + rpz = commreply->rpz; + switch(rpz->st) { + case st_off: + case st_servfail: + case st_iterate: + case st_rewritten: + default: + fatal_exit("impossible RPZ state %d in rpz_iter_resp()", + rpz->st); + case st_ck_ns: + case st_unknown: + break; + } + + /* We know !iq->rpz_rewritten and so the response was after a simple + * cache miss when the original QNAME did not trigger a response + * or after a CNAME whose owner name did hit but was then forgotten + * with pop_st(). + * In either case, it is necessary to check the QNAME here. + * Checking the QNAME will not lose a better hit. */ + rpz->st = st_unknown; + ck_qname(iq->qchase.qname, iq->qchase.qname_len, true, false, + rpz, qstate->env); + + /* Check the RRs in the ANSWER section. */ + if(!push_st(rpz)) + return rpz_iter_resp_fail; + ck_reply(iq->response->rep, iq->qchase.qname, false, rpz, qstate->env); + get_result_msg(resp, &qstate->qinfo, 1, qstate->query_flags, true, + rpz, commreply->c, qstate->region); + switch(rpz->st) { + case st_off: + iq->rpz_rewritten = 1; /* Do not come back. */ + return rpz_iter_resp_done; + case st_servfail: /* Send SERVFAIL */ + return rpz_iter_resp_fail; + case st_unknown: + case st_ck_ns: + return rpz_iter_resp_done; /* continue without change */ + case st_iterate: + default: + fatal_exit("impossible RPZ state %d in rpz_iter_resp()", + rpz->st); + case st_rewritten: + /* Tell the iterator to use handle_cname_response() to + * prepend any preceding CNAMEs. + * We have a replacement dns_msg that also has an SOA RR in the + * ADDITIONAL section that the iterator will lose if it is a + * CNAME. Save that SOA in that case. */ + rep = (*resp)->rep; + if(rep->an_numrrsets != 0 && + rep->rrsets[0]->rk.type == ntohs(LDNS_RR_TYPE_CNAME)) { + *is_cname = true; + iq->rpz_soa = rep->rrsets[1]; + } + return rpz_iter_resp_rewrite; + } +} + + +/* Tell handle_cname_response() to stop adding to the answer prepend list + * after adding CNAME with a target that hits a QNAME trigger. + * Do not change any RPZ state, but expect the call of handle_cname_response() + * to try to resolve the CNAME and hit the same QNAME trigger and rewrite + * the response. */ +rpz_cname_t +rpz_cname(struct module_qstate* qstate, + uint8_t* oname, size_t oname_size) +{ + struct mesh_reply* reply_list; + struct comm_reply* commreply; + commreply_rpz_t* rpz; + rpz_cname_t ret; + + /* Quit if RPZ is off */ + reply_list = qstate->mesh_info->reply_list; + if(!reply_list) + return rpz_cname_prepend; + commreply = &reply_list->query_reply; + rpz = commreply->rpz; + + if(!rpz || rpz->st == st_off) + return rpz_cname_prepend; + + /* Stop on a 2nd or later CNAME for rpz_iter_resp(). */ + if(rpz->cname_hit.size != 0) { + if(!query_dname_compare(rpz->cname_hit.d, oname)) + return rpz_cname_stop; + return rpz_cname_prepend; + } + + if(rpz->st != st_unknown) + fatal_exit("impossible RPZ state %d in rpz_cname()", rpz->st); + + ret = rpz_cname_prepend; + if(!push_st(rpz)) + return rpz_cname_fail; + /* Stop before prepending a CNAME that would preempt a + * rewritten response or before a possible NSDNAME or NSIP trigger. */ + ++rpz->hit_id; + ck_qname(oname, oname_size, true, true, rpz, qstate->env); + if(rpz->st != st_unknown) + ret = rpz_cname_stop; + if(!pop_st(rpz)) + return rpz_cname_fail; + return ret; +} + +#endif /* ENABLE_FASTRPZ */ diff --git a/fastrpz/rpz.h b/fastrpz/rpz.h new file mode 100644 index 00000000..5d7e31c5 --- /dev/null +++ b/fastrpz/rpz.h @@ -0,0 +1,138 @@ +/* + * fastrpz/rpz.h - interface to the fastrpz response policy zone library + * + * Copyright (c) 2016 Farsight Security, Inc. + * + * 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. + */ + +#ifndef UNBOUND_FASTRPZ_RPZ_H +#define UNBOUND_FASTRPZ_RPZ_H + +#ifndef PACKAGE_VERSION +/* Ensure that config.h has been included to correctly set ENABLE_FASTRPZ */ +#include "config.h" +#endif + +#ifdef ENABLE_FASTRPZ + +#include "librpz.h" + +#include "daemon/daemon.h" +#include "util/config_file.h" + +struct comm_point; /* forward references */ +struct comm_reply; +struct dns_msg; +struct edns_data; +struct iter_qstate; +struct query_info; +struct reply_info; +enum response_type; /* iterator/iter_utils.h */ + + +struct commreply_rpz; + +/** + * Connect to the librpz database. + * @param pclist: future pointer to opaque librpz client data + * @param pclient: future pointer to opaque librpz client data + * @param cfg: parsed unbound configuration + */ +void rpz_init(librpz_clist_t** pclist, librpz_client_t** pclient, + const struct config_file* cfg); + +/** + * Disconnect from the librpz database + * @param client: opaque librpz client data + */ +void rpz_delete(librpz_clist_t** pclist, librpz_client_t** pclient); + +/** + * Start working on a DNS request and check for client IP address triggers. + * @param worker: the DNS request context + * @param qinfo: the DNS question + * @param[in,out] commreply: the answer + * @param c: where to send the response + * @param[in,out] edns for the DO flag + * @return true if response already sent or dropped + */ +bool rpz_start(struct worker* worker, struct query_info* qinfo, + struct comm_reply* commreply, struct edns_data* edns); + +/** + * Release resources held for a DNS request + * @param rspp: pointer to pointer to rpz client context. + */ +void rpz_end(struct comm_reply* comm_rep); + +/** + * Check a cached reply for RPZ hits before iteration + * @param worker: the DNS request context + * @param casheresp: cache reply + * @param qinfo: the DNS question + * @param id from the DNS request + * @param flags from the DNS request + * @param[in,out] edns for the DO flag + * @param[in,out] commreply: RPZ state + * @return 1=use cache entry, -1=rewritten response already sent or dropped, + * 0=deny a cached entry exists + */ +int rpz_worker_cache(struct worker* worker, struct reply_info* cacheresp, + struct query_info* qinfo, uint16_t id, uint16_t flags, + struct edns_data* edns, struct comm_reply* commreply); + +/** + * Check for an existing RPZ CNAME rewrite with "QNAME-WAIT-RECURSE no" + * that needs to be resolved before resolving the external request. + * @param[out] msg: rewritten CNAME response. + * @param qstate: query state. + * @param iq: iterator query state. + * @return false=send SERVFAIL + */ +bool rpz_iter_cache(struct dns_msg** msg, enum response_type* type, + struct module_qstate* qstate, struct iter_qstate* iq); + +/** + * Check a response from an authority in the iterator. + * @param[out] type: of the final response + * @param qstate: query state. + * @param iq: iterator query state. + * @param is_cname: true if the rewritten response is a CNAME + * @return one of rpz_resp_t + */ +typedef enum { + rpz_iter_resp_fail, /* Send SERVFAIL. */ + rpz_iter_resp_rewrite, /* We rewrote the response. */ + rpz_iter_resp_done, /* Restart to refetch glue. */ +} rpz_iter_resp_t; +rpz_iter_resp_t rpz_iter_resp(struct module_qstate* qstate, + struct iter_qstate* iq, struct dns_msg** resp, + bool* is_cname); + +/** + * Check a CNAME RR + * @param qstate: query state. + * @param oname: cname owner name + * @param oname_size: length of oname + * @return: one of rpz_cname_t + */ +typedef enum { + rpz_cname_fail, /* send SERVFAIL */ + rpz_cname_prepend, /* prepend CNAME as usual */ + rpz_cname_stop, /* stop before prepending this CNAME */ +} rpz_cname_t; +rpz_cname_t rpz_cname(struct module_qstate* qstate, + uint8_t* oname, size_t oname_size); + +#endif /* ENABLE_FASTRPZ */ +#endif /* UNBOUND_FASTRPZ_RPZ_H */ diff --git a/fastrpz/rpz.m4 b/fastrpz/rpz.m4 new file mode 100644 index 00000000..21235355 --- /dev/null +++ b/fastrpz/rpz.m4 @@ -0,0 +1,64 @@ +# fastrpz/rpz.m4 + +# ck_FASTRPZ +# -------------------------------------------------------------------------- +# check for Fastrpz +# --enable-fastrpz enable Fastrpz response policy zones +# --enable-fastrpz-dl Fastrpz delayed link [default=have dlopen] +# --with-fastrpz-dir directory containing librpz.so +# +# Fastrpz can be compiled into Unbound everywhere with a reasonably +# modern C compiler. It is enabled on systems with dlopen() and librpz.so. + +AC_DEFUN([ck_FASTRPZ], +[ + fastrpz_avail=yes + AC_MSG_CHECKING([for librpz __attribute__s]) + AC_TRY_COMPILE(,[ + extern void f(char *p __attribute__((unused)), ...) + __attribute__((format(printf,1,2))) __attribute__((__noreturn__));], + librpz_have_attr=yes + AC_DEFINE([LIBRPZ_HAVE_ATTR], 1, [have __attribute__s used in librpz.h]) + AC_MSG_RESULT([yes]), + librpz_have_attr=no + AC_MSG_RESULT([no])) + + AC_SEARCH_LIBS(dlopen, dl) + librpz_dl=yes + AC_CHECK_FUNCS(dlopen dlclose dlsym,,librpz_dl=no) + AC_ARG_ENABLE([fastrpz-dl], + [ --enable-fastrpz-dl Fastrpz delayed link [[default=$librpz_dl]]], + [enable_librpz_dl="$enableval"], + [enable_librpz_dl="$librpz_dl"]) + AC_ARG_WITH([fastrpz-dir], + [ --with-fastrpz-dir directory containing librpz.so], + [librpz_path="$withval/librpz.so"], [librpz_path="librpz.so"]) + AC_DEFINE_UNQUOTED([FASTRPZ_LIBRPZ_PATH], ["$librpz_path"], + [fastrpz librpz.so]) + if test "x$enable_librpz_dl" = "xyes"; then + fastrpz_lib_open=2 + else + fastrpz_lib_open=1 + # Add librpz.so to linked libraries if we are not using dlopen() + AC_SEARCH_LIBS([librpz_client_create], [rpz], [], + [fastrpz_lib_open=0 + fastrpz_avail=no]) + fi + AC_DEFINE_UNQUOTED([FASTRPZ_LIB_OPEN], [$fastrpz_lib_open], + [0=no fastrpz 1=static link 2=dlopen()]) + + AC_ARG_ENABLE([fastrpz], + AS_HELP_STRING([--enable-fastrpz],[enable Fastrpz response policy zones]), + [enable_fastrpz=$enableval],[enable_fastrpz=$fastrpz_avail]) + if test "x$enable_fastrpz" = xyes; then + AC_DEFINE([ENABLE_FASTRPZ], [1], [Enable fastrpz]) + if test "x$fastrpz_lib_open" = "x0"; then + AC_MSG_ERROR([[dlopen and librpz.so needed for fastrpz]]) + fi + # used in Makefile.in + AC_SUBST([FASTRPZ_SRC], [fastrpz/rpz.c]) + AC_SUBST([FASTRPZ_OBJ], [rpz.lo]) + elif test "x$fastrpz_avail" = "x0"; then + AC_MSG_WARN([[dlopen and librpz.so needed for fastrpz]]) + fi +]) diff --git a/iterator/iterator.c b/iterator/iterator.c index 23b07ea9..c3d31a33 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -68,6 +68,9 @@ #include "sldns/str2wire.h" #include "sldns/parseutil.h" #include "sldns/sbuffer.h" +#ifdef ENABLE_FASTRPZ +#include "fastrpz/rpz.h" +#endif /* in msec */ int UNKNOWN_SERVER_NICENESS = 376; @@ -563,6 +566,23 @@ handle_cname_response(struct module_qstate* qstate, struct iter_qstate* iq, if(ntohs(r->rk.type) == LDNS_RR_TYPE_CNAME && query_dname_compare(*mname, r->rk.dname) == 0 && !iter_find_rrset_in_prepend_answer(iq, r)) { +#ifdef ENABLE_FASTRPZ + /* Stop adding CNAME rrsets to the prepend list + * before defining an RPZ hit. */ + if(!iq->rpz_rewritten) { + switch (rpz_cname(qstate, *mname, *mname_len)) { + case rpz_cname_fail: + /* send SERVFAIL */ + return 0; + case rpz_cname_prepend: + /* save the CNAME. */ + break; + case rpz_cname_stop: + /* Pause before adding the CNAME. */ + goto stop_short; + } + } +#endif /* Add this relevant CNAME rrset to the prepend list.*/ if(!iter_add_prepend_answer(qstate, iq, r)) return 0; @@ -571,6 +591,9 @@ handle_cname_response(struct module_qstate* qstate, struct iter_qstate* iq, /* Other rrsets in the section are ignored. */ } +#ifdef ENABLE_FASTRPZ +stop_short: ; +#endif /* add authority rrsets to authority prepend, for wildcarded CNAMEs */ for(i=msg->rep->an_numrrsets; irep->an_numrrsets + msg->rep->ns_numrrsets; i++) { @@ -1231,6 +1254,7 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq, uint8_t* delname; size_t delnamelen; struct dns_msg* msg = NULL; + enum response_type type; log_query_info(VERB_DETAIL, "resolving", &qstate->qinfo); /* check effort */ @@ -1317,8 +1341,7 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq, } if(msg) { /* handle positive cache response */ - enum response_type type = response_type_from_cache(msg, - &iq->qchase); + type = response_type_from_cache(msg, &iq->qchase); if(verbosity >= VERB_ALGO) { log_dns_msg("msg from cache lookup", &msg->qinfo, msg->rep); @@ -1326,7 +1349,22 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq, (int)msg->rep->ttl, (int)msg->rep->prefetch_ttl); } +#ifdef ENABLE_FASTRPZ + } + /* Check for an RPZ hit in the cached DNS message or an existing + * RPZ CNAME rewrite that can be resolved now after a hit on the QNAME + * or client IP address. This can involve a creating a fake cache + * hit. It can also involve overriding an RESPONSE_TYPE_ANSWER + * result from response_type_from_cache(). Or it can ignore + * the cached result to refetch glue. */ + if(!iq->rpz_rewritten && + qstate->mesh_info->reply_list && + qstate->mesh_info->reply_list->query_reply.rpz && + !rpz_iter_cache(&msg, &type, qstate, iq)) + return error_response(qstate, id, LDNS_RCODE_SERVFAIL); + if(msg) { +#endif if(type == RESPONSE_TYPE_CNAME) { uint8_t* sname = 0; size_t slen = 0; @@ -2801,6 +2839,62 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq, sock_list_insert(&qstate->reply_origin, &qstate->reply->addr, qstate->reply->addrlen, qstate->region); +#ifdef ENABLE_FASTRPZ + /* Check the response for an RPZ hit. The response has already + * been saved in the cache. This should have the same effect + * as finding that response in the cache. + * We have already used rpz_iter_cache() at least once. */ + if(!iq->rpz_rewritten && + qstate->mesh_info->reply_list && + qstate->mesh_info->reply_list->query_reply.rpz) { + struct dns_msg* resp; + bool is_cname; + uint8_t* sname; + size_t slen; + + switch (rpz_iter_resp(qstate, iq, &resp, &is_cname)) { + case rpz_iter_resp_fail: + return error_response(qstate, id, + LDNS_RCODE_SERVFAIL); + case rpz_iter_resp_rewrite: + /* Prepend any initial CNAMEs from the original + * response up to a hit. */ + if(!handle_cname_response(qstate, iq, + iq->response, + &sname, &slen)) + return error_response(qstate, id, + LDNS_RCODE_SERVFAIL); + if (resp) { + iq->response = resp; + iq->rpz_security = resp->rep->security; + iq->rpz_rewritten = 1; + + /* Send the rewritten record if it + * is not a CNAME. */ + if(!is_cname) + break; + + /* Prepend the new CNAME + * and restart to resolve it. */ + if(!handle_cname_response(qstate, iq, + resp, &sname, &slen)) + return error_response(qstate, id, + LDNS_RCODE_SERVFAIL); + } + iq->qchase.qname = sname; + iq->qchase.qname_len = slen; + iq->dp = NULL; + iq->refetch_glue = 0; + iq->query_restart_count++; + iq->sent_count = 0; + iq->state = INIT_REQUEST_STATE; + return 1; + + case rpz_iter_resp_done: + break; + } + } +#endif if(iq->minimisation_state != DONOT_MINIMISE_STATE && !(iq->chase_flags & BIT_RD)) { if(FLAGS_GET_RCODE(iq->response->rep->flags) != @@ -3563,12 +3657,44 @@ processFinished(struct module_qstate* qstate, struct iter_qstate* iq, * but only if we did recursion. The nonrecursion referral * from cache does not need to be stored in the msg cache. */ if(!qstate->no_cache_store && qstate->query_flags&BIT_RD) { +#ifdef ENABLE_FASTRPZ + /* Do not save RPZ rewritten messages. */ + if(!iq->rpz_rewritten) +#endif iter_dns_store(qstate->env, &qstate->qinfo, iq->response->rep, 0, qstate->prefetch_leeway, iq->dp&&iq->dp->has_parent_side_NS, qstate->region, qstate->query_flags); } } +#ifdef ENABLE_FASTRPZ + if(iq->rpz_rewritten) { + /* Restore RPZ marks on a rewritten response. The marks + * are lost if the rewrite is to a CNAME. */ + iq->response->rep->security = iq->rpz_security; + + /* Append the RPZ SOA to rewritten CNAME chains. */ + if(iq->rpz_soa) { + struct ub_packed_rrset_key** sets; + uint n; + + n = iq->response->rep->rrset_count; + sets = regional_alloc(qstate->region, + (1+n) * sizeof(*sets)); + if(!sets) { + log_err("append RPZ SOA: out of memory"); + return error_response(qstate, id, + LDNS_RCODE_SERVFAIL); + } + memcpy(sets, iq->response->rep->rrsets, + n * sizeof(struct ub_packed_rrset_key*)); + sets[n] = iq->rpz_soa; + iq->response->rep->rrsets = sets; + ++iq->response->rep->rrset_count; + ++iq->response->rep->ar_numrrsets; + } + } +#endif qstate->return_rcode = LDNS_RCODE_NOERROR; qstate->return_msg = iq->response; return 0; diff --git a/iterator/iterator.h b/iterator/iterator.h index 342ac207..49b0ecdd 100644 --- a/iterator/iterator.h +++ b/iterator/iterator.h @@ -396,6 +396,16 @@ struct iter_qstate { */ int minimise_count; + +#ifdef ENABLE_FASTRPZ + /** The response has been rewritten by RPZ. */ + int rpz_rewritten; + /** RPZ SOA RR for the ADDITIONAL section */ + struct ub_packed_rrset_key* rpz_soa; + /** sec_status_rpz_rewritten or sec_status_rpz_drop if rewritten. */ + enum sec_status rpz_security; +#endif + /** * Count number of time-outs. Used to prevent resolving failures when * the QNAME minimisation QTYPE is blocked. Used to determine if diff --git a/services/cache/dns.c b/services/cache/dns.c index 7b6e142c..6d7449f5 100644 --- a/services/cache/dns.c +++ b/services/cache/dns.c @@ -969,6 +969,14 @@ dns_cache_store(struct module_env* env, struct query_info* msgqinf, struct regional* region, uint32_t flags) { struct reply_info* rep = NULL; + +#ifdef ENABLE_FASTRPZ + /* Never save RPZ rewritten data. */ + if (msgrep->security == sec_status_rpz_drop || + msgrep->security == sec_status_rpz_rewritten) + return 1; +#endif + /* alloc, malloc properly (not in region, like msg is) */ rep = reply_info_copy(msgrep, env->alloc, NULL); if(!rep) diff --git a/services/mesh.c b/services/mesh.c index 4b0c5db4..eb9cfa5b 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -61,6 +61,9 @@ #include "sldns/wire2str.h" #include "services/localzone.h" #include "util/data/dname.h" +#ifdef ENABLE_FASTRPZ +#include "fastrpz/rpz.h" +#endif #include "respip/respip.h" #include "services/listen_dnsport.h" @@ -1207,6 +1210,13 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep, else secure = 0; if(!rep && rcode == LDNS_RCODE_NOERROR) rcode = LDNS_RCODE_SERVFAIL; +#ifdef ENABLE_FASTRPZ + /* Drop the response here for LIBRPZ_POLICY_DROP after iteration. */ + if(rep && rep->security == sec_status_rpz_drop) { + log_query_info(VERB_QUERY, "rpz drop", &m->s.qinfo); + secure = 0; + } else +#endif /* send the reply */ /* We don't reuse the encoded answer if either the previous or current * response has a local alias. We could compare the alias records @@ -1434,6 +1444,7 @@ struct mesh_state* mesh_area_find(struct mesh_area* mesh, key.s.is_valrec = valrec; key.s.qinfo = *qinfo; key.s.query_flags = qflags; + key.reply_list = NULL; /* We are searching for a similar mesh state when we DO want to * aggregate the state. Thus unique is set to NULL. (default when we * desire aggregation).*/ @@ -1480,6 +1491,10 @@ int mesh_state_add_reply(struct mesh_state* s, struct edns_data* edns, if(!r) return 0; r->query_reply = *rep; +#ifdef ENABLE_FASTRPZ + /* The new reply structure owns the RPZ state. */ + rep->rpz = NULL; +#endif r->edns = *edns; if(edns->opt_list) { r->edns.opt_list = edns_opt_copy_region(edns->opt_list, diff --git a/util/config_file.c b/util/config_file.c index 0e9ee471..a5fd72e0 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -1495,6 +1495,8 @@ config_delete(struct config_file* cfg) free(cfg->dnstap_tls_client_cert_file); free(cfg->dnstap_identity); free(cfg->dnstap_version); + if (cfg->rpz_cstr) + free(cfg->rpz_cstr); config_deldblstrlist(cfg->ratelimit_for_domain); config_deldblstrlist(cfg->ratelimit_below_domain); config_delstrlist(cfg->python_script); diff --git a/util/config_file.h b/util/config_file.h index 66e5025d..504f4f92 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -522,6 +522,11 @@ struct config_file { /** true to disable DNSSEC lameness check in iterator */ int disable_dnssec_lame_check; + /** true to enable RPZ */ + int rpz_enable; + /** RPZ configuration */ + char* rpz_cstr; + /** ratelimit for ip addresses. 0 is off, otherwise qps (unless overridden) */ int ip_ratelimit; /** number of slabs for ip_ratelimit cache */ diff --git a/util/configlexer.lex b/util/configlexer.lex index 83cea4b9..9a7feea4 100644 --- a/util/configlexer.lex +++ b/util/configlexer.lex @@ -467,6 +467,10 @@ dnstap-log-forwarder-query-messages{COLON} { YDVAR(1, VAR_DNSTAP_LOG_FORWARDER_QUERY_MESSAGES) } dnstap-log-forwarder-response-messages{COLON} { YDVAR(1, VAR_DNSTAP_LOG_FORWARDER_RESPONSE_MESSAGES) } +rpz{COLON} { YDVAR(0, VAR_RPZ) } +rpz-enable{COLON} { YDVAR(1, VAR_RPZ_ENABLE) } +rpz-zone{COLON} { YDVAR(1, VAR_RPZ_ZONE) } +rpz-option{COLON} { YDVAR(1, VAR_RPZ_OPTION) } disable-dnssec-lame-check{COLON} { YDVAR(1, VAR_DISABLE_DNSSEC_LAME_CHECK) } ip-ratelimit{COLON} { YDVAR(1, VAR_IP_RATELIMIT) } ratelimit{COLON} { YDVAR(1, VAR_RATELIMIT) } diff --git a/util/configparser.y b/util/configparser.y index fe600a99..ce43390f 100644 --- a/util/configparser.y +++ b/util/configparser.y @@ -128,6 +128,7 @@ extern struct config_parser_state* cfg_parser; %token VAR_DNSTAP_LOG_CLIENT_RESPONSE_MESSAGES %token VAR_DNSTAP_LOG_FORWARDER_QUERY_MESSAGES %token VAR_DNSTAP_LOG_FORWARDER_RESPONSE_MESSAGES +%token VAR_RPZ VAR_RPZ_ENABLE VAR_RPZ_ZONE VAR_RPZ_OPTION %token VAR_RESPONSE_IP_TAG VAR_RESPONSE_IP VAR_RESPONSE_IP_DATA %token VAR_HARDEN_ALGO_DOWNGRADE VAR_IP_TRANSPARENT %token VAR_IP_DSCP @@ -179,7 +180,7 @@ extern struct config_parser_state* cfg_parser; %% toplevelvars: /* empty */ | toplevelvars toplevelvar ; -toplevelvar: serverstart contents_server | stubstart contents_stub | +toplevelvar: serverstart contents_server | stubstart contents_stub | rpzstart contents_rpz | forwardstart contents_forward | pythonstart contents_py | rcstart contents_rc | dtstart contents_dt | viewstart contents_view | dnscstart contents_dnsc | cachedbstart contents_cachedb | @@ -2939,6 +2940,50 @@ dt_dnstap_log_forwarder_response_messages: VAR_DNSTAP_LOG_FORWARDER_RESPONSE_MES free($2); } ; +rpzstart: VAR_RPZ + { + OUTYY(("\nP(rpz:)\n")); + } + ; +contents_rpz: contents_rpz content_rpz + | ; +content_rpz: rpz_enable | rpz_zone | rpz_option + ; +rpz_enable: VAR_RPZ_ENABLE STRING_ARG + { + OUTYY(("P(rpz_enable:%s)\n", $2)); + if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0) + yyerror("expected yes or no."); + else cfg_parser->cfg->rpz_enable = (strcmp($2, "yes")==0); + free($2); + } + ; +rpz_zone: VAR_RPZ_ZONE STRING_ARG + { + char *new_cstr, *old_cstr; + + OUTYY(("P(rpz_zone:%s)\n", $2)); + old_cstr = cfg_parser->cfg->rpz_cstr; + if(asprintf(&new_cstr, "%s\nzone %s", old_cstr?old_cstr:"", $2) == -1) {new_cstr = NULL; yyerror("out of memory");} + else if(!new_cstr) + yyerror("out of memory"); + free(old_cstr); + cfg_parser->cfg->rpz_cstr = new_cstr; + } + ; +rpz_option: VAR_RPZ_OPTION STRING_ARG + { + char *new_cstr, *old_cstr; + + OUTYY(("P(rpz_option:%s)\n", $2)); + old_cstr = cfg_parser->cfg->rpz_cstr; + if(asprintf(&new_cstr, "%s\n%s", old_cstr ? old_cstr : "", $2) == -1) {new_cstr = NULL; yyerror("out of memory");} + else if(!new_cstr) + yyerror("out of memory"); + free(old_cstr); + cfg_parser->cfg->rpz_cstr = new_cstr; + } + ; pythonstart: VAR_PYTHON { OUTYY(("\nP(python:)\n")); diff --git a/util/data/msgencode.c b/util/data/msgencode.c index be69f628..f10773aa 100644 --- a/util/data/msgencode.c +++ b/util/data/msgencode.c @@ -592,6 +592,35 @@ insert_section(struct reply_info* rep, size_t num_rrsets, uint16_t* num_rrs, return RETVAL_OK; } +#ifdef ENABLE_FASTRPZ +/* Insert the RPZ SOA even with MINIMAL_RESPONSES */ +static int +insert_rpz_soa(struct reply_info* rep, size_t num_rrsets, uint16_t* num_rrs, + sldns_buffer* pkt, size_t rrsets_before, time_t timenow, + struct regional* region, struct compress_tree_node** tree, + size_t rr_offset) +{ + int r; + size_t i, setstart; + + *num_rrs = 0; + for(i=0; irrsets[rrsets_before+i]->rk.type != LDNS_RR_TYPE_SOA) + continue; + setstart = sldns_buffer_position(pkt); + if((r=packed_rrset_encode(rep->rrsets[rrsets_before+i], + pkt, num_rrs, timenow, region, + 1, 0, tree, LDNS_SECTION_ADDITIONAL, + LDNS_RR_TYPE_ANY, 0, rr_offset)) + != RETVAL_OK) { + sldns_buffer_set_position(pkt, setstart); + return r; + } + } + return RETVAL_OK; +} + +#endif /** store query section in wireformat buffer, return RETVAL */ static int insert_query(struct query_info* qinfo, struct compress_tree_node** tree, @@ -779,6 +808,19 @@ reply_info_encode(struct query_info* qinfo, struct reply_info* rep, } sldns_buffer_write_u16_at(buffer, 10, arcount); } +#ifdef ENABLE_FASTRPZ + } else if(rep->security == sec_status_rpz_rewritten) { + /* Insert the RPZ SOA for rpz even with MINIMAL_RESPONSES */ + r = insert_rpz_soa(rep, rep->ar_numrrsets, &arcount, buffer, + rep->an_numrrsets + rep->ns_numrrsets, + timenow, region, &tree, rr_offset); + if(r!= RETVAL_OK) { + if(r != RETVAL_TRUNC) + return 0; + /* no need to set TC bit, this is the additional */ + sldns_buffer_write_u16_at(buffer, 10, arcount); + } +#endif } sldns_buffer_flip(buffer); return 1; diff --git a/util/data/packed_rrset.c b/util/data/packed_rrset.c index 4b0294f9..3b3838f6 100644 --- a/util/data/packed_rrset.c +++ b/util/data/packed_rrset.c @@ -256,6 +256,10 @@ sec_status_to_string(enum sec_status s) case sec_status_insecure: return "sec_status_insecure"; case sec_status_secure_sentinel_fail: return "sec_status_secure_sentinel_fail"; case sec_status_secure: return "sec_status_secure"; +#ifdef ENABLE_FASTRPZ + case sec_status_rpz_rewritten: return "sec_status_rpz_rewritten"; + case sec_status_rpz_drop: return "sec_status_rpz_drop"; +#endif } return "unknown_sec_status_value"; } diff --git a/util/data/packed_rrset.h b/util/data/packed_rrset.h index 729877ba..ccd1a0c2 100644 --- a/util/data/packed_rrset.h +++ b/util/data/packed_rrset.h @@ -193,7 +193,15 @@ enum sec_status { sec_status_secure_sentinel_fail, /** SECURE means that the object (RRset or message) validated * according to local policy. */ - sec_status_secure + sec_status_secure, +#ifdef ENABLE_FASTRPZ + /** RPZ_REWRITTEN means that the response has been rewritten by + * rpz and so cannot be verified. */ + sec_status_rpz_rewritten, + /** RPZ_DROP means that the response has been rewritten by rpz + * as silence. */ + sec_status_rpz_drop +#endif }; /** diff --git a/util/netevent.c b/util/netevent.c index 3e7a433e..f20d806f 100644 --- a/util/netevent.c +++ b/util/netevent.c @@ -57,6 +57,9 @@ #ifdef HAVE_OPENSSL_ERR_H #include #endif +#ifdef ENABLE_FASTRPZ +#include "fastrpz/rpz.h" +#endif /* -------- Start of local definitions -------- */ /** if CMSG_ALIGN is not defined on this platform, a workaround */ @@ -596,6 +599,9 @@ comm_point_udp_ancil_callback(int fd, short event, void* arg) struct cmsghdr* cmsg; #endif /* S_SPLINT_S */ +#ifdef ENABLE_FASTRPZ + rep.rpz = NULL; +#endif rep.c = (struct comm_point*)arg; log_assert(rep.c->type == comm_udp); @@ -685,6 +691,9 @@ comm_point_udp_callback(int fd, short event, void* arg) int i; struct sldns_buffer *buffer; +#ifdef ENABLE_FASTRPZ + rep.rpz = NULL; +#endif rep.c = (struct comm_point*)arg; log_assert(rep.c->type == comm_udp); @@ -728,6 +737,9 @@ comm_point_udp_callback(int fd, short event, void* arg) (void)comm_point_send_udp_msg(rep.c, buffer, (struct sockaddr*)&rep.addr, rep.addrlen); } +#ifdef ENABLE_FASTRPZ + rpz_end(&rep); +#endif if(!rep.c || rep.c->fd != fd) /* commpoint closed to -1 or reused for another UDP port. Note rep.c cannot be reused with TCP fd. */ break; @@ -3175,6 +3187,9 @@ comm_point_send_reply(struct comm_reply *repinfo) repinfo->c->tcp_timeout_msec); } } +#ifdef ENABLE_FASTRPZ + rpz_end(repinfo); +#endif } void @@ -3184,6 +3199,9 @@ comm_point_drop_reply(struct comm_reply* repinfo) return; log_assert(repinfo->c); log_assert(repinfo->c->type != comm_tcp_accept); +#ifdef ENABLE_FASTRPZ + rpz_end(repinfo); +#endif if(repinfo->c->type == comm_udp) return; if(repinfo->c->tcp_req_info) @@ -3205,6 +3223,9 @@ comm_point_start_listening(struct comm_point* c, int newfd, int msec) { verbose(VERB_ALGO, "comm point start listening %d (%d msec)", c->fd==-1?newfd:c->fd, msec); +#ifdef ENABLE_FASTRPZ + rpz_end(&c->repinfo); +#endif if(c->type == comm_tcp_accept && !c->tcp_free) { /* no use to start listening no free slots. */ return; diff --git a/util/netevent.h b/util/netevent.h index bb2cd1e5..666067e8 100644 --- a/util/netevent.h +++ b/util/netevent.h @@ -120,6 +120,10 @@ struct comm_reply { /** return type 0 (none), 4(IP4), 6(IP6) */ int srctype; /* DnsCrypt context */ +#ifdef ENABLE_FASTRPZ + /** per-request RPZ state */ + struct commreply_rpz* rpz; +#endif #ifdef USE_DNSCRYPT uint8_t client_nonce[crypto_box_HALF_NONCEBYTES]; uint8_t nmkey[crypto_box_BEFORENMBYTES]; diff --git a/validator/validator.c b/validator/validator.c index c3ca0a27..15251988 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -2761,6 +2761,12 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq, default: /* NSEC proof did not work, try next */ break; +#ifdef ENABLE_FASTRPZ + case sec_status_rpz_rewritten: + case sec_status_rpz_drop: + fatal_exit("impossible RPZ sec_status"); + break; +#endif } sec = nsec3_prove_nods(qstate->env, ve, @@ -2794,6 +2800,12 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq, default: /* NSEC3 proof did not work */ break; +#ifdef ENABLE_FASTRPZ + case sec_status_rpz_rewritten: + case sec_status_rpz_drop: + fatal_exit("impossible RPZ sec_status"); + break; +#endif } /* Apparently, no available NSEC/NSEC3 proved NODATA, so