From: George Thessalonikefs Date: Tue, 6 Dec 2016 13:42:51 +0000 (+0000) Subject: - Added generic EDNS code for registering known EDNS option codes, X-Git-Tag: release-1.6.0rc1~10 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=7b948b064732f37a1db5c396aef9d6c11efd5255;p=thirdparty%2Funbound.git - Added generic EDNS code for registering known EDNS option codes, bypassing the cache response stage and uniquifying mesh states. Four EDNS option lists were added to module_qstate (module_qstate.edns_opts_*) to store EDNS options from/to front/back side. - Added two flags to module_qstate (no_cache_lookup, no_cache_store) that control the modules' cache interactions. - Added code for registering inplace callback functions. The registered functions can be called just before replying with local data or Chaos, replying from cache, replying with SERVFAIL, replying with a resolved query, sending a query to a nameserver. The functions can inspect the available data and maybe change response/query related data (i.e. append EDNS options). - Updated Python module for the above. - Updated Python documentation. git-svn-id: file:///svn/unbound/trunk@3947 be551aaa-1e26-0410-a405-d3ace91eadb9 --- diff --git a/cachedb/cachedb.c b/cachedb/cachedb.c index cbec59df3..a326d6ef8 100644 --- a/cachedb/cachedb.c +++ b/cachedb/cachedb.c @@ -547,8 +547,8 @@ cachedb_handle_query(struct module_qstate* qstate, return; } - if(qstate->blacklist) { - /* cache is blacklisted */ + if(qstate->blacklist || qstate->no_cache_lookup) { + /* cache is blacklisted or we are instructed from edns to not look */ /* pass request to next module */ qstate->ext_state[id] = module_wait_module; return; @@ -600,8 +600,8 @@ static void cachedb_handle_response(struct module_qstate* qstate, struct cachedb_qstate* ATTR_UNUSED(iq), struct cachedb_env* ie, int id) { - /* check if we are enabled, and skip if not */ - if(!ie->enabled) { + /* check if we are not enabled or instructed to not cache, and skip */ + if(!ie->enabled || qstate->no_cache_store) { /* we are done with the query */ qstate->ext_state[id] = module_finished; return; diff --git a/daemon/daemon.c b/daemon/daemon.c index 53a32dcf8..88c695be3 100644 --- a/daemon/daemon.c +++ b/daemon/daemon.c @@ -249,9 +249,16 @@ daemon_init(void) free(daemon); return NULL; } + /* init edns_known_options */ + if(!edns_known_options_init(daemon->env)) { + free(daemon->env); + free(daemon); + return NULL; + } alloc_init(&daemon->superalloc, NULL, 0); daemon->acl = acl_list_create(); if(!daemon->acl) { + edns_known_options_delete(daemon->env); free(daemon->env); free(daemon); return NULL; @@ -348,6 +355,7 @@ static void daemon_setup_modules(struct daemon* daemon) daemon->env)) { fatal_exit("failed to setup modules"); } + log_edns_known_options(VERB_ALGO, daemon->env); } /** @@ -644,6 +652,8 @@ daemon_delete(struct daemon* daemon) slabhash_delete(daemon->env->msg_cache); rrset_cache_delete(daemon->env->rrset_cache); infra_delete(daemon->env->infra_cache); + edns_known_options_delete(daemon->env); + inplace_cb_lists_delete(daemon->env); } ub_randfree(daemon->rand); alloc_clear(&daemon->superalloc); diff --git a/daemon/worker.c b/daemon/worker.c index 94f636b65..486cf4afe 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -460,8 +460,9 @@ answer_norec_from_cache(struct worker* worker, struct query_info* qinfo, edns->udp_size = EDNS_ADVERTISED_SIZE; edns->ext_rcode = 0; edns->bits &= EDNS_DO; - if(!edns_opt_inplace_reply(edns, worker->scratchpad)) - return 0; + if(!inplace_cb_reply_servfail_call(&worker->env, qinfo, NULL, + msg->rep, LDNS_RCODE_SERVFAIL, edns, worker->scratchpad)) + return 0; error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL, &msg->qinfo, id, flags, edns); if(worker->stats.extended) { @@ -489,12 +490,16 @@ answer_norec_from_cache(struct worker* worker, struct query_info* qinfo, edns->udp_size = EDNS_ADVERTISED_SIZE; edns->ext_rcode = 0; edns->bits &= EDNS_DO; - if(!edns_opt_inplace_reply(edns, worker->scratchpad)) - return 0; + if(!inplace_cb_reply_cache_call(&worker->env, qinfo, NULL, msg->rep, + flags & LDNS_RCODE_MASK, edns, worker->scratchpad)) + return 0; msg->rep->flags |= BIT_QR|BIT_RA; if(!reply_info_answer_encode(&msg->qinfo, msg->rep, id, flags, repinfo->c->buffer, 0, 1, worker->scratchpad, udpsize, edns, (int)(edns->bits & EDNS_DO), secure)) { + if(!inplace_cb_reply_servfail_call(&worker->env, qinfo, NULL, NULL, + LDNS_RCODE_SERVFAIL, edns, worker->scratchpad)) + edns->opt_list = NULL; error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL, &msg->qinfo, id, flags, edns); } @@ -559,8 +564,9 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo, edns->udp_size = EDNS_ADVERTISED_SIZE; edns->ext_rcode = 0; edns->bits &= EDNS_DO; - if(!edns_opt_inplace_reply(edns, worker->scratchpad)) - return 0; + if(!inplace_cb_reply_servfail_call(&worker->env, qinfo, NULL, rep, + LDNS_RCODE_SERVFAIL, edns, worker->scratchpad)) + return 0; error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL, qinfo, id, flags, edns); rrset_array_unlock_touch(worker->env.rrset_cache, @@ -591,11 +597,15 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo, edns->udp_size = EDNS_ADVERTISED_SIZE; edns->ext_rcode = 0; edns->bits &= EDNS_DO; - if(!edns_opt_inplace_reply(edns, worker->scratchpad)) - return 0; + if(!inplace_cb_reply_cache_call(&worker->env, qinfo, NULL, rep, + flags & LDNS_RCODE_MASK, edns, worker->scratchpad)) + return 0; if(!reply_info_answer_encode(qinfo, rep, id, flags, repinfo->c->buffer, timenow, 1, worker->scratchpad, udpsize, edns, (int)(edns->bits & EDNS_DO), secure)) { + if(!inplace_cb_reply_servfail_call(&worker->env, qinfo, NULL, NULL, + LDNS_RCODE_SERVFAIL, edns, worker->scratchpad)) + edns->opt_list = NULL; error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL, qinfo, id, flags, edns); } @@ -667,8 +677,9 @@ chaos_replystr(sldns_buffer* pkt, const char* str, struct edns_data* edns, edns->edns_version = EDNS_ADVERTISED_VERSION; edns->udp_size = EDNS_ADVERTISED_SIZE; edns->bits &= EDNS_DO; - if(!edns_opt_inplace_reply(edns, worker->scratchpad)) - edns->opt_list = NULL; + if(!inplace_cb_reply_local_call(&worker->env, NULL, NULL, NULL, + LDNS_RCODE_NOERROR, edns, worker->scratchpad)) + edns->opt_list = NULL; attach_edns_record(pkt, edns); } @@ -919,9 +930,9 @@ worker_handle_request(struct comm_point* c, void* arg, int error, regional_free_all(worker->scratchpad); goto send_reply; } - if(local_zones_answer(worker->daemon->local_zones, &qinfo, &edns, - c->buffer, worker->scratchpad, repinfo, - acladdr->taglist, acladdr->taglen, acladdr->tag_actions, + if(local_zones_answer(worker->daemon->local_zones, &worker->env, &qinfo, + &edns, c->buffer, worker->scratchpad, repinfo, acladdr->taglist, + acladdr->taglen, acladdr->tag_actions, acladdr->tag_actions_size, acladdr->tag_datas, acladdr->tag_datas_size, worker->daemon->cfg->tagname, worker->daemon->cfg->num_tags, acladdr->view)) { @@ -981,48 +992,50 @@ worker_handle_request(struct comm_point* c, void* arg, int error, qinfo.qname_len = d->rr_len[0] - 2; } - h = query_info_hash(&qinfo, sldns_buffer_read_u16_at(c->buffer, 2)); - if((e=slabhash_lookup(worker->env.msg_cache, h, &qinfo, 0))) { - /* answer from cache - we have acquired a readlock on it */ - if(answer_from_cache(worker, &qinfo, - (struct reply_info*)e->data, - *(uint16_t*)(void *)sldns_buffer_begin(c->buffer), - sldns_buffer_read_u16_at(c->buffer, 2), repinfo, - &edns)) { - /* prefetch it if the prefetch TTL expired */ - if((worker->env.cfg->prefetch || worker->env.cfg->serve_expired) - && *worker->env.now >= - ((struct reply_info*)e->data)->prefetch_ttl) { - time_t leeway = ((struct reply_info*)e-> - data)->ttl - *worker->env.now; - if(((struct reply_info*)e->data)->ttl - < *worker->env.now) - leeway = 0; + if(!edns_bypass_cache_stage(edns.opt_list, &worker->env)) { + h = query_info_hash(&qinfo, sldns_buffer_read_u16_at(c->buffer, 2)); + if((e=slabhash_lookup(worker->env.msg_cache, h, &qinfo, 0))) { + /* answer from cache - we have acquired a readlock on it */ + if(answer_from_cache(worker, &qinfo, + (struct reply_info*)e->data, + *(uint16_t*)(void *)sldns_buffer_begin(c->buffer), + sldns_buffer_read_u16_at(c->buffer, 2), repinfo, + &edns)) { + /* prefetch it if the prefetch TTL expired */ + if((worker->env.cfg->prefetch || worker->env.cfg->serve_expired) + && *worker->env.now >= + ((struct reply_info*)e->data)->prefetch_ttl) { + time_t leeway = ((struct reply_info*)e-> + data)->ttl - *worker->env.now; + if(((struct reply_info*)e->data)->ttl + < *worker->env.now) + leeway = 0; + lock_rw_unlock(&e->lock); + reply_and_prefetch(worker, &qinfo, + sldns_buffer_read_u16_at(c->buffer, 2), + repinfo, leeway); + rc = 0; + regional_free_all(worker->scratchpad); + goto send_reply_rc; + } lock_rw_unlock(&e->lock); - reply_and_prefetch(worker, &qinfo, - sldns_buffer_read_u16_at(c->buffer, 2), - repinfo, leeway); - rc = 0; regional_free_all(worker->scratchpad); - goto send_reply_rc; + goto send_reply; } + verbose(VERB_ALGO, "answer from the cache failed"); lock_rw_unlock(&e->lock); - regional_free_all(worker->scratchpad); - goto send_reply; } - verbose(VERB_ALGO, "answer from the cache failed"); - lock_rw_unlock(&e->lock); - } - if(!LDNS_RD_WIRE(sldns_buffer_begin(c->buffer))) { - if(answer_norec_from_cache(worker, &qinfo, - *(uint16_t*)(void *)sldns_buffer_begin(c->buffer), - sldns_buffer_read_u16_at(c->buffer, 2), repinfo, - &edns)) { - regional_free_all(worker->scratchpad); - goto send_reply; + if(!LDNS_RD_WIRE(sldns_buffer_begin(c->buffer))) { + if(answer_norec_from_cache(worker, &qinfo, + *(uint16_t*)(void *)sldns_buffer_begin(c->buffer), + sldns_buffer_read_u16_at(c->buffer, 2), repinfo, + &edns)) { + regional_free_all(worker->scratchpad); + goto send_reply; + } + verbose(VERB_ALGO, "answer norec from cache -- " + "need to validate or not primed"); } - verbose(VERB_ALGO, "answer norec from cache -- " - "need to validate or not primed"); } sldns_buffer_rewind(c->buffer); server_stats_querymiss(&worker->stats, worker); @@ -1376,11 +1389,10 @@ worker_delete(struct worker* worker) } struct outbound_entry* -worker_send_query(uint8_t* qname, size_t qnamelen, uint16_t qtype, - uint16_t qclass, uint16_t flags, int dnssec, int want_dnssec, - int nocaps, struct edns_option* opt_list, - struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, - size_t zonelen, int ssl_upstream, struct module_qstate* q) +worker_send_query(struct query_info* qinfo, uint16_t flags, int dnssec, + int want_dnssec, int nocaps, struct sockaddr_storage* addr, + socklen_t addrlen, uint8_t* zone, size_t zonelen, int ssl_upstream, + struct module_qstate* q) { struct worker* worker = q->env->worker; struct outbound_entry* e = (struct outbound_entry*)regional_alloc( @@ -1388,11 +1400,10 @@ worker_send_query(uint8_t* qname, size_t qnamelen, uint16_t qtype, if(!e) return NULL; e->qstate = q; - e->qsent = outnet_serviced_query(worker->back, qname, - qnamelen, qtype, qclass, flags, dnssec, want_dnssec, nocaps, - q->env->cfg->tcp_upstream, ssl_upstream, opt_list, - addr, addrlen, zone, zonelen, worker_handle_service_reply, e, - worker->back->udp_buff); + e->qsent = outnet_serviced_query(worker->back, qinfo, flags, dnssec, + want_dnssec, nocaps, q->env->cfg->tcp_upstream, + ssl_upstream, addr, addrlen, zone, zonelen, q, + worker_handle_service_reply, e, worker->back->udp_buff, q->env); if(!e->qsent) { return NULL; } @@ -1432,15 +1443,13 @@ void worker_stop_accept(void* arg) } /* --- fake callbacks for fptr_wlist to work --- */ -struct outbound_entry* libworker_send_query(uint8_t* ATTR_UNUSED(qname), - size_t ATTR_UNUSED(qnamelen), uint16_t ATTR_UNUSED(qtype), - uint16_t ATTR_UNUSED(qclass), uint16_t ATTR_UNUSED(flags), - int ATTR_UNUSED(dnssec), int ATTR_UNUSED(want_dnssec), - int ATTR_UNUSED(nocaps), struct edns_option* ATTR_UNUSED(opt_list), - struct sockaddr_storage* ATTR_UNUSED(addr), - socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone), - size_t ATTR_UNUSED(zonelen), int ATTR_UNUSED(ssl_upstream), - struct module_qstate* ATTR_UNUSED(q)) +struct outbound_entry* libworker_send_query( + struct query_info* ATTR_UNUSED(qinfo), + uint16_t ATTR_UNUSED(flags), int ATTR_UNUSED(dnssec), + int ATTR_UNUSED(want_dnssec), int ATTR_UNUSED(nocaps), + struct sockaddr_storage* ATTR_UNUSED(addr), socklen_t ATTR_UNUSED(addrlen), + uint8_t* ATTR_UNUSED(zone), size_t ATTR_UNUSED(zonelen), + int ATTR_UNUSED(ssl_upstream), struct module_qstate* ATTR_UNUSED(q)) { log_assert(0); return 0; diff --git a/daemon/worker.h b/daemon/worker.h index 63613430b..d6c87c807 100644 --- a/daemon/worker.h +++ b/daemon/worker.h @@ -61,6 +61,7 @@ struct ub_randstate; struct regional; struct tube; struct daemon_remote; +struct query_info; /** worker commands */ enum worker_commands { diff --git a/dns64/dns64.c b/dns64/dns64.c index 5fa2096b8..befec864d 100644 --- a/dns64/dns64.c +++ b/dns64/dns64.c @@ -825,8 +825,9 @@ dns64_inform_super(struct module_qstate* qstate, int id, } /* Store the generated response in cache. */ - if (!dns_cache_store(super->env, &super->qinfo, super->return_msg->rep, - 0, 0, 0, NULL, super->query_flags)) + if (!super->no_cache_store && + !dns_cache_store(super->env, &super->qinfo, super->return_msg->rep, + 0, 0, 0, NULL, super->query_flags)) log_err("out of memory"); } diff --git a/doc/Changelog b/doc/Changelog index 305dec4e9..96008eeba 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,19 @@ +6 December 2016: George + - Added generic EDNS code for registering known EDNS option codes, + bypassing the cache response stage and uniquifying mesh states. Four EDNS + option lists were added to module_qstate (module_qstate.edns_opts_*) to + store EDNS options from/to front/back side. + - Added two flags to module_qstate (no_cache_lookup, no_cache_store) that + control the modules' cache interactions. + - Added code for registering inplace callback functions. The registered + functions can be called just before replying with local data or Chaos, + replying from cache, replying with SERVFAIL, replying with a resolved + query, sending a query to a nameserver. The functions can inspect the + available data and maybe change response/query related data (i.e. append + EDNS options). + - Updated Python module for the above. + - Updated Python documentation. + 5 December 2016: Ralph - Fix #1173: differ local-zone type deny from unset tag_actions element. diff --git a/iterator/iterator.c b/iterator/iterator.c index dfe4fa6f2..f60893323 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -230,9 +230,8 @@ error_supers(struct module_qstate* qstate, int id, struct module_qstate* super) return; } else { /* see if the failure did get (parent-lame) info */ - if(!cache_fill_missing(super->env, - super_iq->qchase.qclass, super->region, - super_iq->dp)) + if(!cache_fill_missing(super->env, super_iq->qchase.qclass, + super->region, super_iq->dp)) log_err("out of memory adding missing"); } dpns->resolved = 1; /* mark as failed */ @@ -278,27 +277,29 @@ error_response(struct module_qstate* qstate, int id, int rcode) static int error_response_cache(struct module_qstate* qstate, int id, int rcode) { - /* store in cache */ - struct reply_info err; - if(qstate->prefetch_leeway > NORR_TTL) { - verbose(VERB_ALGO, "error response for prefetch in cache"); - /* attempt to adjust the cache entry prefetch */ - if(dns_cache_prefetch_adjust(qstate->env, &qstate->qinfo, - NORR_TTL, qstate->query_flags)) - return error_response(qstate, id, rcode); - /* if that fails (not in cache), fall through to store err */ - } - memset(&err, 0, sizeof(err)); - err.flags = (uint16_t)(BIT_QR | BIT_RA); - FLAGS_SET_RCODE(err.flags, rcode); - err.qdcount = 1; - err.ttl = NORR_TTL; - err.prefetch_ttl = PREFETCH_TTL_CALC(err.ttl); - /* do not waste time trying to validate this servfail */ - err.security = sec_status_indeterminate; - verbose(VERB_ALGO, "store error response in message cache"); - iter_dns_store(qstate->env, &qstate->qinfo, &err, 0, 0, 0, NULL, - qstate->query_flags); + if(!qstate->no_cache_store) { + /* store in cache */ + struct reply_info err; + if(qstate->prefetch_leeway > NORR_TTL) { + verbose(VERB_ALGO, "error response for prefetch in cache"); + /* attempt to adjust the cache entry prefetch */ + if(dns_cache_prefetch_adjust(qstate->env, &qstate->qinfo, + NORR_TTL, qstate->query_flags)) + return error_response(qstate, id, rcode); + /* if that fails (not in cache), fall through to store err */ + } + memset(&err, 0, sizeof(err)); + err.flags = (uint16_t)(BIT_QR | BIT_RA); + FLAGS_SET_RCODE(err.flags, rcode); + err.qdcount = 1; + err.ttl = NORR_TTL; + err.prefetch_ttl = PREFETCH_TTL_CALC(err.ttl); + /* do not waste time trying to validate this servfail */ + err.security = sec_status_indeterminate; + verbose(VERB_ALGO, "store error response in message cache"); + iter_dns_store(qstate->env, &qstate->qinfo, &err, 0, 0, 0, NULL, + qstate->query_flags); + } return error_response(qstate, id, rcode); } @@ -969,7 +970,7 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq, { uint8_t* delname; size_t delnamelen; - struct dns_msg* msg; + struct dns_msg* msg = NULL; log_query_info(VERB_DETAIL, "resolving", &qstate->qinfo); /* check effort */ @@ -1009,13 +1010,13 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq, * getting older results from cache is a bad idea, no cache */ verbose(VERB_ALGO, "cache blacklisted, going to the network"); msg = NULL; - } else { + } else if(!qstate->no_cache_lookup) { msg = dns_cache_lookup(qstate->env, iq->qchase.qname, iq->qchase.qname_len, iq->qchase.qtype, iq->qchase.qclass, qstate->query_flags, qstate->region, qstate->env->scratch); if(!msg && qstate->env->neg_cache) { - /* lookup in negative cache; may result in + /* lookup in negative cache; may result in * NOERROR/NODATA or NXDOMAIN answers that need validation */ msg = val_neg_getmsg(qstate->env->neg_cache, &iq->qchase, qstate->region, qstate->env->rrset_cache, @@ -1701,10 +1702,11 @@ processLastResort(struct module_qstate* qstate, struct iter_qstate* iq, /* if this was a parent-side glue query itself, then store that * failure in cache. */ - if(iq->query_for_pside_glue && !iq->pside_glue) - iter_store_parentside_neg(qstate->env, &qstate->qinfo, - iq->deleg_msg?iq->deleg_msg->rep: - (iq->response?iq->response->rep:NULL)); + if(!qstate->no_cache_store && iq->query_for_pside_glue + && !iq->pside_glue) + iter_store_parentside_neg(qstate->env, &qstate->qinfo, + iq->deleg_msg?iq->deleg_msg->rep: + (iq->response?iq->response->rep:NULL)); verbose(VERB_QUERY, "out of query targets -- returning SERVFAIL"); /* fail -- no more targets, no more hope of targets, no hope @@ -1788,8 +1790,6 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, int tf_policy; struct delegpt_addr* target; struct outbound_entry* outq; - /* EDNS options to set on outgoing packet */ - struct edns_option* opt_list = NULL; /* NOTE: a request will encounter this state for each target it * needs to send a query to. That is, at least one per referral, @@ -2070,7 +2070,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, || iq->qchase.qtype == LDNS_RR_TYPE_A))) /* Stop minimising this query, resolve "as usual" */ iq->minimisation_state = DONOT_MINIMISE_STATE; - else { + else if(!qstate->no_cache_lookup) { struct dns_msg* msg = dns_cache_lookup(qstate->env, iq->qinfo_out.qname, iq->qinfo_out.qname_len, iq->qinfo_out.qtype, iq->qinfo_out.qclass, @@ -2108,9 +2108,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, iq->dnssec_lame_query?" but lame_query anyway": ""); } fptr_ok(fptr_whitelist_modenv_send_query(qstate->env->send_query)); - outq = (*qstate->env->send_query)( - iq->qinfo_out.qname, iq->qinfo_out.qname_len, - iq->qinfo_out.qtype, iq->qinfo_out.qclass, + outq = (*qstate->env->send_query)(&iq->qinfo_out, iq->chase_flags | (iq->chase_to_rd?BIT_RD:0), /* unset CD if to forwarder(RD set) and not dnssec retry * (blacklist nonempty) and no trust-anchors are configured @@ -2119,7 +2117,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, !qstate->blacklist&&(!iter_indicates_dnssec_fwd(qstate->env, &iq->qinfo_out)||target->attempts==1)?0:BIT_CD), iq->dnssec_expected, iq->caps_fallback || is_caps_whitelisted( - ie, iq), opt_list, &target->addr, target->addrlen, + ie, iq), &target->addr, target->addrlen, iq->dp->name, iq->dp->namelen, (iq->dp->ssl_upstream || qstate->env->cfg->ssl_upstream), qstate); if(!outq) { @@ -2262,10 +2260,11 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq, iq->num_target_queries = 0; return processDSNSFind(qstate, iq, id); } - iter_dns_store(qstate->env, &iq->response->qinfo, - iq->response->rep, 0, qstate->prefetch_leeway, - iq->dp&&iq->dp->has_parent_side_NS, - qstate->region, qstate->query_flags); + if(!qstate->no_cache_store) + iter_dns_store(qstate->env, &iq->response->qinfo, + iq->response->rep, 0, qstate->prefetch_leeway, + iq->dp&&iq->dp->has_parent_side_NS, + qstate->region, qstate->query_flags); /* close down outstanding requests to be discarded */ outbound_list_clear(&iq->outlist); iq->num_current_queries = 0; @@ -2333,7 +2332,8 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq, } /* if hardened, only store referral if we asked for it */ - if(!qstate->env->cfg->harden_referral_path || + if(!qstate->no_cache_store && + (!qstate->env->cfg->harden_referral_path || ( qstate->qinfo.qtype == LDNS_RR_TYPE_NS && (qstate->query_flags&BIT_RD) && !(qstate->query_flags&BIT_CD) @@ -2348,7 +2348,7 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq, iq->qchase.qname, iq->qchase.qname_len, LDNS_RR_TYPE_NS, iq->qchase.qclass) ) - )) { + ))) { /* Store the referral under the current query */ /* no prefetch-leeway, since its not the answer */ iter_dns_store(qstate->env, &iq->response->qinfo, @@ -2361,16 +2361,17 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq, iq->response->rep, iq->dp->name); } /* store parent-side-in-zone-glue, if directly queried for */ - if(iq->query_for_pside_glue && !iq->pside_glue) { - iq->pside_glue = reply_find_rrset(iq->response->rep, - iq->qchase.qname, iq->qchase.qname_len, - iq->qchase.qtype, iq->qchase.qclass); - if(iq->pside_glue) { - log_rrset_key(VERB_ALGO, "found parent-side " - "glue", iq->pside_glue); - iter_store_parentside_rrset(qstate->env, - iq->pside_glue); - } + if(!qstate->no_cache_store && iq->query_for_pside_glue + && !iq->pside_glue) { + iq->pside_glue = reply_find_rrset(iq->response->rep, + iq->qchase.qname, iq->qchase.qname_len, + iq->qchase.qtype, iq->qchase.qclass); + if(iq->pside_glue) { + log_rrset_key(VERB_ALGO, "found parent-side " + "glue", iq->pside_glue); + iter_store_parentside_rrset(qstate->env, + iq->pside_glue); + } } /* Reset the event state, setting the current delegation @@ -2451,10 +2452,11 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq, /* NOTE : set referral=1, so that rrsets get stored but not * the partial query answer (CNAME only). */ /* prefetchleeway applied because this updates answer parts */ - iter_dns_store(qstate->env, &iq->response->qinfo, - iq->response->rep, 1, qstate->prefetch_leeway, - iq->dp&&iq->dp->has_parent_side_NS, NULL, - qstate->query_flags); + if(!qstate->no_cache_store) + iter_dns_store(qstate->env, &iq->response->qinfo, + iq->response->rep, 1, qstate->prefetch_leeway, + iq->dp&&iq->dp->has_parent_side_NS, NULL, + qstate->query_flags); /* set the current request's qname to the new value. */ iq->qchase.qname = sname; iq->qchase.qname_len = snamelen; @@ -2934,10 +2936,11 @@ processFinished(struct module_qstate* qstate, struct iter_qstate* iq, &qstate->qinfo); /* store negative cache element for parent side glue. */ - if(iq->query_for_pside_glue && !iq->pside_glue) - iter_store_parentside_neg(qstate->env, &qstate->qinfo, - iq->deleg_msg?iq->deleg_msg->rep: - (iq->response?iq->response->rep:NULL)); + if(!qstate->no_cache_store && iq->query_for_pside_glue + && !iq->pside_glue) + iter_store_parentside_neg(qstate->env, &qstate->qinfo, + iq->deleg_msg?iq->deleg_msg->rep: + (iq->response?iq->response->rep:NULL)); if(!iq->response) { verbose(VERB_ALGO, "No response is set, servfail"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); @@ -2973,7 +2976,7 @@ processFinished(struct module_qstate* qstate, struct iter_qstate* iq, /* store message with the finished prepended items, * but only if we did recursion. The nonrecursion referral * from cache does not need to be stored in the msg cache. */ - if(qstate->query_flags&BIT_RD) { + if(!qstate->no_cache_store && qstate->query_flags&BIT_RD) { iter_dns_store(qstate->env, &qstate->qinfo, iq->response->rep, 0, qstate->prefetch_leeway, iq->dp&&iq->dp->has_parent_side_NS, @@ -3148,6 +3151,18 @@ process_response(struct module_qstate* qstate, struct iter_qstate* iq, if(parse_extract_edns(prs, &edns, qstate->env->scratch) != LDNS_RCODE_NOERROR) goto handle_it; + + /* Copy the edns options we may got from the back end */ + if(edns.opt_list) { + qstate->edns_opts_back_in = edns_opt_copy_region(edns.opt_list, + qstate->region); + if(!qstate->edns_opts_back_in) { + log_err("out of memory on incoming message"); + /* like packet got dropped */ + goto handle_it; + } + } + /* remove CD-bit, we asked for in case we handle validation ourself */ prs->flags &= ~BIT_CD; diff --git a/libunbound/context.c b/libunbound/context.c index 4469b5bb4..94a2472ae 100644 --- a/libunbound/context.c +++ b/libunbound/context.c @@ -62,6 +62,7 @@ context_finalize(struct ub_ctx* ctx) config_apply(cfg); if(!modstack_setup(&ctx->mods, cfg->module_conf, ctx->env)) return UB_INITFAIL; + log_edns_known_options(VERB_ALGO, ctx->env); ctx->local_zones = local_zones_create(); if(!ctx->local_zones) return UB_NOMEM; diff --git a/libunbound/libunbound.c b/libunbound/libunbound.c index 69ccb08f4..aaaaec08c 100644 --- a/libunbound/libunbound.c +++ b/libunbound/libunbound.c @@ -132,6 +132,15 @@ static struct ub_ctx* ub_ctx_create_nopipe(void) errno = ENOMEM; return NULL; } + /* init edns_known_options */ + if(!edns_known_options_init(ctx->env)) { + config_delete(ctx->env->cfg); + free(ctx->env); + ub_randfree(ctx->seed_rnd); + free(ctx); + errno = ENOMEM; + return NULL; + } ctx->env->alloc = &ctx->superalloc; ctx->env->worker = NULL; ctx->env->need_to_validate = 0; @@ -151,6 +160,7 @@ ub_ctx_create(void) ub_randfree(ctx->seed_rnd); config_delete(ctx->env->cfg); modstack_desetup(&ctx->mods, ctx->env); + edns_known_options_delete(ctx->env); free(ctx->env); free(ctx); errno = e; @@ -162,6 +172,7 @@ ub_ctx_create(void) ub_randfree(ctx->seed_rnd); config_delete(ctx->env->cfg); modstack_desetup(&ctx->mods, ctx->env); + edns_known_options_delete(ctx->env); free(ctx->env); free(ctx); errno = e; @@ -298,6 +309,8 @@ ub_ctx_delete(struct ub_ctx* ctx) rrset_cache_delete(ctx->env->rrset_cache); infra_delete(ctx->env->infra_cache); config_delete(ctx->env->cfg); + edns_known_options_delete(ctx->env); + inplace_cb_lists_delete(ctx->env); free(ctx->env); } ub_randfree(ctx->seed_rnd); diff --git a/libunbound/libworker.c b/libunbound/libworker.c index b00b96647..c90101956 100644 --- a/libunbound/libworker.c +++ b/libunbound/libworker.c @@ -609,7 +609,7 @@ int libworker_fg(struct ub_ctx* ctx, struct ctx_query* q) /* see if there is a fixed answer */ sldns_buffer_write_u16_at(w->back->udp_buff, 0, qid); sldns_buffer_write_u16_at(w->back->udp_buff, 2, qflags); - if(local_zones_answer(ctx->local_zones, &qinfo, &edns, + if(local_zones_answer(ctx->local_zones, w->env, &qinfo, &edns, w->back->udp_buff, w->env->scratch, NULL, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL)) { regional_free_all(w->env->scratch); @@ -680,7 +680,7 @@ int libworker_attach_mesh(struct ub_ctx* ctx, struct ctx_query* q, /* see if there is a fixed answer */ sldns_buffer_write_u16_at(w->back->udp_buff, 0, qid); sldns_buffer_write_u16_at(w->back->udp_buff, 2, qflags); - if(local_zones_answer(ctx->local_zones, &qinfo, &edns, + if(local_zones_answer(ctx->local_zones, w->env, &qinfo, &edns, w->back->udp_buff, w->env->scratch, NULL, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL)) { regional_free_all(w->env->scratch); @@ -801,7 +801,7 @@ handle_newq(struct libworker* w, uint8_t* buf, uint32_t len) /* see if there is a fixed answer */ sldns_buffer_write_u16_at(w->back->udp_buff, 0, qid); sldns_buffer_write_u16_at(w->back->udp_buff, 2, qflags); - if(local_zones_answer(w->ctx->local_zones, &qinfo, &edns, + if(local_zones_answer(w->ctx->local_zones, w->env, &qinfo, &edns, w->back->udp_buff, w->env->scratch, NULL, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL)) { regional_free_all(w->env->scratch); @@ -826,9 +826,8 @@ void libworker_alloc_cleanup(void* arg) slabhash_clear(w->env->msg_cache); } -struct outbound_entry* libworker_send_query(uint8_t* qname, size_t qnamelen, - uint16_t qtype, uint16_t qclass, uint16_t flags, int dnssec, - int want_dnssec, int nocaps, struct edns_option* opt_list, +struct outbound_entry* libworker_send_query(struct query_info* qinfo, + uint16_t flags, int dnssec, int want_dnssec, int nocaps, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, size_t zonelen, int ssl_upstream, struct module_qstate* q) { @@ -838,11 +837,10 @@ struct outbound_entry* libworker_send_query(uint8_t* qname, size_t qnamelen, if(!e) return NULL; e->qstate = q; - e->qsent = outnet_serviced_query(w->back, qname, - qnamelen, qtype, qclass, flags, dnssec, want_dnssec, nocaps, - q->env->cfg->tcp_upstream, ssl_upstream, opt_list, - addr, addrlen, zone, zonelen, libworker_handle_service_reply, - e, w->back->udp_buff); + e->qsent = outnet_serviced_query(w->back, qinfo, flags, dnssec, + want_dnssec, nocaps, q->env->cfg->tcp_upstream, ssl_upstream, + addr, addrlen, zone, zonelen, q, libworker_handle_service_reply, + e, w->back->udp_buff, q->env); if(!e->qsent) { return NULL; } @@ -957,15 +955,12 @@ void worker_sighandler(int ATTR_UNUSED(sig), void* ATTR_UNUSED(arg)) log_assert(0); } -struct outbound_entry* worker_send_query(uint8_t* ATTR_UNUSED(qname), - size_t ATTR_UNUSED(qnamelen), uint16_t ATTR_UNUSED(qtype), - uint16_t ATTR_UNUSED(qclass), uint16_t ATTR_UNUSED(flags), - int ATTR_UNUSED(dnssec), int ATTR_UNUSED(want_dnssec), - int ATTR_UNUSED(nocaps), struct edns_option* ATTR_UNUSED(opt_list), - struct sockaddr_storage* ATTR_UNUSED(addr), - socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone), - size_t ATTR_UNUSED(zonelen), int ATTR_UNUSED(ssl_upstream), - struct module_qstate* ATTR_UNUSED(q)) +struct outbound_entry* worker_send_query(struct query_info* ATTR_UNUSED(qinfo), + uint16_t ATTR_UNUSED(flags), int ATTR_UNUSED(dnssec), + int ATTR_UNUSED(want_dnssec), int ATTR_UNUSED(nocaps), + struct sockaddr_storage* ATTR_UNUSED(addr), socklen_t ATTR_UNUSED(addrlen), + uint8_t* ATTR_UNUSED(zone), size_t ATTR_UNUSED(zonelen), + int ATTR_UNUSED(ssl_upstream), struct module_qstate* ATTR_UNUSED(q)) { log_assert(0); return 0; diff --git a/libunbound/libworker.h b/libunbound/libworker.h index e8c293280..b546e89f2 100644 --- a/libunbound/libworker.h +++ b/libunbound/libworker.h @@ -59,6 +59,7 @@ struct regional; struct tube; struct sldns_buffer; struct ub_event_base; +struct query_info; /** * The library-worker status structure diff --git a/libunbound/python/doc/conf.py b/libunbound/python/doc/conf.py index 97fca2125..1766036b9 100644 --- a/libunbound/python/doc/conf.py +++ b/libunbound/python/doc/conf.py @@ -82,10 +82,13 @@ pygments_style = 'sphinx' # Options for HTML output # ----------------------- +# The theme that the html output should use. +html_theme = "classic" + # The style sheet to use for HTML and HTML Help pages. A file of that name # must exist either in Sphinx' static/ path, or in one of the custom paths # given in html_static_path. -html_style = 'default.css' +#html_style = 'default.css' # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". diff --git a/libunbound/python/doc/examples/example1a.rst b/libunbound/python/doc/examples/example1a.rst index 3c81547f2..f46cb92f4 100644 --- a/libunbound/python/doc/examples/example1a.rst +++ b/libunbound/python/doc/examples/example1a.rst @@ -1,26 +1,33 @@ .. _example_resolve_name: -============================== Resolve a name -============================== +============== -This basic example shows how to create a context and resolve a host address (DNS record of A type). +This basic example shows how to create a context and resolve a host address +(DNS record of A type). + +Source code +----------- :: - #!/usr/bin/python - import unbound - - ctx = unbound.ub_ctx() - ctx.resolvconf("/etc/resolv.conf") - - status, result = ctx.resolve("www.google.com") - if status == 0 and result.havedata: - print "Result.data:", result.data.address_list - elif status != 0: - print "Resolve error:", unbound.ub_strerror(status) - -In contrast with C API, the source code is more compact while the performance of C implementation is preserved. -The main advantage is that you need not take care about the deallocation and allocation of context and result structures; pyUnbound module do it automatically for you. - -If only domain name is given, the :meth:`unbound.ub_ctx.resolve` looks for A records in IN class. + #!/usr/bin/python + import unbound + + ctx = unbound.ub_ctx() + ctx.resolvconf("/etc/resolv.conf") + + status, result = ctx.resolve("www.google.com") + if status == 0 and result.havedata: + print "Result.data:", result.data.address_list + elif status != 0: + print "Resolve error:", unbound.ub_strerror(status) + +In contrast with the C API, the source code is more compact while the +performance of C implementation is preserved. +The main advantage is that you need not take care about the deallocation and +allocation of context and result structures; pyUnbound module does it +automatically for you. + +If only domain name is given, the :meth:`unbound.ub_ctx.resolve` looks for +A records in IN class. diff --git a/libunbound/python/doc/examples/example1b.rst b/libunbound/python/doc/examples/example1b.rst index ea1e6f57d..1adae2cb1 100644 --- a/libunbound/python/doc/examples/example1b.rst +++ b/libunbound/python/doc/examples/example1b.rst @@ -1,33 +1,37 @@ .. _example_reverse_lookup: -============================== Reverse DNS lookup -============================== +================== -Reverse DNS lookup involves determining the hostname associated with a given IP address. +Reverse DNS lookup involves determining the hostname associated with a given IP +address. This example shows how reverse lookup can be done using unbound module. For the reverse DNS records, the special domain in-addr.arpa is reserved. -For example, a host name for the IP address 74.125.43.147 can be obtained by issuing a DNS query for the PTR record for address 147.43.125.74.in-addr.arpa. +For example, a host name for the IP address ``74.125.43.147`` can be obtained +by issuing a DNS query for the PTR record for address +``147.43.125.74.in-addr.arpa.`` + +Source code +----------- :: - #!/usr/bin/python - import unbound - - ctx = unbound.ub_ctx() - ctx.resolvconf("/etc/resolv.conf") - - status, result = ctx.resolve(unbound.reverse("74.125.43.147") + ".in-addr.arpa.", unbound.RR_TYPE_PTR, unbound.RR_CLASS_IN) - if status == 0 and result.havedata: - print "Result.data:", result.data.domain_list - elif status != 0: - print "Resolve error:", unbound.ub_strerror(status) - -In order to simplify the python code, unbound module contains function which reverses the hostname components. -This function is defined as follows:: + #!/usr/bin/python + import unbound - def reverse(domain): - return '.'.join([a for a in domain.split(".")][::-1]) + ctx = unbound.ub_ctx() + ctx.resolvconf("/etc/resolv.conf") + status, result = ctx.resolve(unbound.reverse("74.125.43.147") + ".in-addr.arpa.", unbound.RR_TYPE_PTR, unbound.RR_CLASS_IN) + if status == 0 and result.havedata: + print "Result.data:", result.data.domain_list + elif status != 0: + print "Resolve error:", unbound.ub_strerror(status) + +In order to simplify the python code, unbound module contains the +:meth:`unbound.reverse` function which reverses the hostname components. +This function is defined as follows:: + def reverse(domain): + return '.'.join([a for a in domain.split(".")][::-1]) diff --git a/libunbound/python/doc/examples/example2.rst b/libunbound/python/doc/examples/example2.rst index c009ec1f5..a2bf2cbf5 100644 --- a/libunbound/python/doc/examples/example2.rst +++ b/libunbound/python/doc/examples/example2.rst @@ -1,41 +1,41 @@ .. _example_setup_ctx: -============================== Lookup from threads -============================== +=================== -This example shows how to use unbound module from a threaded program. -In this example, three lookup threads are created which work in background. -Each thread resolves different DNS record. +This example shows how to use unbound module from a threaded program. +In this example, three lookup threads are created which work in background. +Each thread resolves different DNS record. + +Source code +----------- :: - #!/usr/bin/python - from unbound import ub_ctx, RR_TYPE_A, RR_CLASS_IN - from threading import Thread - - ctx = ub_ctx() - ctx.resolvconf("/etc/resolv.conf") - - class LookupThread(Thread): - def __init__(self,ctx, name): - Thread.__init__(self) - self.ctx = ctx - self.name = name - - def run(self): - print "Thread lookup started:",self.name - status, result = self.ctx.resolve(self.name, RR_TYPE_A, RR_CLASS_IN) - if status == 0 and result.havedata: - print " Result:",self.name,":", result.data.address_list - - threads = [] - for name in ["www.fit.vutbr.cz","www.vutbr.cz","www.google.com"]: - thread = LookupThread(ctx, name) - thread.start() - threads.append(thread) - - for thread in threads: - thread.join() + #!/usr/bin/python + from unbound import ub_ctx, RR_TYPE_A, RR_CLASS_IN + from threading import Thread + + ctx = ub_ctx() + ctx.resolvconf("/etc/resolv.conf") + + class LookupThread(Thread): + def __init__(self,ctx, name): + Thread.__init__(self) + self.ctx = ctx + self.name = name + + def run(self): + print "Thread lookup started:",self.name + status, result = self.ctx.resolve(self.name, RR_TYPE_A, RR_CLASS_IN) + if status == 0 and result.havedata: + print " Result:",self.name,":", result.data.address_list + threads = [] + for name in ["www.fit.vutbr.cz","www.vutbr.cz","www.google.com"]: + thread = LookupThread(ctx, name) + thread.start() + threads.append(thread) + for thread in threads: + thread.join() diff --git a/libunbound/python/doc/examples/example3.rst b/libunbound/python/doc/examples/example3.rst index 91360335c..b0626b55f 100644 --- a/libunbound/python/doc/examples/example3.rst +++ b/libunbound/python/doc/examples/example3.rst @@ -1,12 +1,14 @@ .. _example_asynch: -============================== Asynchronous lookup -============================== +=================== This example performs the name lookup in the background. The main program keeps running while the name is resolved. +Source code +----------- + :: #!/usr/bin/python @@ -33,4 +35,5 @@ The main program keeps running while the name is resolved. if (status != 0): print "Resolve error:", unbound.ub_strerror(status) -The :meth:`unbound.ub_ctx.resolve_async` method is able to pass on any Python object. In this example, we used a dictionary object `my_data`. +The :meth:`unbound.ub_ctx.resolve_async` method is able to pass on any Python +object. In this example, we used a dictionary object ``my_data``. diff --git a/libunbound/python/doc/examples/example4.rst b/libunbound/python/doc/examples/example4.rst index 996ef4ede..3b43eb85f 100644 --- a/libunbound/python/doc/examples/example4.rst +++ b/libunbound/python/doc/examples/example4.rst @@ -1,33 +1,35 @@ .. _example_examine: -============================== DNSSEC validator -============================== +================ This example program performs DNSSEC validation of a DNS lookup. +Source code +----------- + :: - #!/usr/bin/python - import os - from unbound import ub_ctx,RR_TYPE_A,RR_CLASS_IN - - ctx = ub_ctx() - ctx.resolvconf("/etc/resolv.conf") - if (os.path.isfile("keys")): - ctx.add_ta_file("keys") #read public keys for DNSSEC verification - - status, result = ctx.resolve("www.nic.cz", RR_TYPE_A, RR_CLASS_IN) - if status == 0 and result.havedata: - - print "Result:", result.data.address_list - - if result.secure: - print "Result is secure" - elif result.bogus: - print "Result is bogus" - else: - print "Result is insecure" + #!/usr/bin/python + import os + from unbound import ub_ctx,RR_TYPE_A,RR_CLASS_IN + + ctx = ub_ctx() + ctx.resolvconf("/etc/resolv.conf") + if (os.path.isfile("keys")): + ctx.add_ta_file("keys") #read public keys for DNSSEC verification + + status, result = ctx.resolve("www.nic.cz", RR_TYPE_A, RR_CLASS_IN) + if status == 0 and result.havedata: + + print "Result:", result.data.address_list + + if result.secure: + print "Result is secure" + elif result.bogus: + print "Result is bogus" + else: + print "Result is insecure" More detailed informations can be seen in libUnbound DNSSEC tutorial `here`_. diff --git a/libunbound/python/doc/examples/example5.rst b/libunbound/python/doc/examples/example5.rst index 0a31d9a57..9262014bb 100644 --- a/libunbound/python/doc/examples/example5.rst +++ b/libunbound/python/doc/examples/example5.rst @@ -1,13 +1,17 @@ .. _example_resolver_only: -============================== Resolver only -============================== +============= This example program shows how to perform DNS resolution only. Unbound contains two basic modules: resolver and validator. -In case, the validator is not necessary, the validator module can be turned off using "module-config" option. -This option contains a list of module names separated by the space char. This list determined which modules should be employed and in what order. +In case, the validator is not necessary, the validator module can be turned off +using "module-config" option. +This option contains a list of module names separated by the space char. This +list determined which modules should be employed and in what order. + +Source code +----------- :: @@ -25,5 +29,6 @@ This option contains a list of module names separated by the space char. This li print "Result:", result.data.address_list .. note:: - The :meth:`unbound.ub_ctx.set_option` method must be used before the first resolution (i.e. before :meth:`unbound.ub_ctx.resolve` or :meth:`unbound.ub_ctx.resolve_async` call). - + The :meth:`unbound.ub_ctx.set_option` method must be used before the first + resolution (i.e. before :meth:`unbound.ub_ctx.resolve` or + :meth:`unbound.ub_ctx.resolve_async` call). diff --git a/libunbound/python/doc/examples/example6.rst b/libunbound/python/doc/examples/example6.rst index 478e13909..6fde8b25f 100644 --- a/libunbound/python/doc/examples/example6.rst +++ b/libunbound/python/doc/examples/example6.rst @@ -1,11 +1,13 @@ .. _example_localzone: -============================== Local zone manipulation -============================== +======================= -This example program shows how to define local zone containing custom DNS records. +This example program shows how to define local zone containing custom DNS +records. -.. literalinclude:: example6-1.py - :language: python +Source code +----------- +.. literalinclude:: example6-1.py + :language: python diff --git a/libunbound/python/doc/examples/example7.rst b/libunbound/python/doc/examples/example7.rst index d4050215e..2f48c8f0f 100644 --- a/libunbound/python/doc/examples/example7.rst +++ b/libunbound/python/doc/examples/example7.rst @@ -1,18 +1,33 @@ .. _example_idna: -================================================= Internationalized domain name support -================================================= +===================================== Unlike the libUnbound, pyUnbound is able to handle IDN queries. -.. literalinclude:: example7-1.py - :language: python +Automatic IDN DNAME conversion +------------------------------- -If we use unicode string in :meth:`unbound.ub_ctx.resolve` method, the IDN DNAME conversion (if it is necessary) is performed on background. +If we use unicode string in :meth:`unbound.ub_ctx.resolve` method, +the IDN DNAME conversion (if it is necessary) is performed on background. -.. literalinclude:: example7-2.py - :language: python +Source code +........... -The :class:`unbound.ub_data` class contains attributes suffix which converts the dname to UTF string. These attributes have the '_idn' suffix. -Apart from this aproach, two conversion functions exist (:func:`unbound.idn2dname` and :func:`unbound.dname2idn`). +.. literalinclude:: example7-1.py + :language: python + +IDN converted attributes +------------------------ + +The :class:`unbound.ub_data` class contains attributes suffix which converts +the dname to UTF string. These attributes have the ``_idn`` suffix. + +Apart from this aproach, two conversion functions exist +(:func:`unbound.idn2dname` and :func:`unbound.dname2idn`). + +Source code +........... + +.. literalinclude:: example7-2.py + :language: python diff --git a/libunbound/python/doc/examples/example8.rst b/libunbound/python/doc/examples/example8.rst index 8cdfcdc0a..16c140475 100644 --- a/libunbound/python/doc/examples/example8.rst +++ b/libunbound/python/doc/examples/example8.rst @@ -1,28 +1,34 @@ .. _example_mxlookup: -================================================= Lookup for MX and NS records -================================================= +============================ -The pyUnbound extension provides functions which are able to encode RAW RDATA produces by unbound resolver (see :class:`unbound.ub_data`). +The pyUnbound extension provides functions which are able to encode RAW RDATA +produces by unbound resolver (see :class:`unbound.ub_data`). -.. literalinclude:: example8-1.py - :language: python +Source code +----------- -Previous example produces following output:: +.. literalinclude:: example8-1.py + :language: python - Result: - raw data: 00 0F 05 6D 61 69 6C 34 03 6E 69 63 02 63 7A 00;00 14 02 6D 78 05 63 7A 6E 69 63 03 6F 72 67 00;00 0A 04 6D 61 69 6C 03 6E 69 63 02 63 7A 00 - priority:15 address: mail4.nic.cz. - priority:20 address: mx.cznic.org. - priority:10 address: mail.nic.cz. +Output +------ - Result: - raw data: D9 1F CD 32 - address: 217.31.205.50 +The previous example produces the following output:: - Result: - raw data: 01 61 02 6E 73 03 6E 69 63 02 63 7A 00;01 65 02 6E 73 03 6E 69 63 02 63 7A 00;01 63 02 6E 73 03 6E 69 63 02 63 7A 00 - host: a.ns.nic.cz. - host: e.ns.nic.cz. - host: c.ns.nic.cz. + Result: + raw data: 00 0F 05 6D 61 69 6C 34 03 6E 69 63 02 63 7A 00;00 14 02 6D 78 05 63 7A 6E 69 63 03 6F 72 67 00;00 0A 04 6D 61 69 6C 03 6E 69 63 02 63 7A 00 + priority:15 address: mail4.nic.cz. + priority:20 address: mx.cznic.org. + priority:10 address: mail.nic.cz. + + Result: + raw data: D9 1F CD 32 + address: 217.31.205.50 + + Result: + raw data: 01 61 02 6E 73 03 6E 69 63 02 63 7A 00;01 65 02 6E 73 03 6E 69 63 02 63 7A 00;01 63 02 6E 73 03 6E 69 63 02 63 7A 00 + host: a.ns.nic.cz. + host: e.ns.nic.cz. + host: c.ns.nic.cz. diff --git a/libunbound/python/doc/examples/index.rst b/libunbound/python/doc/examples/index.rst index c2c9cf457..283261652 100644 --- a/libunbound/python/doc/examples/index.rst +++ b/libunbound/python/doc/examples/index.rst @@ -1,14 +1,16 @@ Examples -============================== +======== -Here you can find several examples which utilizes the unbound library in Python environment. -Unbound is a caching validator and resolver and can be linked into an application, as a library where can answer DNS queries for the application. +Here you can find several examples which utilizes the unbound library in Python +environment. Unbound is a caching validator and resolver and can be linked into +an application, as a library where can answer DNS queries for the application. This set of examples shows how to use the functions from Python environment. -`Tutorials` +Tutorials +--------- .. toctree:: - :maxdepth: 1 - :glob: + :maxdepth: 1 + :glob: - example* + example* diff --git a/libunbound/python/doc/install.rst b/libunbound/python/doc/install.rst index a073a5c66..bb3118984 100644 --- a/libunbound/python/doc/install.rst +++ b/libunbound/python/doc/install.rst @@ -1,31 +1,38 @@ Installation -=================================== +============ -**Prerequisites** +Prerequisites +------------- Python 2.4 or higher, SWIG 1.3 or higher, GNU make -**Compiling** +Compiling +--------- After downloading, you can compile the pyUnbound library by doing:: - > tar -xzf unbound-x.x.x-py.tar.gz - > cd unbound-x.x.x - > ./configure --with-pyunbound - > make + > tar -xzf unbound-x.x.x-py.tar.gz + > cd unbound-x.x.x + > ./configure --with-pyunbound + > make -You may want to --with-pythonmodule as well if you want to use python as -a module in the resolver. +You may want to enable ``--with-pythonmodule`` as well if you want to use +python as a module in the resolver. -You need GNU make to compile sources; SWIG and Python devel libraries to compile extension module. +You need ``GNU make`` to compile sources; ``SWIG`` and ``Python devel`` +libraries to compile extension module. -**Testing** +Testing +------- -If the compilation is successful, you can test the python LDNS extension module by:: +If the compilation is successful, you can test the python LDNS extension module +by:: - > cd contrib/python - > make testenv - > ./dns-lookup.py + > cd contrib/python + > make testenv + > ./dns-lookup.py -You may want to make install in the main directory since make testenv is for debugging. In contrib/examples you can find simple applications written in Python using the Unbound extension. +You may want to ``make install`` in the main directory since ``make testenv`` +is for debugging. In contrib/examples you can find simple applications written +in Python using the Unbound extension. diff --git a/libunbound/python/doc/intro.rst b/libunbound/python/doc/intro.rst index f751f54c0..e490d2c6f 100644 --- a/libunbound/python/doc/intro.rst +++ b/libunbound/python/doc/intro.rst @@ -1,39 +1,58 @@ Introduction -=================================== - -**Unbound** - - `Unbound`_ is an implementation of a DNS resolver, that performs caching and DNSSEC validation. - Together with unbound, the libunbound library is provided. - This library can be used to convert hostnames to ip addresses, and back, as well as obtain other information. - Since the resolver allows to specify the class and type of a query (A record, NS, MX, ...), this library offers powerful resolving tool. - The library also performs public-key validation of results with DNSSEC. - - .. _Unbound: http://www.unbound.net/documentation - -**pyUnbound** - - The pyUnbound is an extension module for Python which provides an object-oriented interface to libunbound. - It is the first Python module which offers thread-safe caching resolver. - - The interface was designed with the emphasis on the simplicity of use. - There are two main classes :class:`unbound.ub_ctx` (a validation and resolution context) and :class:`unbound.ub_result` which contains the validation and resolution results. - The objects are thread-safe, and a context can be used in non-threaded as well as threaded environment. - Resolution can be performed blocking and non-blocking (i.e. asynchronous). - The asynchronous method returns from the call immediately, so that processing can go on, while the results become available later. - -**Features** - * customizable caching validation resolver for synchronous and asynchronous lookups - * easy to use object interface - * easy to integrate extension module - * designed for thread environment (i.e. thread-safe) - * allows define and customize of local zone and its RR's during the operation (i.e. without restart) - * includes encoding functions to simplify the results retrieval - * Internationalized domain name (`IDN`_) support - - .. _IDN: http://en.wikipedia.org/wiki/Internationalized_domain_name - -**Application area** - * DNS-based applications performing DNS lookups; the caching resolver can reduce overhead - * Applications where the validation of DNS records is required - * Great solution for customizable and dynamic DNS-based white/blacklists (spam rejection, connection rejection, ...) using the dynamic local zone manipulation +============ + +Unbound +------- + +`Unbound`_ is an implementation of a DNS resolver, that performs caching and +DNSSEC validation. +Together with unbound, the libunbound library is provided. +This library can be used to convert hostnames to ip addresses, and back, as +well as obtain other information. +Since the resolver allows to specify the class and type of a query (A record, +NS, MX, ...), this library offers powerful resolving tool. +The library also performs public-key validation of results with DNSSEC. + +.. _Unbound: http://www.unbound.net/documentation + +pyUnbound +--------- + +The pyUnbound is an extension module for Python which provides an +object-oriented interface to libunbound. +It is the first Python module which offers thread-safe caching resolver. + +The interface was designed with the emphasis on the simplicity of use. +There are two main classes :class:`unbound.ub_ctx` (a validation and resolution +context) and :class:`unbound.ub_result` which contains the validation and +resolution results. +The objects are thread-safe, and a context can be used in non-threaded as well +as threaded environment. +Resolution can be performed blocking and non-blocking (i.e. asynchronous). +The asynchronous method returns from the call immediately, so that processing +can go on, while the results become available later. + +Features +-------- + +* Customizable caching validation resolver for synchronous and asynchronous + lookups +* Easy to use object interface +* Easy to integrate extension module +* Designed for thread environment (i.e. thread-safe) +* Allows define and customize of local zone and its RR's during the operation + (i.e. without restart) +* Includes encoding functions to simplify the results retrieval +* Internationalized domain name (`IDN`_) support + +.. _IDN: http://en.wikipedia.org/wiki/Internationalized_domain_name + +Application area +---------------- + +* DNS-based applications performing DNS lookups; the caching resolver can + reduce overhead +* Applications where the validation of DNS records is required +* Great solution for customizable and dynamic DNS-based white/blacklists (spam + rejection, connection rejection, ...) using the dynamic local zone + manipulation diff --git a/libunbound/worker.h b/libunbound/worker.h index 5d1c9ab92..88e1cf799 100644 --- a/libunbound/worker.h +++ b/libunbound/worker.h @@ -49,18 +49,15 @@ struct comm_point; struct module_qstate; struct tube; struct edns_option; +struct query_info; /** * Worker service routine to send serviced queries to authoritative servers. - * @param qname: query name. (host order) - * @param qnamelen: length in bytes of qname, including trailing 0. - * @param qtype: query type. (host order) - * @param qclass: query class. (host order) + * @param qinfo: query info. * @param flags: host order flags word, with opcode and CD bit. * @param dnssec: if set, EDNS record will have DO bit set. * @param want_dnssec: signatures needed. * @param nocaps: ignore capsforid(if in config), do not perturb qname. - * @param opt_list: EDNS options on outgoing packet. * @param addr: where to. * @param addrlen: length of addr. * @param zone: delegation point name. @@ -70,9 +67,8 @@ struct edns_option; * @return: false on failure (memory or socket related). no query was * sent. */ -struct outbound_entry* libworker_send_query(uint8_t* qname, size_t qnamelen, - uint16_t qtype, uint16_t qclass, uint16_t flags, int dnssec, - int want_dnssec, int nocaps, struct edns_option* opt_list, +struct outbound_entry* libworker_send_query(struct query_info* qinfo, + uint16_t flags, int dnssec, int want_dnssec, int nocaps, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, size_t zonelen, int ssl_upstream, struct module_qstate* q); @@ -109,15 +105,11 @@ void worker_sighandler(int sig, void* arg); /** * Worker service routine to send serviced queries to authoritative servers. - * @param qname: query name. (host order) - * @param qnamelen: length in bytes of qname, including trailing 0. - * @param qtype: query type. (host order) - * @param qclass: query class. (host order) + * @param qinfo: query info. * @param flags: host order flags word, with opcode and CD bit. * @param dnssec: if set, EDNS record will have DO bit set. * @param want_dnssec: signatures needed. * @param nocaps: ignore capsforid(if in config), do not perturb qname. - * @param opt_list: EDNS options on outgoing packet. * @param addr: where to. * @param addrlen: length of addr. * @param zone: wireformat dname of the zone. @@ -127,9 +119,8 @@ void worker_sighandler(int sig, void* arg); * @return: false on failure (memory or socket related). no query was * sent. */ -struct outbound_entry* worker_send_query(uint8_t* qname, size_t qnamelen, - uint16_t qtype, uint16_t qclass, uint16_t flags, int dnssec, - int want_dnssec, int nocaps, struct edns_option* opt_list, +struct outbound_entry* worker_send_query(struct query_info* qinfo, + uint16_t flags, int dnssec, int want_dnssec, int nocaps, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, size_t zonelen, int ssl_upstream, struct module_qstate* q); diff --git a/pythonmod/doc/conf.py b/pythonmod/doc/conf.py index bc7a5aba6..7fcfe2d05 100644 --- a/pythonmod/doc/conf.py +++ b/pythonmod/doc/conf.py @@ -80,10 +80,13 @@ pygments_style = 'sphinx' # Options for HTML output # ----------------------- +# The theme that the html output should use. +html_theme = "classic" + # The style sheet to use for HTML and HTML Help pages. A file of that name # must exist either in Sphinx' static/ path, or in one of the custom paths # given in html_static_path. -html_style = 'default.css' +#html_style = 'default.css' # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". diff --git a/pythonmod/doc/examples/example2.rst b/pythonmod/doc/examples/example2.rst index f00fcc239..4ba9239a0 100644 --- a/pythonmod/doc/examples/example2.rst +++ b/pythonmod/doc/examples/example2.rst @@ -1,12 +1,14 @@ Response generation -===================== +=================== This example shows how to handle queries and generate response packet. .. note:: - If the python module is the first module and validator module is enabled (``module-config: "python validator iterator"``), - a return_msg security flag has to be set at least to 2. Leaving security flag untouched causes that the - response will be refused by unbound worker as unbound will consider it as non-valid response. + If the python module is the first module and validator module is enabled + (``module-config: "python validator iterator"``), a return_msg security flag + has to be set at least to 2. Leaving security flag untouched causes that the + response will be refused by unbound worker as unbound will consider it as + non-valid response. Complete source code -------------------- @@ -27,20 +29,21 @@ Query for a A record ending with .localdomain Dig produces the following output:: - ;; global options: printcmd - ;; Got answer: - ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48426 - ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 - - ;; QUESTION SECTION: - ;test.xxx.localdomain. IN A - - ;; ANSWER SECTION: - test.xxx.localdomain. 10 IN A 127.0.0.1 - - ;; Query time: 2 msec - ;; SERVER: 127.0.0.1#53(127.0.0.1) - ;; WHEN: Mon Jan 01 12:46:02 2009 - ;; MSG SIZE rcvd: 54 - -As we handle (override) in python module only queries ending with "localdomain.", the unboud can still resolve host names. + ;; global options: printcmd + ;; Got answer: + ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48426 + ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 + + ;; QUESTION SECTION: + ;test.xxx.localdomain. IN A + + ;; ANSWER SECTION: + test.xxx.localdomain. 10 IN A 127.0.0.1 + + ;; Query time: 2 msec + ;; SERVER: 127.0.0.1#53(127.0.0.1) + ;; WHEN: Mon Jan 01 12:46:02 2009 + ;; MSG SIZE rcvd: 54 + +As we handle (override) in the python module only queries ending with +``localdomain.``, unboud can still resolve host names. diff --git a/pythonmod/doc/examples/example4.rst b/pythonmod/doc/examples/example4.rst index b665351e8..338210990 100644 --- a/pythonmod/doc/examples/example4.rst +++ b/pythonmod/doc/examples/example4.rst @@ -1,15 +1,19 @@ DNS-based language dictionary -=============================== +============================= This example shows how to create a simple language dictionary based on **DNS** -service within 15 minutes. The translation will be performed using TXT resource records. +service within 15 minutes. The translation will be performed using TXT resource +records. Key parts ------------ +--------- Initialization -~~~~~~~~~~~~~~~~~~~~~~~ -On **init()** module loads dictionary from a text file containing records in ``word [tab] translation`` format. +~~~~~~~~~~~~~~ + +On **init()** module loads dictionary from a text file containing records in +``word [tab] translation`` format. + :: def init(id, cfg): @@ -20,11 +24,14 @@ On **init()** module loads dictionary from a text file containing records in ``w The suitable file can be found at http://slovnik.zcu.cz DNS query and word lookup -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~ -Let's define the following format od DNS queries: ``word1[.]word2[.] ... wordN[.]{en,cs}[._dict_.cz.]``. +Let's define the following format od DNS queries: +``word1[.]word2[.] ... wordN[.]{en,cs}[._dict_.cz.]``. Word lookup is done by simple ``dict`` lookup from broken DNS request. -Query name is divided into a list of labels. This list is accessible as qname_list attribute. +Query name is divided into a list of labels. This list is accessible as +``qname_list`` attribute. + :: aword = ' '.join(qstate.qinfo.qname_list[0:-4]) #skip last four labels @@ -37,35 +44,40 @@ Query name is divided into a list of labels. This list is accessible as qname_li if (adict == "cs") and (aword in cz_dict): words = cz_dict[aword] # CS -> EN -In the first step, we get a string in the form: ``word1[space]word2[space]...word[space]``. -In the second assignment, fourth label from the end is obtained. This label should contains *"cs"* or *"en"*. -This label determines the direction of translation. - +In the first step, we get a string in the form: +``word1[space]word2[space]...word[space]``. +In the second assignment, fourth label from the end is obtained. This label +should contains *"cs"* or *"en"*. This label determines the direction of +translation. Forming of a DNS reply -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~ DNS reply is formed only on valid match and added as TXT answer. + :: - msg = DNSMessage(qstate.qinfo.qname_str, RR_TYPE_TXT, RR_CLASS_IN, PKT_AA) + msg = DNSMessage(qstate.qinfo.qname_str, RR_TYPE_TXT, RR_CLASS_IN, PKT_AA) - for w in words: - msg.answer.append("%s 300 IN TXT \"%s\"" % (qstate.qinfo.qname_str, w.replace("\"", "\\\""))) + for w in words: + msg.answer.append("%s 300 IN TXT \"%s\"" % (qstate.qinfo.qname_str, w.replace("\"", "\\\""))) - if not msg.set_return_msg(qstate): - qstate.ext_state[id] = MODULE_ERROR - return True + if not msg.set_return_msg(qstate): + qstate.ext_state[id] = MODULE_ERROR + return True - qstate.return_rcode = RCODE_NOERROR - qstate.ext_state[id] = MODULE_FINISHED - return True + qstate.return_rcode = RCODE_NOERROR + qstate.ext_state[id] = MODULE_FINISHED + return True -In the first step, a :class:`DNSMessage` instance is created for a given query *(type TXT)*. +In the first step, a :class:`DNSMessage` instance is created for a given query +*(type TXT)*. The fourth argument specifies the flags *(authoritative answer)*. -In the second step, we append TXT records containing the translation *(on the right side of RR)*. +In the second step, we append TXT records containing the translation *(on the +right side of RR)*. Then, the response is finished and ``qstate.return_msg`` contains new response. -If no error, the module sets :attr:`module_qstate.return_rcode` and :attr:`module_qstate.ext_state`. +If no error, the module sets :attr:`module_qstate.return_rcode` and +:attr:`module_qstate.ext_state`. **Steps:** @@ -82,80 +94,82 @@ Run the Unbound server: In case you use own configuration file, don't forget to enable Python module:: - module-config: "validator python iterator" + module-config: "validator python iterator" and use valid script path:: - python-script: "./examples/dict.py" + python-script: "./examples/dict.py" The translation from english word *"a bar fly"* to Czech can be done by doing: ``>>>dig TXT @127.0.0.1 a.bar.fly.en._dict_.cz`` -:: +:: + + ; (1 server found) + ;; global options: printcmd + ;; Got answer: + ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48691 + ;; flags: aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 + + ;; QUESTION SECTION: + ;a.bar.fly.en._dict_.cz. IN TXT + + ;; ANSWER SECTION: + a.bar.fly.en._dict_.cz. 300 IN TXT "barov\253 povale\232" + + ;; Query time: 5 msec + ;; SERVER: 127.0.0.1#53(127.0.0.1) + ;; WHEN: Mon Jan 01 17:44:18 2009 + ;; MSG SIZE rcvd: 67 - ; (1 server found) - ;; global options: printcmd - ;; Got answer: - ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48691 - ;; flags: aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 - - ;; QUESTION SECTION: - ;a.bar.fly.en._dict_.cz. IN TXT - - ;; ANSWER SECTION: - a.bar.fly.en._dict_.cz. 300 IN TXT "barov\253 povale\232" - - ;; Query time: 5 msec - ;; SERVER: 127.0.0.1#53(127.0.0.1) - ;; WHEN: Mon Jan 01 17:44:18 2009 - ;; MSG SIZE rcvd: 67 - ``>>>dig TXT @127.0.0.1 nic.cs._dict_.cz`` + :: - ; <<>> DiG 9.5.0-P2 <<>> TXT @127.0.0.1 nic.cs._dict_.cz - ; (1 server found) - ;; global options: printcmd - ;; Got answer: - ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58710 - ;; flags: aa rd ra; QUERY: 1, ANSWER: 6, AUTHORITY: 0, ADDITIONAL: 0 - - ;; QUESTION SECTION: - ;nic.cs._dict_.cz. IN TXT - - ;; ANSWER SECTION: - nic.cs._dict_.cz. 300 IN TXT "aught" - nic.cs._dict_.cz. 300 IN TXT "naught" - nic.cs._dict_.cz. 300 IN TXT "nihil" - nic.cs._dict_.cz. 300 IN TXT "nix" - nic.cs._dict_.cz. 300 IN TXT "nothing" - nic.cs._dict_.cz. 300 IN TXT "zilch" - - ;; Query time: 0 msec - ;; SERVER: 127.0.0.1#53(127.0.0.1) - ;; WHEN: Mon Jan 01 17:45:39 2009 - ;; MSG SIZE rcvd: 143 - -Proof that the unbound still works as resolver. + ; <<>> DiG 9.5.0-P2 <<>> TXT @127.0.0.1 nic.cs._dict_.cz + ; (1 server found) + ;; global options: printcmd + ;; Got answer: + ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58710 + ;; flags: aa rd ra; QUERY: 1, ANSWER: 6, AUTHORITY: 0, ADDITIONAL: 0 + + ;; QUESTION SECTION: + ;nic.cs._dict_.cz. IN TXT + + ;; ANSWER SECTION: + nic.cs._dict_.cz. 300 IN TXT "aught" + nic.cs._dict_.cz. 300 IN TXT "naught" + nic.cs._dict_.cz. 300 IN TXT "nihil" + nic.cs._dict_.cz. 300 IN TXT "nix" + nic.cs._dict_.cz. 300 IN TXT "nothing" + nic.cs._dict_.cz. 300 IN TXT "zilch" + + ;; Query time: 0 msec + ;; SERVER: 127.0.0.1#53(127.0.0.1) + ;; WHEN: Mon Jan 01 17:45:39 2009 + ;; MSG SIZE rcvd: 143 + + Proof that the unbound still works as resolver. ``>>>dig A @127.0.0.1 www.nic.cz`` + :: - ; (1 server found) - ;; global options: printcmd - ;; Got answer: - ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19996 - ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 3, ADDITIONAL: 5 - - ;; QUESTION SECTION: - ;www.nic.cz. IN A - - ;; ANSWER SECTION: - www.nic.cz. 1662 IN A 217.31.205.50 - - ;; AUTHORITY SECTION: - ... + ; (1 server found) + ;; global options: printcmd + ;; Got answer: + ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19996 + ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 3, ADDITIONAL: 5 + + ;; QUESTION SECTION: + ;www.nic.cz. IN A + + ;; ANSWER SECTION: + www.nic.cz. 1662 IN A 217.31.205.50 + + ;; AUTHORITY SECTION: + ... Complete source code -------------------- diff --git a/pythonmod/doc/examples/example5.rst b/pythonmod/doc/examples/example5.rst new file mode 100644 index 000000000..058fc331e --- /dev/null +++ b/pythonmod/doc/examples/example5.rst @@ -0,0 +1,191 @@ +EDNS options +============ + +This example shows how to interact with EDNS options. + +When quering unbound with the EDNS option ``65001`` and data ``0xc001`` we +expect an answer with the same EDNS option code and data ``0xdeadbeef``. + + +Key parts +~~~~~~~~~ + +This example relies on the following functionalities: + + +Registering EDNS options +------------------------ + +By registering EDNS options we can tune unbound's behavior when encountering a +query with a known EDNS option. The two available options are: + +- ``bypass_cache_stage``: If set to ``True`` unbound will not try to answer + from cache. Instead execution is passed to the modules +- ``no_aggregation``: If set to ``True`` unbound will consider this query + unique and will not aggregate it with similar queries + +Both values default to ``False``. + +.. code-block:: python + + if not register_edns_option(env, 65001, bypass_cache_stage=True, + no_aggregation=True): + log_info("python: Could not register EDNS option {}".format(65001)) + + +EDNS option lists +----------------- + +EDNS option lists can be found in the :class:`module_qstate` class. There are +four available lists in total: + +- :class:`module_qstate.edns_opts_front_in`: options that came from the client + side. **Should not** be changed +- :class:`module_qstate.edns_opts_back_out`: options that will be sent to the + server side. Can be populated by edns literate modules +- :class:`module_qstate.edns_opts_back_in`: options that came from the server + side. **Should not** be changed +- :class:`module_qstate.edns_opts_front_out`: options that will be sent to the + client side. Can be populated by edns literate modules + +Each list element has the following members: + +- ``code``: the EDNS option code; +- ``data``: the EDNS option data. + + +Reading an EDNS option list +........................... + +The lists' contents can be accessed in python by their ``_iter`` counterpart as +an iterator: + +.. code-block:: python + + if not edns_opt_list_is_empty(qstate.edns_opts_front_in): + for o in qstate.edns_opts_front_in_iter: + log_info("python: Code: {}, Data: '{}'".format(o.code, + "".join('{:02x}'.format(x) for x in o.data))) + + +Writing to an EDNS option list +.............................. + +By appending to an EDNS option list we can add new EDNS options. The new +element is going to be allocated in :class:`module_qstate.region`. The data +**must** be represented with a python ``bytearray``: + +.. code-block:: python + + b = bytearray.fromhex("deadbeef") + if not edns_opt_list_append(qstate.edns_opts_front_out, + o.code, b, qstate.region): + log_info("python: Could not append EDNS option {}".format(o.code)) + +We can also remove an EDNS option code from an EDNS option list. + +.. code-block:: python + + if not edns_opt_list_remove(edns_opt_list, code): + log_info("python: Option code {} was not found in the " + "list.".format(code)) + +.. note:: All occurences of the EDNS option code will be removed from the list: + + +Controlling other modules' cache behavior +----------------------------------------- + +During the modules' operation, some modules may interact with the cache +(e.g., iterator). This behavior can be controlled by using the following +:class:`module_qstate` flags: + +- :class:`module_qstate.no_cache_lookup`: Modules *operating after* this module + will not lookup the cache for an answer +- :class:`module_qstate.no_cache_store`: Modules *operating after* this module + will not store the response in the cache + +Both values default to ``0``. + +.. code-block:: python + + def operate(id, event, qstate, qdata): + if (event == MODULE_EVENT_NEW) or (event == MODULE_EVENT_PASS): + # Detect if edns option code 56001 is present from the client side. If + # so turn on the flags for cache management. + if not edns_opt_list_is_empty(qstate.edns_opts_front_in): + log_info("python: searching for edns option code 65001 during NEW " + "or PASS event ") + for o in qstate.edns_opts_front_in_iter: + if o.code == 65001: + log_info("python: found edns option code 65001") + # Instruct other modules to not lookup for an + # answer in the cache. + qstate.no_cache_lookup = 1 + log_info("python: enabled no_cache_lookup") + + # Instruct other modules to not store the answer in + # the cache. + qstate.no_cache_store = 1 + log_info("python: enabled no_cache_store") + + +Testing +~~~~~~~ + +Run the Unbound server: :: + + root@localhost$ unbound -dv -c ./test-edns.conf + +In case you use your own configuration file, don't forget to enable the Python +module:: + + module-config: "validator python iterator" + +and use a valid script path:: + + python-script: "./examples/edns.py" + +Quering with EDNS option ``65001:0xc001``: + +:: + + root@localhost$ dig @localhost nlnetlabs.nl +ednsopt=65001:c001 + + ; <<>> DiG 9.10.3-P4-Ubuntu <<>> @localhost nlnetlabs.nl +ednsopt=65001:c001 + ; (1 server found) + ;; global options: +cmd + ;; Got answer: + ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 33450 + ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 4, ADDITIONAL: 3 + + ;; OPT PSEUDOSECTION: + ; EDNS: version: 0, flags:; udp: 4096 + ; OPT=65001: de ad be ef ("....") + ;; QUESTION SECTION: + ;nlnetlabs.nl. IN A + + ;; ANSWER SECTION: + nlnetlabs.nl. 10200 IN A 185.49.140.10 + + ;; AUTHORITY SECTION: + nlnetlabs.nl. 10200 IN NS anyns.pch.net. + nlnetlabs.nl. 10200 IN NS ns.nlnetlabs.nl. + nlnetlabs.nl. 10200 IN NS ns-ext1.sidn.nl. + nlnetlabs.nl. 10200 IN NS sec2.authdns.ripe.net. + + ;; ADDITIONAL SECTION: + ns.nlnetlabs.nl. 10200 IN AAAA 2a04:b900::8:0:0:60 + ns.nlnetlabs.nl. 10200 IN A 185.49.140.60 + + ;; Query time: 10 msec + ;; SERVER: 127.0.0.1#53(127.0.0.1) + ;; WHEN: Mon Dec 05 14:50:56 CET 2016 + ;; MSG SIZE rcvd: 212 + + +Complete source code +~~~~~~~~~~~~~~~~~~~~ + +.. literalinclude:: ../../examples/edns.py + :language: python diff --git a/pythonmod/doc/examples/example6.rst b/pythonmod/doc/examples/example6.rst new file mode 100644 index 000000000..ce89aab99 --- /dev/null +++ b/pythonmod/doc/examples/example6.rst @@ -0,0 +1,299 @@ +Inplace callbacks +================= + +This example shows how to register and use inplace callback functions. These +functions are going to be called just before unbound replies back to a client. +They can perform certain actions without interrupting unbound's execution flow +(e.g. add/remove EDNS options, manipulate the reply). + +Two different scenarios will be shown: + +- If answering from cache and the client used EDNS option code ``65002`` we + will answer with the same code but with data ``0xdeadbeef``; +- When answering with a SERVFAIL we also add an empty EDNS option code + ``65003``. + + +Key parts +~~~~~~~~~ + +This example relies on the following functionalities: + + +Registering inplace callback functions +-------------------------------------- + +There are four types of inplace callback functions: + +- `inplace callback reply functions`_ +- `inplace callback reply_cache functions`_ +- `inplace callback reply_local functions`_ +- `inplace callback reply_servfail functions`_ + + +Inplace callback reply functions +................................ + +Called when answering with a *resolved* query. + +The callback function's prototype is the following: + +.. code-block:: python + + def inplace_reply_callback(qinfo, qstate, rep, rcode, edns, opt_list_out, region): + """Function that will be registered as an inplace callback function. + It will be called when answering with a resolved query. + :param qinfo: query_info struct; + :param qstate: module qstate. It contains the available opt_lists; It + SHOULD NOT be altered; + :param rep: reply_info struct; + :param rcode: return code for the query; + :param edns: edns_data to be sent to the client side. It SHOULD NOT be + altered; + :param opt_list_out: the list with the EDNS options that will be sent as a + reply. It can be populated with EDNS options; + :param region: region to allocate temporary data. Needs to be used when we + want to append a new option to opt_list_out. + :return: True on success, False on failure. + """ + +.. note:: The function's name is irrelevant. + +We can register such function as: + +.. code-block:: python + + if not register_inplace_cb_reply(inplace_reply_callback, env): + log_info("python: Could not register inplace callback function.") + + +Inplace callback reply_cache functions +...................................... + +Called when answering *from cache*. + +The callback function's prototype is the following: + +.. code-block:: python + + def inplace_cache_callback(qinfo, qstate, rep, rcode, edns, opt_list_out, region): + """Function that will be registered as an inplace callback function. + It will be called when answering from the cache. + :param qinfo: query_info struct; + :param qstate: module qstate. None; + :param rep: reply_info struct; + :param rcode: return code for the query; + :param edns: edns_data sent from the client side. The list with the EDNS + options is accesible through edns.opt_list. It SHOULD NOT be + altered; + :param opt_list_out: the list with the EDNS options that will be sent as a + reply. It can be populated with EDNS options; + :param region: region to allocate temporary data. Needs to be used when we + want to append a new option to opt_list_out. + :return: True on success, False on failure. + """ + +.. note:: The function's name is irrelevant. + +We can register such function as: + +.. code-block:: python + + if not register_inplace_cb_reply_cache(inplace_cache_callback, env): + log_info("python: Could not register inplace callback function.") + + +Inplace callback reply_local functions +...................................... + +Called when answering with *local data* or a *Chaos(CH) reply*. + +The callback function's prototype is the following: + +.. code-block:: python + + def inplace_local_callback(qinfo, qstate, rep, rcode, edns, opt_list_out, region): + """Function that will be registered as an inplace callback function. + It will be called when answering from local data. + :param qinfo: query_info struct; + :param qstate: module qstate. None; + :param rep: reply_info struct; + :param rcode: return code for the query; + :param edns: edns_data sent from the client side. The list with the + EDNS options is accesible through edns.opt_list. It + SHOULD NOT be altered; + :param opt_list_out: the list with the EDNS options that will be sent as a + reply. It can be populated with EDNS options; + :param region: region to allocate temporary data. Needs to be used when we + want to append a new option to opt_list_out. + :return: True on success, False on failure. + """ + +.. note:: The function's name is irrelevant. + +We can register such function as: + +.. code-block:: python + + if not register_inplace_cb_reply_local(inplace_local_callback, env): + log_info("python: Could not register inplace callback function.") + + +Inplace callback reply_servfail functions +......................................... + +Called when answering with *SERVFAIL*. + +The callback function's prototype is the following: + +.. code-block:: python + + def inplace_servfail_callback(qinfo, qstate, rep, rcode, edns, opt_list_out, region): + """Function that will be registered as an inplace callback function. + It will be called when answering with SERVFAIL. + :param qinfo: query_info struct; + :param qstate: module qstate. If not None the relevant opt_lists are + available here; + :param rep: reply_info struct. None; + :param rcode: return code for the query. LDNS_RCODE_SERVFAIL; + :param edns: edns_data to be sent to the client side. If qstate is None + edns.opt_list contains the EDNS options sent from the client + side. It SHOULD NOT be altered; + :param opt_list_out: the list with the EDNS options that will be sent as a + reply. It can be populated with EDNS options; + :param region: region to allocate temporary data. Needs to be used when we + want to append a new option to opt_list_out. + :return: True on success, False on failure. + """ + +.. note:: The function's name is irrelevant. + +We can register such function as: + +.. code-block:: python + + if not register_inplace_cb_reply_servfail(inplace_servfail_callback, env): + log_info("python: Could not register inplace callback function.") + + +Testing +~~~~~~~ + +Run the Unbound server: :: + + root@localhost$ unbound -dv -c ./test-inplace_callbacks.conf + +In case you use your own configuration file, don't forget to enable the Python +module:: + + module-config: "validator python iterator" + +and use a valid script path :: + + python-script: "./examples/inplace_callbacks.py" + +On the first query for the nlnetlabs.nl A record we get no EDNS option back: + +:: + + root@localhost$ dig @localhost nlnetlabs.nl +ednsopt=65002 + + ; <<>> DiG 9.10.3-P4-Ubuntu <<>> @localhost nlnetlabs.nl +ednsopt=65002 + ; (1 server found) + ;; global options: +cmd + ;; Got answer: + ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48057 + ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 4, ADDITIONAL: 3 + + ;; OPT PSEUDOSECTION: + ; EDNS: version: 0, flags:; udp: 4096 + ;; QUESTION SECTION: + ;nlnetlabs.nl. IN A + + ;; ANSWER SECTION: + nlnetlabs.nl. 10200 IN A 185.49.140.10 + + ;; AUTHORITY SECTION: + nlnetlabs.nl. 10200 IN NS ns.nlnetlabs.nl. + nlnetlabs.nl. 10200 IN NS sec2.authdns.ripe.net. + nlnetlabs.nl. 10200 IN NS anyns.pch.net. + nlnetlabs.nl. 10200 IN NS ns-ext1.sidn.nl. + + ;; ADDITIONAL SECTION: + ns.nlnetlabs.nl. 10200 IN A 185.49.140.60 + ns.nlnetlabs.nl. 10200 IN AAAA 2a04:b900::8:0:0:60 + + ;; Query time: 813 msec + ;; SERVER: 127.0.0.1#53(127.0.0.1) + ;; WHEN: Mon Dec 05 16:15:32 CET 2016 + ;; MSG SIZE rcvd: 204 + +When we issue the same query again we get a cached response and the expected +``65002: 0xdeadbeef`` EDNS option: + +:: + + root@localhost$ dig @localhost nlnetlabs.nl +ednsopt=65002 + + ; <<>> DiG 9.10.3-P4-Ubuntu <<>> @localhost nlnetlabs.nl +ednsopt=65002 + ; (1 server found) + ;; global options: +cmd + ;; Got answer: + ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 26489 + ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 4, ADDITIONAL: 3 + + ;; OPT PSEUDOSECTION: + ; EDNS: version: 0, flags:; udp: 4096 + ; OPT=65002: de ad be ef ("....") + ;; QUESTION SECTION: + ;nlnetlabs.nl. IN A + + ;; ANSWER SECTION: + nlnetlabs.nl. 10197 IN A 185.49.140.10 + + ;; AUTHORITY SECTION: + nlnetlabs.nl. 10197 IN NS ns.nlnetlabs.nl. + nlnetlabs.nl. 10197 IN NS sec2.authdns.ripe.net. + nlnetlabs.nl. 10197 IN NS anyns.pch.net. + nlnetlabs.nl. 10197 IN NS ns-ext1.sidn.nl. + + ;; ADDITIONAL SECTION: + ns.nlnetlabs.nl. 10197 IN AAAA 2a04:b900::8:0:0:60 + ns.nlnetlabs.nl. 10197 IN A 185.49.140.60 + + ;; Query time: 0 msec + ;; SERVER: 127.0.0.1#53(127.0.0.1) + ;; WHEN: Mon Dec 05 16:50:04 CET 2016 + ;; MSG SIZE rcvd: 212 + +By issuing a query for a bogus domain unbound replies with SERVFAIL and an +empty EDNS option code ``65003``. *For this example to work unbound needs to be +validating*: + +:: + + root@localhost$ dig @localhost bogus.nlnetlabs.nl txt + + ; <<>> DiG 9.10.3-P4-Ubuntu <<>> @localhost bogus.nlnetlabs.nl txt + ; (1 server found) + ;; global options: +cmd + ;; Got answer: + ;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 19865 + ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1 + + ;; OPT PSEUDOSECTION: + ; EDNS: version: 0, flags:; udp: 4096 + ; OPT=65003 + ;; QUESTION SECTION: + ;bogus.nlnetlabs.nl. IN TXT + + ;; Query time: 11 msec + ;; SERVER: 127.0.0.1#53(127.0.0.1) + ;; WHEN: Mon Dec 05 17:06:01 CET 2016 + ;; MSG SIZE rcvd: 51 + + +Complete source code +~~~~~~~~~~~~~~~~~~~~ +.. literalinclude:: ../../examples/inplace_callbacks.py + :language: python diff --git a/pythonmod/doc/examples/index.rst b/pythonmod/doc/examples/index.rst index 6c5022581..93d9b8e1e 100644 --- a/pythonmod/doc/examples/index.rst +++ b/pythonmod/doc/examples/index.rst @@ -1,15 +1,16 @@ .. _Tutorials: -============================== -Tutorials -============================== +Examples +======== -Here you can find several tutorials which clarify the usage and capabilities of Unbound scriptable interface. +Here you can find several tutorials which clarify the usage and capabilities of +the Unbound scriptable interface. -`Tutorials` +Tutorials +--------- .. toctree:: - :maxdepth: 2 - :glob: + :maxdepth: 2 + :glob: - example* + example* diff --git a/pythonmod/doc/install.rst b/pythonmod/doc/install.rst index 991e2b4be..b8d0b9fa6 100644 --- a/pythonmod/doc/install.rst +++ b/pythonmod/doc/install.rst @@ -1,39 +1,44 @@ Installation -=================================== +============ -**Prerequisites** +Prerequisites +------------- Python 2.4 or higher, SWIG 1.3 or higher, GNU make -**Download** +Download +-------- You can download the source codes `here`_. The latest release is 1.1.1, Jan 15, 2009. .. _here: unbound-1.1.1-py.tar.gz -**Compiling** +Compiling +--------- After downloading, you can compile the Unbound library by doing:: - > tar -xzf unbound-1.1.1-py.tar.gz - > cd unbound-1.1.1 - > ./configure --with-pythonmodule - > make + > tar -xzf unbound-1.1.1-py.tar.gz + > cd unbound-1.1.1 + > ./configure --with-pythonmodule + > make You need GNU make to compile sources. SWIG and Python devel libraries to compile extension module. -**Testing** +Testing +------- If the compilation is successful, you can test the extension module by:: - > cd pythonmod - > make sudo # or "make test" or "make suexec" + > cd pythonmod + > make sudo # or "make test" or "make suexec" -This will start unbound server with language dictionary service (see :ref:`Tutorials`). +This will start unbound server with language dictionary service +(see :ref:`Tutorials`). In order to test this service, type:: - + > dig TXT @127.0.0.1 aught.en._dict_.cz Dig should print this message (czech equivalent of aught):: @@ -44,16 +49,17 @@ Dig should print this message (czech equivalent of aught):: ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30085 ;; flags: aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 - + ;; QUESTION SECTION: - ;aught.en._dict_.cz. IN TXT - + ;aught.en._dict_.cz. IN TXT + ;; ANSWER SECTION: - aught.en._dict_.cz. 300 IN TXT "nic" - + aught.en._dict_.cz. 300 IN TXT "nic" + ;; Query time: 11 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) ;; WHEN: Thu Jan 10 16:45:58 2009 ;; MSG SIZE rcvd: 52 -The ``pythonmod/examples`` directory contains simple applications written in Python. +The ``pythonmod/examples`` directory contains simple applications written in +Python. diff --git a/pythonmod/doc/modules/functions.rst b/pythonmod/doc/modules/functions.rst index 45a469fec..627d44922 100644 --- a/pythonmod/doc/modules/functions.rst +++ b/pythonmod/doc/modules/functions.rst @@ -7,25 +7,26 @@ Network .. function:: ntohs(netshort) This subroutine converts values between the host and network byte order. - Specifically, **ntohs()** converts 16-bit quantities from network byte order to host byte order. - + Specifically, **ntohs()** converts 16-bit quantities from network byte order + to host byte order. + :param netshort: 16-bit short addr :rtype: converted addr - - + + Cache ----- .. function:: storeQueryInCache(qstate, qinfo, msgrep, is_referral) Store pending query in local cache. - + :param qstate: :class:`module_qstate` :param qinfo: :class:`query_info` :param msgrep: :class:`reply_info` :param is_referal: integer :rtype: boolean - + .. function:: invalidateQueryInCache(qstate, qinfo) Invalidate record in local cache. @@ -34,6 +35,111 @@ Cache :param qinfo: :class:`query_info` +EDNS options +------------ + +.. function:: register_edns_option(env, code, bypass_cache_stage=False, no_aggregation=False) + + Register EDNS option code. + + :param env: :class:`module_env` + :param code: option code(integer) + :param bypass_cache_stage: whether to bypass the cache response stage + :param no_aggregation: whether this query should be unique + :return: ``1`` if successful, ``0`` otherwise + :rtype: integer + +.. function:: edns_opt_list_find(list, code) + + Find the EDNS option code in the EDNS option list. + + :param list: linked list of :class:`edns_option` + :param code: option code (integer) + :return: the edns option if found or None + :rtype: :class:`edns_option` or None + +.. function:: edns_opt_list_remove(list, code); + + Remove an ENDS option code from the list. + .. note:: All :class:`edns_option` with the code will be removed + + :param list: linked list of :class:`edns_option` + :param code: option code (integer) + :return: ``1`` if at least one :class:`edns_option` was removed, ``0`` otherwise + :rtype: integer + +.. function:: edns_opt_list_append(list, code, data, region) + + Append given EDNS option code with data to the list. + + :param list: linked list of :class:`edns_option` + :param code: option code (integer) + :param data: EDNS data. **Must** be a :class:`bytearray` + :param region: :class:`regional` + +.. function:: edns_opt_list_is_empty(list) + + Check if an EDNS option list is empty. + + :param list: linked list of :class:`edns_option` + :return: ``1`` if list is empty, ``0`` otherwise + :rtype: integer + + +Inplace callbacks +----------------- + +.. function:: inplace_cb_reply(qinfo, qstate, rep, rcode, edns, opt_list_out, region) + + Function prototype for callback functions used in + `register_inplace_cb_reply`_, `register_inplace_cb_reply_cache`_, + `register_inplace_cb_reply_local` and `register_inplace_cb_reply_servfail`. + + :param qinfo: :class:`query_info` + :param qstate: :class:`module_qstate` + :param rep: :class:`reply_info` + :param rcode: return code (integer), check ``RCODE_`` constants. + :param edns: :class:`edns_data` + :param opt_list_out: :class:`edns_option`. EDNS option list to append options to. + :param region: :class:`regional` + +.. function:: register_inplace_cb_reply(py_cb, env) + + Register py_cb as an inplace reply callback function. + + :param py_cb: Python function that follows `inplace_cb_reply`_'s prototype. **Must** be callable. + :param env: :class:`module_env` + :return: True on success, False otherwise + :rtype: boolean + +.. function:: register_inplace_cb_reply_cache(py_cb, env) + + Register py_cb as an inplace reply_cache callback function. + + :param py_cb: Python function that follows `inplace_cb_reply`_'s prototype. **Must** be callable. + :param env: :class:`module_env` + :return: True on success, False otherwise + :rtype: boolean + +.. function:: register_inplace_cb_reply_local(py_cb, env) + + Register py_cb as an inplace reply_local callback function. + + :param py_cb: Python function that follows `inplace_cb_reply`_'s prototype. **Must** be callable. + :param env: :class:`module_env` + :return: True on success, False otherwise + :rtype: boolean + +.. function:: register_inplace_cb_reply_servfail(py_cb, env) + + Register py_cb as an inplace reply_servfail callback function. + + :param py_cb: Python function that follows `inplace_cb_reply`_'s prototype. **Must** be callable. + :param env: :class:`module_env` + :return: True on success, False otherwise + :rtype: boolean + + Logging ------- @@ -71,50 +177,51 @@ Logging :param msg: string desc to accompany the hexdump. :param data: data to dump in hex format. :param length: length of data. - + .. function:: log_dns_msg(str, qinfo, reply) Log DNS message. - + :param str: string message :param qinfo: :class:`query_info` :param reply: :class:`reply_info` - + .. function:: log_query_info(verbosity_value, str, qinf) Log query information. - + :param verbosity_value: see constants :param str: string message :param qinf: :class:`query_info` - + .. function:: regional_log_stats(r) Log regional statistics. - + :param r: :class:`regional` + Debugging --------- .. function:: strextstate(module_ext_state) Debug utility, module external qstate to string. - + :param module_ext_state: the state value. :rtype: descriptive string. .. function:: strmodulevent(module_event) Debug utility, module event to string. - + :param module_event: the module event value. :rtype: descriptive string. - + .. function:: ldns_rr_type2str(atype) Convert RR type to string. - + .. function:: ldns_rr_class2str(aclass) Convert RR class to string. diff --git a/pythonmod/doc/modules/struct.rst b/pythonmod/doc/modules/struct.rst index 669f36d91..3af5d8a48 100644 --- a/pythonmod/doc/modules/struct.rst +++ b/pythonmod/doc/modules/struct.rst @@ -6,55 +6,94 @@ module_qstate .. class:: module_qstate - Module state, per query. - - This class provides these data attributes: - - .. attribute:: qinfo - - (:class:`query_info`) Informations about query being answered. Name, RR type, RR class. - - .. attribute:: query_flags - - (uint16) Flags for query. See QF_BIT\_ predefined constants. - - .. attribute:: is_priming - - If this is a (stub or root) priming query (with hints). - - .. attribute:: reply - - comm_reply contains server replies. - - .. attribute:: return_msg - - (:class:`dns_msg`) The reply message, with message for client and calling module (read-only attribute). - Note that if you want to create of modify return_msg you should use :class:`DNSMessage`. - - .. attribute:: return_rcode - - The rcode, in case of error, instead of a reply message. Determines whether the return_msg contains reply. - - .. attribute:: region - - Region for this query. Cleared when query process finishes. - - .. attribute:: curmod - - Which module is executing. - - .. attribute:: ext_state[] - - Module states. - - .. attribute:: env - - Environment for this query. - - .. attribute:: mesh_info - - Mesh related information for this query. + Module state, per query. + + This class provides these data attributes: + + .. attribute:: qinfo + + (:class:`query_info`) Informations about query being answered. Name, RR type, RR class. + + .. attribute:: query_flags + + (uint16) Flags for query. See QF_BIT\_ predefined constants. + + .. attribute:: is_priming + + If this is a (stub or root) priming query (with hints). + + .. attribute:: reply + + comm_reply contains server replies. + + .. attribute:: return_msg + + (:class:`dns_msg`) The reply message, with message for client and calling module (read-only attribute). + Note that if you want to create of modify return_msg you should use :class:`DNSMessage`. + + .. attribute:: return_rcode + + The rcode, in case of error, instead of a reply message. Determines whether the return_msg contains reply. + + .. attribute:: region + + Region for this query. Cleared when query process finishes. + + .. attribute:: curmod + + Which module is executing. + + .. attribute:: ext_state[] + + Module states. + + .. attribute:: env + + Environment for this query. + .. attribute:: mesh_info + + Mesh related information for this query. + + .. attribute:: edns_opts_front_in + + Incoming EDNS options from the front end. + + .. attribute:: edns_opts_front_in_iter + + Iterator for `edns_opts_front_in`. + + .. attribute:: edns_opts_back_out + + Outgoing EDNS options to the back end. + + .. attribute:: edns_opts_back_out_iter + + Iterator for `edns_opts_back_out`. + + .. attribute:: edns_opts_back_in + + Incoming EDNS options from the back end. + + .. attribute:: edns_opts_back_in_iter + + Iterator for `ends_opts_back_in`. + + .. attribute:: edns_opts_front_out + + Outgoing EDNS options to the front end. + + .. attribute:: edns_opts_front_out_iter + + Iterator for `edns_opts_front_out`. + + .. attribute:: no_cache_lookup + + Flag to indicate whether modules should answer from the cache. + + .. attribute:: no_cache_store + + Flag to indicate whether modules should store answer in the cache. query_info ---------------- @@ -94,7 +133,57 @@ query_info .. attribute:: qclass_str The ``qclass`` in display presentation format (string). - + +edns_data +--------- + +.. class:: edns_data + + This class represents the EDNS information parsed/encoded from/to a packet. It provides these data attributes: + + .. attribute:: edns_present + + If EDNS OPT record is present. + + .. attribute:: ext_rcode + + Extended RCODE. + + .. attribute:: edns_version + + The EDNS version number. + + .. attribute:: bits + + The EDNS bits field from ttl (host order): Z. + + .. attribute:: udp_size + + UDP reassembly size. + + .. attribute:: opt_list + + The EDNS option list. + + .. attribute:: opt_list_iter + + Iterator for `opt_list`. + +edns_option +----------- + +.. class:: edns_option + + This class represents an EDNS option (code, data) found in EDNS option lists. It provides these data attributes: + + .. attribute:: code + + The EDNS option code. + + .. attribute:: data + + The EDNS option data. + reply_info -------------------- diff --git a/pythonmod/examples/edns.py b/pythonmod/examples/edns.py new file mode 100644 index 000000000..3fae1c652 --- /dev/null +++ b/pythonmod/examples/edns.py @@ -0,0 +1,194 @@ +# -*- coding: utf-8 -*- +''' + edns.py: python module showcasing EDNS option functionality. + + Copyright (c) 2016, NLnet Labs. + + This software is open source. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the organization nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +''' +#Try: +# - dig @localhost nlnetlabs.nl +ednsopt=65001:c001 +# This query will always reach the modules stage as EDNS option 65001 is +# registered to bypass the cache response stage. It will also be handled +# as a unique query because of the no_aggregation flag. This means that +# it will not be aggregated with other queries for the same qinfo. +# For demonstration purposes when option 65001 with hexdata 'c001' is +# sent from the client side this module will reply with the same code and +# data 'deadbeef'. + +# Useful functions: +# edns_opt_list_is_empty(edns_opt_list): +# Check if the option list is empty. +# Return True if empty, False otherwise. +# +# edns_opt_list_append(edns_opt_list, code, data_bytearray, region): +# Append the EDNS option with code and data_bytearray to the given +# edns_opt_list. +# NOTE: data_bytearray MUST be a Python bytearray. +# Return True on success, False on failure. +# +# edns_opt_list_remove(edns_opt_list, code): +# Remove all occurences of the given EDNS option code from the +# edns_opt_list. +# Return True when at least one EDNS option was removed, False otherwise. +# +# register_edns_option(env, code, bypass_cache_stage=True, +# no_aggregation=True): +# Register EDNS option code as a known EDNS option. +# bypass_cache_stage: +# bypasses answering from cache and allows the query to reach the +# modules for further EDNS handling. +# no_aggregation: +# makes every query with the said EDNS option code unique. +# Return True on success, False on failure. +# +# Examples on how to use the functions are given in this file. + + +def init_standard(id, env): + """New version of the init function. + The function's signature is the same as the C counterpart and allows for + extra functionality during init. + ..note:: This function is preferred by unbound over the old init function. + ..note:: The previously accesible configuration options can now be found in + env.cgf. + """ + log_info("python: inited script {}".format(env.cfg.python_script)) + + # Register EDNS option 65001 as a known EDNS option. + if not register_edns_option(env, 65001, bypass_cache_stage=True, + no_aggregation=True): + return False + + return True + + +def init(id, cfg): + """Previous version init function. + ..note:: This function is still supported for backwards compatibility when + the init_standard function is missing. When init_standard is + present this function SHOULD be ommited to avoid confusion to the + reader. + """ + return True + + +def deinit(id): return True + + +def inform_super(id, qstate, superqstate, qdata): return True + + +def operate(id, event, qstate, qdata): + if (event == MODULE_EVENT_NEW) or (event == MODULE_EVENT_PASS): + # Detect if EDNS option code 56001 is present from the client side. If + # so turn on the flags for cache management. + if not edns_opt_list_is_empty(qstate.edns_opts_front_in): + log_info("python: searching for EDNS option code 65001 during NEW " + "or PASS event ") + for o in qstate.edns_opts_front_in_iter: + if o.code == 65001: + log_info("python: found EDNS option code 65001") + # Instruct other modules to not lookup for an + # answer in the cache. + qstate.no_cache_lookup = 1 + log_info("python: enabled no_cache_lookup") + + # Instruct other modules to not store the answer in + # the cache. + qstate.no_cache_store = 1 + log_info("python: enabled no_cache_store") + + #Pass on the query + qstate.ext_state[id] = MODULE_WAIT_MODULE + return True + + elif event == MODULE_EVENT_MODDONE: + # If the client sent EDNS option code 65001 and data 'c001' reply + # with the same code and data 'deadbeef'. + if not edns_opt_list_is_empty(qstate.edns_opts_front_in): + log_info("python: searching for EDNS option code 65001 during " + "MODDONE") + for o in qstate.edns_opts_front_in_iter: + if o.code == 65001 and o.data == bytearray.fromhex("c001"): + b = bytearray.fromhex("deadbeef") + if not edns_opt_list_append(qstate.edns_opts_front_out, + o.code, b, qstate.region): + qstate.ext_state[id] = MODULE_ERROR + return False + + # List every EDNS option in all lists. + # The available lists are: + # - qstate.edns_opts_front_in: EDNS options that came from the + # client side. SHOULD NOT be changed; + # + # - qstate.edns_opts_back_out: EDNS options that will be sent to the + # server side. Can be populated by + # EDNS literate modules; + # + # - qstate.edns_opts_back_in: EDNS options that came from the + # server side. SHOULD NOT be changed; + # + # - qstate.edns_opts_front_out: EDNS options that will be sent to the + # client side. Can be populated by + # EDNS literate modules; + # + # The lists' contents can be accessed in python by their _iter + # counterpart as an iterator. + if not edns_opt_list_is_empty(qstate.edns_opts_front_in): + log_info("python: EDNS options in edns_opts_front_in:") + for o in qstate.edns_opts_front_in_iter: + log_info("python: Code: {}, Data: '{}'".format(o.code, + "".join('{:02x}'.format(x) for x in o.data))) + + if not edns_opt_list_is_empty(qstate.edns_opts_back_out): + log_info("python: EDNS options in edns_opts_back_out:") + for o in qstate.edns_opts_back_out_iter: + log_info("python: Code: {}, Data: '{}'".format(o.code, + "".join('{:02x}'.format(x) for x in o.data))) + + if not edns_opt_list_is_empty(qstate.edns_opts_back_in): + log_info("python: EDNS options in edns_opts_back_in:") + for o in qstate.edns_opts_back_in_iter: + log_info("python: Code: {}, Data: '{}'".format(o.code, + "".join('{:02x}'.format(x) for x in o.data))) + + if not edns_opt_list_is_empty(qstate.edns_opts_front_out): + log_info("python: EDNS options in edns_opts_front_out:") + for o in qstate.edns_opts_front_out_iter: + log_info("python: Code: {}, Data: '{}'".format(o.code, + "".join('{:02x}'.format(x) for x in o.data))) + + qstate.ext_state[id] = MODULE_FINISHED + return True + + log_err("pythonmod: Unknown event") + qstate.ext_state[id] = MODULE_ERROR + return True diff --git a/pythonmod/examples/inplace_callbacks.py b/pythonmod/examples/inplace_callbacks.py new file mode 100644 index 000000000..e87614a12 --- /dev/null +++ b/pythonmod/examples/inplace_callbacks.py @@ -0,0 +1,244 @@ +# -*- coding: utf-8 -*- +''' + inplace_callbacks.py: python module showcasing inplace callback function + registration and functionality. + + Copyright (c) 2016, NLnet Labs. + + This software is open source. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the organization nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +''' +#Try: +# - dig @localhost nlnetlabs.nl +ednsopt=65002: +# This query *could* be answered from cache. If so, unbound will reply +# with the same EDNS option 65002, but with hexdata 'deadbeef' as data. +# +# - dig @localhost bogus.nlnetlabs.nl txt: +# This query returns SERVFAIL as the txt record of bogus.nlnetlabs.nl is +# intentionally bogus. The reply will contain an empty EDNS option +# with option code 65003. +# (unbound needs to be validating for this example to work) + +# Useful functions: +# register_inplace_cb_reply(inplace_reply_callback, env): +# Register the reply_callback function as an inplace callback function +# when answering with a resolved query. +# Return True on success, False on failure. +# +# register_inplace_cb_reply_cache(inplace_reply_cache_callback, env): +# Register the reply_cache_callback function as an inplace callback +# function when answering from cache. +# Return True on success, False on failure. +# +# register_inplace_cb_reply_local(inplace_reply_local_callback, env): +# Register the reply_local_callback function as an inplace callback +# function when answering from local data or chaos reply. +# Return True on success, False on failure. +# +# register_inplace_cb_reply_servfail(inplace_reply_servfail_callback, env): +# Register the reply_servfail_callback function as an inplace callback +# function when answering with servfail. +# Return True on success, False on failure. +# +# Examples on how to use the functions are given in this file. + + +def inplace_reply_callback(qinfo, qstate, rep, rcode, edns, opt_list_out, + region): + """Function that will be registered as an inplace callback function. + It will be called when answering with a resolved query. + :param qinfo: query_info struct; + :param qstate: module qstate. It contains the available opt_lists; It + SHOULD NOT be altered; + :param rep: reply_info struct; + :param rcode: return code for the query; + :param edns: edns_data to be sent to the client side. It SHOULD NOT be + altered; + :param opt_list_out: the list with the EDNS options that will be sent as a + reply. It can be populated with EDNS options; + :param region: region to allocate temporary data. Needs to be used when we + want to append a new option to opt_list_out. + :return: True on success, False on failure. + """ + log_info("python: called back while replying.") + return True + + +def inplace_cache_callback(qinfo, qstate, rep, rcode, edns, opt_list_out, + region): + """Function that will be registered as an inplace callback function. + It will be called when answering from the cache. + :param qinfo: query_info struct; + :param qstate: module qstate. None; + :param rep: reply_info struct; + :param rcode: return code for the query; + :param edns: edns_data sent from the client side. The list with the EDNS + options is accesible through edns.opt_list. It SHOULD NOT be + altered; + :param opt_list_out: the list with the EDNS options that will be sent as a + reply. It can be populated with EDNS options; + :param region: region to allocate temporary data. Needs to be used when we + want to append a new option to opt_list_out. + :return: True on success, False on failure. + + For demostration purposes we want to see if EDNS option 65002 is present + and reply with a new value. + """ + log_info("python: called back while answering from cache.") + # Inspect the incoming EDNS options. + if not edns_opt_list_is_empty(edns.opt_list): + log_info("python: available EDNS options:") + for o in edns.opt_list_iter: + log_info("python: Code: {}, Data: '{}'".format(o.code, + "".join('{:02x}'.format(x) for x in o.data))) + if o.code == 65002: + log_info("python: *found option code 65002*") + + # add to opt_list + # Data MUST be represented in a bytearray. + b = bytearray.fromhex("deadbeef") + if edns_opt_list_append(opt_list_out, o.code, b, region): + log_info("python: *added new option code 65002*") + else: + log_info("python: *failed to add new option code 65002*") + return False + break + + return True + + +def inplace_local_callback(qinfo, qstate, rep, rcode, edns, opt_list_out, + region): + """Function that will be registered as an inplace callback function. + It will be called when answering from local data. + :param qinfo: query_info struct; + :param qstate: module qstate. None; + :param rep: reply_info struct; + :param rcode: return code for the query; + :param edns: edns_data sent from the client side. The list with the + EDNS options is accesible through edns.opt_list. It + SHOULD NOT be altered; + :param opt_list_out: the list with the EDNS options that will be sent as a + reply. It can be populated with EDNS options; + :param region: region to allocate temporary data. Needs to be used when we + want to append a new option to opt_list_out. + :return: True on success, False on failure. + """ + log_info("python: called back while replying with local data or chaos" + " reply.") + return True + + +def inplace_servfail_callback(qinfo, qstate, rep, rcode, edns, opt_list_out, + region): + """Function that will be registered as an inplace callback function. + It will be called when answering with SERVFAIL. + :param qinfo: query_info struct; + :param qstate: module qstate. If not None the relevant opt_lists are + available here; + :param rep: reply_info struct. None; + :param rcode: return code for the query. LDNS_RCODE_SERVFAIL; + :param edns: edns_data to be sent to the client side. If qstate is None + edns.opt_list contains the EDNS options sent from the client + side. It SHOULD NOT be altered; + :param opt_list_out: the list with the EDNS options that will be sent as a + reply. It can be populated with EDNS options; + :param region: region to allocate temporary data. Needs to be used when we + want to append a new option to opt_list_out. + :return: True on success, False on failure. + + For demostration purposes we want to reply with an empty EDNS code '65003'. + """ + log_info("python: called back while servfail.") + b = bytearray.fromhex("") + edns_opt_list_append(opt_list_out, 65003, b, region) + return True + + +def init_standard(id, env): + """New version of the init function. + The function's signature is the same as the C counterpart and allows for + extra functionality during init. + ..note:: This function is preferred by unbound over the old init function. + ..note:: The previously accesible configuration options can now be found in + env.cgf. + """ + log_info("python: inited script {}".format(env.cfg.python_script)) + + # Register the inplace_reply_callback function as an inplace callback + # function when answering a resolved query. + if not register_inplace_cb_reply(inplace_reply_callback, env): + return False + + # Register the inplace_cache_callback function as an inplace callback + # function when answering from cache. + if not register_inplace_cb_reply_cache(inplace_cache_callback, env): + return False + + # Register the inplace_local_callback function as an inplace callback + # function when answering from local data. + if not register_inplace_cb_reply_local(inplace_local_callback, env): + return False + + # Register the inplace_servfail_callback function as an inplace callback + # function when answering with SERVFAIL. + if not register_inplace_cb_reply_servfail(inplace_servfail_callback, env): + return False + + return True + + +def init(id, cfg): + """Previous version init function. + ..note:: This function is still supported for backwards compatibility when + the init_standard function is missing. When init_standard is + present this function SHOULD be ommited to avoid confusion to the + reader. + """ + return True + + +def deinit(id): return True + + +def inform_super(id, qstate, superqstate, qdata): return True + + +def operate(id, event, qstate, qdata): + if (event == MODULE_EVENT_NEW) or (event == MODULE_EVENT_PASS): + qstate.ext_state[id] = MODULE_WAIT_MODULE + return True + + elif event == MODULE_EVENT_MODDONE: + qstate.ext_state[id] = MODULE_FINISHED + return True + + log_err("pythonmod: Unknown event") + qstate.ext_state[id] = MODULE_ERROR + return True diff --git a/pythonmod/interface.i b/pythonmod/interface.i index 4b20c6ec1..89d138486 100644 --- a/pythonmod/interface.i +++ b/pythonmod/interface.i @@ -1,7 +1,6 @@ /* * interface.i: unbound python module */ - %module unboundmodule %{ /** @@ -34,10 +33,10 @@ #include "sldns/pkthdr.h" %} -%include "stdint.i" // uint_16_t can be known type now +%include "stdint.i" /* uint_16_t can be known type now */ %inline %{ - //converts [len][data][len][data][0] string to a List of labels (PyBytes) + /* converts [len][data][len][data][0] string to a List of labels (PyBytes) */ PyObject* GetNameAsLabelList(const char* name, int len) { PyObject* list; int cnt=0, i; @@ -202,13 +201,16 @@ struct packed_rrset_key { char* dname; size_t dname_len; uint32_t flags; - uint16_t type; //rrset type in network format - uint16_t rrset_class; //rrset class in network format + uint16_t type; /* rrset type in network format */ + uint16_t rrset_class; /* rrset class in network format */ %mutable; }; -//This subroutine converts values between the host and network byte order. -//Specifically, ntohs() converts 16-bit quantities from network byte order to host byte order. +/** + * This subroutine converts values between the host and network byte order. + * Specifically, ntohs() converts 16-bit quantities from network byte order to + * host byte order. + */ uint16_t ntohs(uint16_t netshort); %inline %{ @@ -269,17 +271,24 @@ struct lruhash_entry { %ignore packed_rrset_data::rr_data; struct packed_rrset_data { - uint32_t ttl; //TTL (in seconds like time()) + /* TTL (in seconds like time()) */ + uint32_t ttl; - size_t count; //number of rrs - size_t rrsig_count; //number of rrsigs + /* number of rrs */ + size_t count; + /* number of rrsigs */ + size_t rrsig_count; enum rrset_trust trust; enum sec_status security; - size_t* rr_len; //length of every rr's rdata - uint32_t *rr_ttl; //ttl of every rr - uint8_t** rr_data; //array of pointers to every rr's rdata; The rr_data[i] rdata is stored in uncompressed wireformat. + /* length of every rr's rdata */ + size_t* rr_len; + /* ttl of every rr */ + uint32_t *rr_ttl; + /* array of pointers to every rr's rdata. The rr_data[i] rdata is stored in + * uncompressed wireformat. */ + uint8_t** rr_data; }; %pythoncode %{ @@ -359,10 +368,10 @@ struct reply_info { size_t an_numrrsets; size_t ns_numrrsets; size_t ar_numrrsets; - size_t rrset_count; // an_numrrsets + ns_numrrsets + ar_numrrsets + size_t rrset_count; /* an_numrrsets + ns_numrrsets + ar_numrrsets */ struct ub_packed_rrset_key** rrsets; - struct rrset_ref ref[1]; //? + struct rrset_ref ref[1]; /* ? */ }; struct rrset_ref { @@ -396,11 +405,11 @@ struct dns_msg { struct rrset_ref* _rrset_ref_get(struct reply_info* r, int idx) { if ((r != NULL) && (idx >= 0) && ((size_t)idx < r->rrset_count)) { -//printf("_rrset_ref_get: %lX key:%lX\n", r->ref + idx, r->ref[idx].key); +/* printf("_rrset_ref_get: %lX key:%lX\n", r->ref + idx, r->ref[idx].key); */ return &(r->ref[idx]); -// return &(r->ref[idx]); +/* return &(r->ref[idx]); */ } -//printf("_rrset_ref_get: NULL\n"); +/* printf("_rrset_ref_get: NULL\n"); */ return NULL; } %} @@ -479,30 +488,166 @@ struct comm_reply { if _newclass:family = _swig_property(_family_get) %} } + +/* ************************************************************************************ * + Structure edns_option + * ************************************************************************************ */ +/* Rename the members to follow the python convention of marking them as + * private. Access to the opt_code and opt_data members is given by the later + * python defined code and data members respectively. */ +%rename(_next) edns_option::next; +%rename(_opt_code) edns_option::opt_code; +%rename(_opt_len) edns_option::opt_len; +%rename(_opt_data) edns_option::opt_data; +struct edns_option { + struct edns_option* next; + uint16_t opt_code; + size_t opt_len; + uint8_t* opt_data; +}; + +%inline %{ + PyObject* _edns_option_opt_code_get(struct edns_option* option) { + uint16_t opt_code = option->opt_code; + return PyInt_FromLong(opt_code); + } + + PyObject* _edns_option_opt_data_get(struct edns_option* option) { + return PyByteArray_FromStringAndSize((uint8_t*)option->opt_data, + option->opt_len); + } +%} +%extend edns_option { + %pythoncode %{ + def _opt_code_get(self): return _edns_option_opt_code_get(self) + __swig_getmethods__["code"] = _opt_code_get + if _newclass: opt_code = _swig_property(_opt_code_get) + + def _opt_data_get(self): return _edns_option_opt_data_get(self) + __swig_getmethods__["data"] = _opt_data_get + if _newclass: opt_data = _swig_property(_opt_data_get) + %} +} + /* ************************************************************************************ * + Structure edns_data + * ************************************************************************************ */ +/* This is ignored because we will pass a double pointer of this to Python + * with custom getmethods. This is done to bypass Swig's behavior to pass NULL + * pointers as None. */ +%ignore edns_data::opt_list; +struct edns_data { + int edns_present; + uint8_t ext_rcode; + uint8_t edns_version; + uint16_t bits; + uint16_t udp_size; + struct edns_option* opt_list; +}; +%inline %{ + struct edns_option** _edns_data_opt_list_get(struct edns_data* edns) { + return &edns->opt_list; + } +%} +%extend edns_data { + %pythoncode %{ + def _opt_list_iter(self): return EdnsOptsListIter(self.opt_list) + __swig_getmethods__["opt_list_iter"] = _opt_list_iter + if _newclass:opt_list_iter = _swig_property(_opt_list_iter) + def _opt_list(self): return _edns_data_opt_list_get(self) + __swig_getmethods__["opt_list"] = _opt_list + if _newclass:opt_list = _swig_property(_opt_list) + %} +} + +/* ************************************************************************************ * + Structure module_env + * ************************************************************************************ */ +struct module_env { + struct config_file* cfg; + struct slabhash* msg_cache; + struct rrset_cache* rrset_cache; + struct infra_cache* infra_cache; + struct key_cache* key_cache; + + /* --- services --- */ + struct outbound_entry* (*send_query)(struct query_info* qinfo, + uint16_t flags, int dnssec, int want_dnssec, int nocaps, + struct sockaddr_storage* addr, socklen_t addrlen, + uint8_t* zone, size_t zonelen, int ssl_upstream, + struct module_qstate* q); + void (*detach_subs)(struct module_qstate* qstate); + int (*attach_sub)(struct module_qstate* qstate, + struct query_info* qinfo, uint16_t qflags, int prime, + int valrec, struct module_qstate** newq); + void (*kill_sub)(struct module_qstate* newq); + int (*detect_cycle)(struct module_qstate* qstate, + struct query_info* qinfo, uint16_t flags, int prime, + int valrec); + + struct regional* scratch; + struct sldns_buffer* scratch_buffer; + struct worker* worker; + struct mesh_area* mesh; + struct alloc_cache* alloc; + struct ub_randstate* rnd; + time_t* now; + struct timeval* now_tv; + int need_to_validate; + struct val_anchors* anchors; + struct val_neg_cache* neg_cache; + struct comm_timer* probe_timer; + struct iter_forwards* fwds; + struct iter_hints* hints; + void* modinfo[MAX_MODULE]; + + void* inplace_cb_lists[inplace_cb_types_total]; + struct edns_known_option* edns_known_options; + size_t edns_known_options_num; +}; + + +/* ************************************************************************************ * Structure module_qstate * ************************************************************************************ */ %ignore module_qstate::ext_state; %ignore module_qstate::minfo; +/* These are ignored because we will pass a double pointer of them to Python + * with custom getmethods. This is done to bypass Swig's behavior to pass NULL + * pointers as None. */ +%ignore module_qstate::edns_opts_front_in; +%ignore module_qstate::edns_opts_back_out; +%ignore module_qstate::edns_opts_back_in; +%ignore module_qstate::edns_opts_front_out; + /* Query state */ struct module_qstate { struct query_info qinfo; - uint16_t query_flags; //See QF_BIT_xx constants - int is_priming; + uint16_t query_flags; /* See QF_BIT_xx constants */ + int is_priming; + int is_valrec; struct comm_reply* reply; struct dns_msg* return_msg; - int return_rcode; + int return_rcode; struct regional* region; /* unwrapped */ - int curmod; + int curmod; - enum module_ext_state ext_state[MAX_MODULE]; - void* minfo[MAX_MODULE]; + enum module_ext_state ext_state[MAX_MODULE]; + void* minfo[MAX_MODULE]; + time_t prefetch_leeway; struct module_env* env; /* unwrapped */ struct mesh_state* mesh_info; + + struct edns_option* edns_opts_front_in; + struct edns_option* edns_opts_back_out; + struct edns_option* edns_opts_back_in; + struct edns_option* edns_opts_front_out; + int no_cache_lookup; + int no_cache_store; }; %constant int MODULE_COUNT = MAX_MODULE; @@ -540,6 +685,25 @@ struct module_qstate { def __getitem__(self, index): return _unboundmodule._ext_state_get(self.obj, index) def __setitem__(self, index, value): _unboundmodule._ext_state_set(self.obj, index, value) def __len__(self): return _unboundmodule.MODULE_COUNT + + class EdnsOptsListIter: + def __init__(self, obj): + self._current = obj + self._temp = None + def __iter__(self): return self + def __next__(self): + """Python 3 compatibility""" + return self._get_next() + def next(self): + """Python 2 compatibility""" + return self._get_next() + def _get_next(self): + if not edns_opt_list_is_empty(self._current): + self._temp = self._current + self._current = _p_p_edns_option_get_next(self._current) + return _dereference_edns_option(self._temp) + else: + raise StopIteration %} %inline %{ @@ -549,12 +713,42 @@ struct module_qstate { } return 0; } - + void _ext_state_set(struct module_qstate* q, int idx, enum module_ext_state state) { if ((q != NULL) && (idx >= 0) && (idx < MAX_MODULE)) { q->ext_state[idx] = state; } } + + int edns_opt_list_is_empty(struct edns_option** opt) { + if (!opt || !(*opt)) return 1; + return 0; + } + + struct edns_option* _dereference_edns_option(struct edns_option** opt) { + if (!opt) return NULL; + return *opt; + } + + struct edns_option** _p_p_edns_option_get_next(struct edns_option** opt) { + return &(*opt)->next; + } + + struct edns_option** _edns_opts_front_in_get(struct module_qstate* q) { + return &q->edns_opts_front_in; + } + + struct edns_option** _edns_opts_back_out_get(struct module_qstate* q) { + return &q->edns_opts_back_out; + } + + struct edns_option** _edns_opts_back_in_get(struct module_qstate* q) { + return &q->edns_opts_back_in; + } + + struct edns_option** _edns_opts_front_out_get(struct module_qstate* q) { + return &q->edns_opts_front_out; + } %} %extend module_qstate { @@ -566,6 +760,32 @@ struct module_qstate { def __ext_state_get(self): return ExtState(self) __swig_getmethods__["ext_state"] = __ext_state_get if _newclass:ext_state = _swig_property(__ext_state_get)#, __ext_state_set) + + def _edns_opts_front_in_iter(self): return EdnsOptsListIter(self.edns_opts_front_in) + __swig_getmethods__["edns_opts_front_in_iter"] = _edns_opts_front_in_iter + if _newclass:edns_opts_front_in_iter = _swig_property(_edns_opts_front_in_iter) + def _edns_opts_back_out_iter(self): return EdnsOptsListIter(self.edns_opts_back_out) + __swig_getmethods__["edns_opts_back_out_iter"] = _edns_opts_back_out_iter + if _newclass:edns_opts_back_out_iter = _swig_property(_edns_opts_back_out_iter) + def _edns_opts_back_in_iter(self): return EdnsOptsListIter(self.edns_opts_back_in) + __swig_getmethods__["edns_opts_back_in_iter"] = _edns_opts_back_in_iter + if _newclass:edns_opts_back_in_iter = _swig_property(_edns_opts_back_in_iter) + def _edns_opts_front_out_iter(self): return EdnsOptsListIter(self.edns_opts_front_out) + __swig_getmethods__["edns_opts_front_out_iter"] = _edns_opts_front_out_iter + if _newclass:edns_opts_front_out_iter = _swig_property(_edns_opts_front_out_iter) + + def _edns_opts_front_in(self): return _edns_opts_front_in_get(self) + __swig_getmethods__["edns_opts_front_in"] = _edns_opts_front_in + if _newclass:edns_opts_front_in = _swig_property(_edns_opts_front_in) + def _edns_opts_back_out(self): return _edns_opts_back_out_get(self) + __swig_getmethods__["edns_opts_back_out"] = _edns_opts_back_out + if _newclass:edns_opts_back_out = _swig_property(_edns_opts_back_out) + def _edns_opts_back_in(self): return _edns_opts_back_in_get(self) + __swig_getmethods__["edns_opts_back_in"] = _edns_opts_back_in + if _newclass:edns_opts_back_in = _swig_property(_edns_opts_back_in) + def _edns_opts_front_out(self): return _edns_opts_front_out_get(self) + __swig_getmethods__["edns_opts_front_out"] = _edns_opts_front_out + if _newclass:edns_opts_front_out = _swig_property(_edns_opts_front_out) %} } @@ -1037,8 +1257,9 @@ struct delegpt* find_delegation(struct module_qstate* qstate, char *nm, size_t n /* ************************************************************************************ * Functions * ************************************************************************************ */ - -// Various debuging functions +/****************************** + * Various debuging functions * + ******************************/ void verbose(enum verbosity_value level, const char* format, ...); void log_info(const char* format, ...); void log_err(const char* format, ...); @@ -1048,24 +1269,166 @@ void log_dns_msg(const char* str, struct query_info* qinfo, struct reply_info* r void log_query_info(enum verbosity_value v, const char* str, struct query_info* qinf); void regional_log_stats(struct regional *r); -// Free allocated memory from marked sources returning corresponding types +/*************************************************************************** + * Free allocated memory from marked sources returning corresponding types * + ***************************************************************************/ %typemap(newfree, noblock = 1) char * { free($1); } -// Mark as source returning newly allocated memory +/*************************************************** + * Mark as source returning newly allocated memory * + ***************************************************/ %newobject sldns_wire2str_type; %newobject sldns_wire2str_class; -// LDNS functions +/****************** + * LDNS functions * + ******************/ char *sldns_wire2str_type(const uint16_t atype); char *sldns_wire2str_class(const uint16_t aclass); -// Functions from pythonmod_utils +/********************************** + * Functions from pythonmod_utils * + **********************************/ int storeQueryInCache(struct module_qstate* qstate, struct query_info* qinfo, struct reply_info* msgrep, int is_referral); void invalidateQueryInCache(struct module_qstate* qstate, struct query_info* qinfo); -// Module conversion functions +/******************************* + * Module conversion functions * + *******************************/ const char* strextstate(enum module_ext_state s); const char* strmodulevent(enum module_ev e); +/************************** + * Edns related functions * + **************************/ +struct edns_option* edns_opt_list_find(struct edns_option* list, uint16_t code); +int edns_register_option(uint16_t opt_code, int bypass_cache_stage, + int no_aggregation, struct module_env* env); + +%pythoncode %{ + def register_edns_option(env, code, bypass_cache_stage=False, + no_aggregation=False): + """Wrapper function to provide keyword attributes.""" + return edns_register_option(code, bypass_cache_stage, + no_aggregation, env) +%} + +/****************************** + * Callback related functions * + ******************************/ +/* typemap to check if argument is callable */ +%typemap(in) PyObject *py_cb { + if (!PyCallable_Check($input)) { + SWIG_exception_fail(SWIG_TypeError, "Need a callable object!"); + return NULL; + } + $1 = $input; +} +/* typemap to get content/size from a bytearray */ +%typemap(in) (size_t len, uint8_t* py_bytearray_data) { + if (!PyByteArray_CheckExact($input)) { + SWIG_exception_fail(SWIG_TypeError, "Expected bytearray!"); + return NULL; + } + $2 = PyByteArray_AsString($input); + $1 = PyByteArray_Size($input); +} + +int edns_opt_list_remove(struct edns_option** list, uint16_t code); +int edns_opt_list_append(struct edns_option** list, uint16_t code, size_t len, + uint8_t* py_bytearray_data, struct regional* region); + +%{ + /* This function is called by unbound in order to call the python + * callback function. */ + int python_inplace_cb_reply_generic(struct query_info* qinfo, + struct module_qstate* qstate, struct reply_info* rep, int rcode, + struct edns_data* edns, struct edns_option** opt_list_out, + struct regional* region, void* python_callback) + { + PyObject *func, *py_edns, *py_qstate, *py_opt_list_out, *py_qinfo; + PyObject *py_rep, *py_region; + PyObject *result; + int res = 0; + + func = (PyObject *) python_callback; + PyGILState_STATE gstate = PyGILState_Ensure(); + py_edns = SWIG_NewPointerObj((void*) edns, SWIGTYPE_p_edns_data, 0); + py_qstate = SWIG_NewPointerObj((void*) qstate, + SWIGTYPE_p_module_qstate, 0); + py_opt_list_out = SWIG_NewPointerObj((void*) opt_list_out, + SWIGTYPE_p_p_edns_option, 0); + py_qinfo = SWIG_NewPointerObj((void*) qinfo, SWIGTYPE_p_query_info, 0); + py_rep = SWIG_NewPointerObj((void*) rep, SWIGTYPE_p_reply_info, 0); + py_region = SWIG_NewPointerObj((void*) region, SWIGTYPE_p_regional, 0); + result = PyObject_CallFunction(func, "OOOiOOO", py_qinfo, py_qstate, + py_rep, rcode, py_edns, py_opt_list_out, py_region); + Py_XDECREF(py_edns); + Py_XDECREF(py_qstate); + Py_XDECREF(py_opt_list_out); + Py_XDECREF(py_qinfo); + Py_XDECREF(py_rep); + Py_XDECREF(py_region); + if (result) { + res = PyInt_AsLong(result); + } + Py_XDECREF(result); + PyGILState_Release(gstate); + return res; + } + + /* Swig implementations for Python */ + static int register_inplace_cb_reply(PyObject* py_cb, + struct module_env* env) + { + int ret = inplace_cb_reply_register( + python_inplace_cb_reply_generic, (void*) py_cb, env); + if (ret) Py_INCREF(py_cb); + return ret; + } + static int register_inplace_cb_reply_cache(PyObject* py_cb, + struct module_env* env) + { + int ret = inplace_cb_reply_cache_register( + python_inplace_cb_reply_generic, (void*) py_cb, env); + if (ret) Py_INCREF(py_cb); + return ret; + } + static int register_inplace_cb_reply_local(PyObject* py_cb, + struct module_env* env) + { + int ret = inplace_cb_reply_local_register( + python_inplace_cb_reply_generic, (void*) py_cb, env); + if (ret) Py_INCREF(py_cb); + return ret; + } + static int register_inplace_cb_reply_servfail(PyObject* py_cb, + struct module_env* env) + { + int ret = inplace_cb_reply_servfail_register( + python_inplace_cb_reply_generic, (void*) py_cb, env); + if (ret) Py_INCREF(py_cb); + return ret; + } +%} +/* C declarations */ +int inplace_cb_reply_register( + inplace_cb_reply_func_t* cb, void* cb_arg, struct module_env* env); +int inplace_cb_reply_cache_register( + inplace_cb_reply_func_t* cb, void* cb_arg, struct module_env* env); +int inplace_cb_reply_local_register( + inplace_cb_reply_func_t* cb, void* cb_arg, struct module_env* env); +int inplace_cb_reply_servfail_register( + inplace_cb_reply_func_t* cb, void* cb_arg, struct module_env* env); + +/* Swig declarations */ +static int register_inplace_cb_reply(PyObject* py_cb, + struct module_env* env); +static int register_inplace_cb_reply_cache(PyObject* py_cb, + struct module_env* env); +static int register_inplace_cb_reply_local(PyObject* py_cb, + struct module_env* env); +static int register_inplace_cb_reply_servfail(PyObject* py_cb, + struct module_env* env); diff --git a/pythonmod/pythonmod.c b/pythonmod/pythonmod.c index 6c5e6392d..92e09dcac 100644 --- a/pythonmod/pythonmod.c +++ b/pythonmod/pythonmod.c @@ -112,8 +112,10 @@ int pythonmod_init(struct module_env* env, int id) { /* Initialize module */ FILE* script_py = NULL; - PyObject* py_cfg, *res; + PyObject* py_init_arg, *res; PyGILState_STATE gil; + int init_standard = 1; + struct pythonmod_env* pe = (struct pythonmod_env*)calloc(1, sizeof(struct pythonmod_env)); if (!pe) { @@ -156,10 +158,10 @@ int pythonmod_init(struct module_env* env, int id) PyRun_SimpleString("import sys \n"); PyRun_SimpleString("sys.path.append('.') \n"); if(env->cfg->directory && env->cfg->directory[0]) { - char wdir[1524]; - snprintf(wdir, sizeof(wdir), "sys.path.append('%s') \n", - env->cfg->directory); - PyRun_SimpleString(wdir); + char wdir[1524]; + snprintf(wdir, sizeof(wdir), "sys.path.append('%s') \n", + env->cfg->directory); + PyRun_SimpleString(wdir); } PyRun_SimpleString("sys.path.append('"RUN_DIR"') \n"); PyRun_SimpleString("sys.path.append('"SHARE_DIR"') \n"); @@ -198,11 +200,15 @@ int pythonmod_init(struct module_env* env, int id) fclose(script_py); - if ((pe->func_init = PyDict_GetItemString(pe->dict, "init")) == NULL) + if ((pe->func_init = PyDict_GetItemString(pe->dict, "init_standard")) == NULL) { - log_err("pythonmod: function init is missing in %s", pe->fname); - PyGILState_Release(gil); - return 0; + init_standard = 0; + if ((pe->func_init = PyDict_GetItemString(pe->dict, "init")) == NULL) + { + log_err("pythonmod: function init is missing in %s", pe->fname); + PyGILState_Release(gil); + return 0; + } } if ((pe->func_deinit = PyDict_GetItemString(pe->dict, "deinit")) == NULL) { @@ -223,16 +229,28 @@ int pythonmod_init(struct module_env* env, int id) return 0; } - py_cfg = SWIG_NewPointerObj((void*) env->cfg, SWIGTYPE_p_config_file, 0); - res = PyObject_CallFunction(pe->func_init, "iO", id, py_cfg); + if (init_standard) + { + py_init_arg = SWIG_NewPointerObj((void*) env, SWIGTYPE_p_module_env, 0); + } + else + { + py_init_arg = SWIG_NewPointerObj((void*) env->cfg, + SWIGTYPE_p_config_file, 0); + } + res = PyObject_CallFunction(pe->func_init, "iO", id, py_init_arg); if (PyErr_Occurred()) { log_err("pythonmod: Exception occurred in function init"); PyErr_Print(); + Py_XDECREF(res); + Py_XDECREF(py_init_arg); + PyGILState_Release(gil); + return 0; } Py_XDECREF(res); - Py_XDECREF(py_cfg); + Py_XDECREF(py_init_arg); PyGILState_Release(gil); return 1; diff --git a/pythonmod/pythonmod.h b/pythonmod/pythonmod.h index b108cf923..2386882de 100644 --- a/pythonmod/pythonmod.h +++ b/pythonmod/pythonmod.h @@ -55,14 +55,22 @@ int pythonmod_init(struct module_env* env, int id); void pythonmod_deinit(struct module_env* env, int id); /** python module operate on a query */ -void pythonmod_operate(struct module_qstate* qstate, enum module_ev event, int id, struct outbound_entry* outbound); +void pythonmod_operate(struct module_qstate* qstate, enum module_ev event, + int id, struct outbound_entry* outbound); /** python module */ -void pythonmod_inform_super(struct module_qstate* qstate, int id, struct module_qstate* super); +void pythonmod_inform_super(struct module_qstate* qstate, int id, + struct module_qstate* super); /** python module cleanup query state */ void pythonmod_clear(struct module_qstate* qstate, int id); /** python module alloc size routine */ size_t pythonmod_get_mem(struct module_env* env, int id); + +/** Declared here for fptr_wlist access. The definition is in interface.i. */ +int python_inplace_cb_reply_generic(struct query_info* qinfo, + struct module_qstate* qstate, struct reply_info* rep, int rcode, + struct edns_data* edns, struct edns_option** opt_list_out, + struct regional* region, void* python_callback); #endif /* PYTHONMOD_H */ diff --git a/pythonmod/test-edns.conf b/pythonmod/test-edns.conf new file mode 100644 index 000000000..440947f01 --- /dev/null +++ b/pythonmod/test-edns.conf @@ -0,0 +1,17 @@ +# Example configuration file for edns.py +server: + verbosity: 1 + interface: 0.0.0.0 + do-daemonize: no + access-control: 0.0.0.0/0 allow + chroot: "" + username: "" + directory: "" + logfile: "" + pidfile: "unbound.pid" + module-config: "validator python iterator" + +# Python config section +python: + # Script file to load + python-script: "./examples/edns.py" diff --git a/pythonmod/test-inplace_callbacks.py b/pythonmod/test-inplace_callbacks.py new file mode 100644 index 000000000..d7081faa6 --- /dev/null +++ b/pythonmod/test-inplace_callbacks.py @@ -0,0 +1,17 @@ +# Example configuration file for edns.py +server: + verbosity: 1 + interface: 0.0.0.0 + do-daemonize: no + access-control: 0.0.0.0/0 allow + chroot: "" + username: "" + directory: "" + logfile: "" + pidfile: "unbound.pid" + module-config: "validator python iterator" + +# Python config section +python: + # Script file to load + python-script: "./examples/inplace_callbacks.py" diff --git a/services/localzone.c b/services/localzone.c index 5822c810f..0ea74d856 100644 --- a/services/localzone.c +++ b/services/localzone.c @@ -1182,8 +1182,8 @@ void local_zones_print(struct local_zones* zones) /** encode answer consisting of 1 rrset */ static int -local_encode(struct query_info* qinfo, struct edns_data* edns, - sldns_buffer* buf, struct regional* temp, +local_encode(struct query_info* qinfo, struct module_env* env, + struct edns_data* edns, sldns_buffer* buf, struct regional* temp, struct ub_packed_rrset_key* rrset, int ansec, int rcode) { struct reply_info rep; @@ -1202,15 +1202,15 @@ local_encode(struct query_info* qinfo, struct edns_data* edns, edns->udp_size = EDNS_ADVERTISED_SIZE; edns->ext_rcode = 0; edns->bits &= EDNS_DO; - if(!edns_opt_inplace_reply(edns, temp) || - !reply_info_answer_encode(qinfo, &rep, + if(!inplace_cb_reply_local_call(env, qinfo, NULL, &rep, rcode, edns, temp) + || !reply_info_answer_encode(qinfo, &rep, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), - buf, 0, 0, temp, udpsize, edns, + buf, 0, 0, temp, udpsize, edns, (int)(edns->bits&EDNS_DO), 0)) error_encode(buf, (LDNS_RCODE_SERVFAIL|BIT_AA), qinfo, *(uint16_t*)sldns_buffer_begin(buf), - sldns_buffer_read_u16_at(buf, 2), edns); + sldns_buffer_read_u16_at(buf, 2), edns); return 1; } @@ -1318,11 +1318,11 @@ find_tag_datas(struct query_info* qinfo, struct config_strlist* list, /** answer local data match */ static int -local_data_answer(struct local_zone* z, struct query_info* qinfo, - struct edns_data* edns, sldns_buffer* buf, struct regional* temp, - int labs, struct local_data** ldp, enum localzone_type lz_type, - int tag, struct config_strlist** tag_datas, size_t tag_datas_size, - char** tagname, int num_tags) +local_data_answer(struct local_zone* z, struct module_env* env, + struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf, + struct regional* temp, int labs, struct local_data** ldp, + enum localzone_type lz_type, int tag, struct config_strlist** tag_datas, + size_t tag_datas_size, char** tagname, int num_tags) { struct local_data key; struct local_data* ld; @@ -1348,7 +1348,7 @@ local_data_answer(struct local_zone* z, struct query_info* qinfo, * chain. */ if(qinfo->local_alias) return 1; - return local_encode(qinfo, edns, buf, temp, + return local_encode(qinfo, env, edns, buf, temp, &r, 1, LDNS_RCODE_NOERROR); } } @@ -1383,16 +1383,17 @@ local_data_answer(struct local_zone* z, struct query_info* qinfo, struct ub_packed_rrset_key r = *lr->rrset; r.rk.dname = qinfo->qname; r.rk.dname_len = qinfo->qname_len; - return local_encode(qinfo, edns, buf, temp, &r, 1, + return local_encode(qinfo, env, edns, buf, temp, &r, 1, LDNS_RCODE_NOERROR); } - return local_encode(qinfo, edns, buf, temp, lr->rrset, 1, + return local_encode(qinfo, env, edns, buf, temp, lr->rrset, 1, LDNS_RCODE_NOERROR); } /** * answer in case where no exact match is found * @param z: zone for query + * @param env: module environment * @param qinfo: query * @param edns: edns from query * @param buf: buffer for answer. @@ -1402,9 +1403,9 @@ local_data_answer(struct local_zone* z, struct query_info* qinfo, * @return 1 if a reply is to be sent, 0 if not. */ static int -lz_zone_answer(struct local_zone* z, struct query_info* qinfo, - struct edns_data* edns, sldns_buffer* buf, struct regional* temp, - struct local_data* ld, enum localzone_type lz_type) +lz_zone_answer(struct local_zone* z, struct module_env* env, + struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf, + struct regional* temp, struct local_data* ld, enum localzone_type lz_type) { if(lz_type == local_zone_deny || lz_type == local_zone_inform_deny) { /** no reply at all, signal caller by clearing buffer. */ @@ -1430,7 +1431,7 @@ lz_zone_answer(struct local_zone* z, struct query_info* qinfo, int rcode = (ld || lz_type == local_zone_redirect)? LDNS_RCODE_NOERROR:LDNS_RCODE_NXDOMAIN; if(z->soa) - return local_encode(qinfo, edns, buf, temp, + return local_encode(qinfo, env, edns, buf, temp, z->soa, 0, rcode); error_encode(buf, (rcode|BIT_AA), qinfo, *(uint16_t*)sldns_buffer_begin(buf), @@ -1448,7 +1449,7 @@ lz_zone_answer(struct local_zone* z, struct query_info* qinfo, if(ld && ld->rrsets) { int rcode = LDNS_RCODE_NOERROR; if(z->soa) - return local_encode(qinfo, edns, buf, temp, + return local_encode(qinfo, env, edns, buf, temp, z->soa, 0, rcode); error_encode(buf, (rcode|BIT_AA), qinfo, *(uint16_t*)sldns_buffer_begin(buf), @@ -1521,10 +1522,10 @@ lz_type(uint8_t *taglist, size_t taglen, uint8_t *taglist2, size_t taglen2, } int -local_zones_answer(struct local_zones* zones, struct query_info* qinfo, - struct edns_data* edns, sldns_buffer* buf, struct regional* temp, - struct comm_reply* repinfo, uint8_t* taglist, size_t taglen, - uint8_t* tagactions, size_t tagactionssize, +local_zones_answer(struct local_zones* zones, struct module_env* env, + struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf, + struct regional* temp, struct comm_reply* repinfo, uint8_t* taglist, + size_t taglen, uint8_t* tagactions, size_t tagactionssize, struct config_strlist** tag_datas, size_t tag_datas_size, char** tagname, int num_tags, struct view* view) { @@ -1578,14 +1579,14 @@ local_zones_answer(struct local_zones* zones, struct query_info* qinfo, if(lzt != local_zone_always_refuse && lzt != local_zone_always_transparent && lzt != local_zone_always_nxdomain - && local_data_answer(z, qinfo, edns, buf, temp, labs, &ld, lzt, + && local_data_answer(z, env, qinfo, edns, buf, temp, labs, &ld, lzt, tag, tag_datas, tag_datas_size, tagname, num_tags)) { lock_rw_unlock(&z->lock); /* We should tell the caller that encode is deferred if we found * a local alias. */ return !qinfo->local_alias; } - r = lz_zone_answer(z, qinfo, edns, buf, temp, ld, lzt); + r = lz_zone_answer(z, env, qinfo, edns, buf, temp, ld, lzt); lock_rw_unlock(&z->lock); return r && !qinfo->local_alias; /* see above */ } diff --git a/services/localzone.h b/services/localzone.h index c202d8cbf..6db9b3dd9 100644 --- a/services/localzone.h +++ b/services/localzone.h @@ -44,6 +44,7 @@ #include "util/rbtree.h" #include "util/locks.h" #include "util/storage/dnstree.h" +#include "util/module.h" #include "services/view.h" struct ub_packed_rrset_key; struct regional; @@ -267,6 +268,7 @@ void local_zones_print(struct local_zones* zones); * Answer authoritatively for local zones. * Takes care of locking. * @param zones: the stored zones (shared, read only). + * @param env: the module environment. * @param qinfo: query info (parsed). * @param edns: edns info (parsed). * @param buf: buffer with query ID and flags, also for reply. @@ -293,10 +295,10 @@ void local_zones_print(struct local_zones* zones); * if it needs to keep it beyond the lifetime of 'temp' or a dynamic update * to local zone data. */ -int local_zones_answer(struct local_zones* zones, struct query_info* qinfo, - struct edns_data* edns, struct sldns_buffer* buf, struct regional* temp, - struct comm_reply* repinfo, uint8_t* taglist, size_t taglen, - uint8_t* tagactions, size_t tagactionssize, +int local_zones_answer(struct local_zones* zones, struct module_env* env, + struct query_info* qinfo, struct edns_data* edns, struct sldns_buffer* buf, + struct regional* temp, struct comm_reply* repinfo, uint8_t* taglist, + size_t taglen, uint8_t* tagactions, size_t tagactionssize, struct config_strlist** tag_datas, size_t tag_datas_size, char** tagname, int num_tags, struct view* view); diff --git a/services/mesh.c b/services/mesh.c index 52de12366..83a01ede8 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -56,6 +56,7 @@ #include "util/alloc.h" #include "util/config_file.h" #include "sldns/sbuffer.h" +#include "sldns/wire2str.h" #include "services/localzone.h" #include "util/data/dname.h" @@ -129,6 +130,11 @@ mesh_state_compare(const void* ap, const void* bp) struct mesh_state* a = (struct mesh_state*)ap; struct mesh_state* b = (struct mesh_state*)bp; + if(a->unique < b->unique) + return -1; + if(a->unique > b->unique) + return 1; + if(a->s.is_priming && !b->s.is_priming) return -1; if(!a->s.is_priming && b->s.is_priming) @@ -284,10 +290,13 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, uint16_t qflags, struct edns_data* edns, struct comm_reply* rep, uint16_t qid) { - struct mesh_state* s = mesh_area_find(mesh, qinfo, qflags&(BIT_RD|BIT_CD), 0, 0); + struct mesh_state* s = NULL; + int unique = edns_unique_mesh_state(edns->opt_list, mesh->env); int was_detached = 0; int was_noreply = 0; int added = 0; + if(!unique) + s = mesh_area_find(mesh, qinfo, qflags&(BIT_RD|BIT_CD), 0, 0); /* does this create a new reply state? */ if(!s || s->list_select == mesh_no_list) { if(!mesh_make_new_space(mesh, rep->c->buffer)) { @@ -317,13 +326,32 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, s = mesh_state_create(mesh->env, qinfo, qflags&(BIT_RD|BIT_CD), 0, 0); if(!s) { log_err("mesh_state_create: out of memory; SERVFAIL"); - if(!edns_opt_inplace_reply(edns, mesh->env->scratch)) - edns->opt_list = NULL; + if(!inplace_cb_reply_servfail_call(mesh->env, qinfo, NULL, NULL, + LDNS_RCODE_SERVFAIL, edns, mesh->env->scratch)) + edns->opt_list = NULL; error_encode(rep->c->buffer, LDNS_RCODE_SERVFAIL, qinfo, qid, qflags, edns); comm_point_send_reply(rep); return; } + if(unique) + mesh_state_make_unique(s); + /* copy the edns options we got from the front */ + if(edns->opt_list) { + s->s.edns_opts_front_in = edns_opt_copy_region(edns->opt_list, + s->s.region); + if(!s->s.edns_opts_front_in) { + log_err("mesh_state_create: out of memory; SERVFAIL"); + if(!inplace_cb_reply_servfail_call(mesh->env, qinfo, NULL, + NULL, LDNS_RCODE_SERVFAIL, edns, mesh->env->scratch)) + edns->opt_list = NULL; + error_encode(rep->c->buffer, LDNS_RCODE_SERVFAIL, + qinfo, qid, qflags, edns); + comm_point_send_reply(rep); + return; + } + } + #ifdef UNBOUND_DEBUG n = #else @@ -342,8 +370,9 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, /* add reply to s */ if(!mesh_state_add_reply(s, edns, rep, qid, qflags, qinfo)) { log_err("mesh_new_client: out of memory; SERVFAIL"); - if(!edns_opt_inplace_reply(edns, mesh->env->scratch)) - edns->opt_list = NULL; + if(!inplace_cb_reply_servfail_call(mesh->env, qinfo, &s->s, + NULL, LDNS_RCODE_SERVFAIL, edns, mesh->env->scratch)) + edns->opt_list = NULL; error_encode(rep->c->buffer, LDNS_RCODE_SERVFAIL, qinfo, qid, qflags, edns); comm_point_send_reply(rep); @@ -382,10 +411,13 @@ mesh_new_callback(struct mesh_area* mesh, struct query_info* qinfo, uint16_t qflags, struct edns_data* edns, sldns_buffer* buf, uint16_t qid, mesh_cb_func_t cb, void* cb_arg) { - struct mesh_state* s = mesh_area_find(mesh, qinfo, qflags&(BIT_RD|BIT_CD), 0, 0); + struct mesh_state* s = NULL; + int unique = edns_unique_mesh_state(edns->opt_list, mesh->env); int was_detached = 0; int was_noreply = 0; int added = 0; + if(!unique) + s = mesh_area_find(mesh, qinfo, qflags&(BIT_RD|BIT_CD), 0, 0); /* there are no limits on the number of callbacks */ /* see if it already exists, if not, create one */ @@ -397,6 +429,15 @@ mesh_new_callback(struct mesh_area* mesh, struct query_info* qinfo, if(!s) { return 0; } + if(unique) + mesh_state_make_unique(s); + if(edns->opt_list) { + s->s.edns_opts_front_in = edns_opt_copy_region(edns->opt_list, + s->s.region); + if(!s->s.edns_opts_front_in) { + return 0; + } + } #ifdef UNBOUND_DEBUG n = #else @@ -435,7 +476,8 @@ mesh_new_callback(struct mesh_area* mesh, struct query_info* qinfo, void mesh_new_prefetch(struct mesh_area* mesh, struct query_info* qinfo, uint16_t qflags, time_t leeway) { - struct mesh_state* s = mesh_area_find(mesh, qinfo, qflags&(BIT_RD|BIT_CD), 0, 0); + struct mesh_state* s = mesh_area_find(mesh, qinfo, qflags&(BIT_RD|BIT_CD), + 0, 0); #ifdef UNBOUND_DEBUG struct rbnode_t* n; #endif @@ -454,6 +496,7 @@ void mesh_new_prefetch(struct mesh_area* mesh, struct query_info* qinfo, mesh->stats_dropped ++; return; } + s = mesh_state_create(mesh->env, qinfo, qflags&(BIT_RD|BIT_CD), 0, 0); if(!s) { log_err("prefetch mesh_state_create: out of memory"); @@ -527,6 +570,7 @@ mesh_state_create(struct module_env* env, struct query_info* qinfo, rbtree_init(&mstate->super_set, &mesh_state_ref_compare); rbtree_init(&mstate->sub_set, &mesh_state_ref_compare); mstate->num_activated = 0; + mstate->unique = NULL; /* init module qstate */ mstate->s.qinfo.qtype = qinfo->qtype; mstate->s.qinfo.qclass = qinfo->qclass; @@ -550,14 +594,34 @@ mesh_state_create(struct module_env* env, struct query_info* qinfo, mstate->s.env = env; mstate->s.mesh_info = mstate; mstate->s.prefetch_leeway = 0; + mstate->s.no_cache_lookup = 0; + mstate->s.no_cache_store = 0; /* init modules */ for(i=0; imesh->mods.num; i++) { mstate->s.minfo[i] = NULL; mstate->s.ext_state[i] = module_state_initial; } + /* init edns option lists */ + mstate->s.edns_opts_front_in = NULL; + mstate->s.edns_opts_back_out = NULL; + mstate->s.edns_opts_back_in = NULL; + mstate->s.edns_opts_front_out = NULL; + return mstate; } +int +mesh_state_is_unique(struct mesh_state* mstate) +{ + return mstate->unique != NULL; +} + +void +mesh_state_make_unique(struct mesh_state* mstate) +{ + mstate->unique = mstate; +} + void mesh_state_cleanup(struct mesh_state* mstate) { @@ -692,8 +756,7 @@ int mesh_attach_sub(struct module_qstate* qstate, struct query_info* qinfo, { /* find it, if not, create it */ struct mesh_area* mesh = qstate->env->mesh; - struct mesh_state* sub = mesh_area_find(mesh, qinfo, qflags, prime, - valrec); + struct mesh_state* sub = mesh_area_find(mesh, qinfo, qflags, prime, valrec); int was_detached; if(mesh_detect_cycle_found(qstate, sub)) { verbose(VERB_ALGO, "attach failed, cycle detected"); @@ -704,8 +767,7 @@ int mesh_attach_sub(struct module_qstate* qstate, struct query_info* qinfo, struct rbnode_t* n; #endif /* create a new one */ - sub = mesh_state_create(qstate->env, qinfo, qflags, prime, - valrec); + sub = mesh_state_create(qstate->env, qinfo, qflags, prime, valrec); if(!sub) { log_err("mesh_attach_sub: out of memory"); return 0; @@ -807,6 +869,15 @@ mesh_do_callback(struct mesh_state* m, int rcode, struct reply_info* rep, } /* send the reply */ if(rcode) { + if(rcode == LDNS_RCODE_SERVFAIL) { + if(!inplace_cb_reply_servfail_call(m->s.env, &m->s.qinfo, &m->s, + rep, rcode, &r->edns, m->s.region)) + r->edns.opt_list = NULL; + } else { + if(!inplace_cb_reply_call(m->s.env, &m->s.qinfo, &m->s, rep, rcode, + &r->edns, m->s.region)) + r->edns.opt_list = NULL; + } fptr_ok(fptr_whitelist_mesh_cb(r->cb)); (*r->cb)(r->cb_arg, rcode, r->buf, sec_status_unchecked, NULL); } else { @@ -816,8 +887,10 @@ mesh_do_callback(struct mesh_state* m, int rcode, struct reply_info* rep, r->edns.udp_size = EDNS_ADVERTISED_SIZE; r->edns.ext_rcode = 0; r->edns.bits &= EDNS_DO; - if(!edns_opt_inplace_reply(&r->edns, m->s.region) || - !reply_info_answer_encode(&m->s.qinfo, rep, r->qid, + + if(!inplace_cb_reply_call(m->s.env, &m->s.qinfo, &m->s, rep, + LDNS_RCODE_NOERROR, &r->edns, m->s.region) || + !reply_info_answer_encode(&m->s.qinfo, rep, r->qid, r->qflags, r->buf, 0, 1, m->s.env->scratch, udp_size, &r->edns, (int)(r->edns.bits & EDNS_DO), secure)) @@ -890,9 +963,16 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep, comm_point_send_reply(&r->query_reply); } else if(rcode) { m->s.qinfo.qname = r->qname; - if(!edns_opt_inplace_reply(&r->edns, m->s.region)) - r->edns.opt_list = NULL; m->s.qinfo.local_alias = r->local_alias; + if(rcode == LDNS_RCODE_SERVFAIL) { + if(!inplace_cb_reply_servfail_call(m->s.env, &m->s.qinfo, &m->s, + rep, rcode, &r->edns, m->s.region)) + r->edns.opt_list = NULL; + } else { + if(!inplace_cb_reply_call(m->s.env, &m->s.qinfo, &m->s, rep, rcode, + &r->edns, m->s.region)) + r->edns.opt_list = NULL; + } error_encode(r->query_reply.c->buffer, rcode, &m->s.qinfo, r->qid, r->qflags, &r->edns); comm_point_send_reply(&r->query_reply); @@ -904,12 +984,16 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep, r->edns.bits &= EDNS_DO; m->s.qinfo.qname = r->qname; m->s.qinfo.local_alias = r->local_alias; - if(!edns_opt_inplace_reply(&r->edns, m->s.region) || - !reply_info_answer_encode(&m->s.qinfo, rep, r->qid, + if(!inplace_cb_reply_call(m->s.env, &m->s.qinfo, &m->s, rep, + LDNS_RCODE_NOERROR, &r->edns, m->s.region) || + !reply_info_answer_encode(&m->s.qinfo, rep, r->qid, r->qflags, r->query_reply.c->buffer, 0, 1, m->s.env->scratch, udp_size, &r->edns, (int)(r->edns.bits & EDNS_DO), secure)) { + if(!inplace_cb_reply_servfail_call(m->s.env, &m->s.qinfo, &m->s, + rep, LDNS_RCODE_SERVFAIL, &r->edns, m->s.region)) + r->edns.opt_list = NULL; error_encode(r->query_reply.c->buffer, LDNS_RCODE_SERVFAIL, &m->s.qinfo, r->qid, r->qflags, &r->edns); @@ -980,6 +1064,10 @@ struct mesh_state* mesh_area_find(struct mesh_area* mesh, key.s.is_valrec = valrec; key.s.qinfo = *qinfo; key.s.query_flags = qflags; + /* 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).*/ + key.unique = NULL; result = (struct mesh_state*)rbtree_search(&mesh->all, &key); return result; @@ -1282,8 +1370,9 @@ mesh_detect_cycle(struct module_qstate* qstate, struct query_info* qinfo, uint16_t flags, int prime, int valrec) { struct mesh_area* mesh = qstate->env->mesh; - struct mesh_state* dep_m = mesh_area_find(mesh, qinfo, flags, prime, - valrec); + struct mesh_state* dep_m = NULL; + if(!mesh_state_is_unique(qstate->mesh_info)) + dep_m = mesh_area_find(mesh, qinfo, flags, prime, valrec); return mesh_detect_cycle_found(qstate, dep_m); } diff --git a/services/mesh.h b/services/mesh.h index 612cbb7f9..7dd62ef19 100644 --- a/services/mesh.h +++ b/services/mesh.h @@ -180,6 +180,8 @@ struct mesh_state { /** if this state is in the forever list, jostle list, or neither */ enum mesh_list_select { mesh_no_list, mesh_forever_list, mesh_jostle_list } list_select; + /** pointer to this state for uniqueness or NULL */ + struct mesh_state* unique; /** true if replies have been sent out (at end for alignment) */ uint8_t replies_sent; @@ -416,6 +418,21 @@ void mesh_state_delete(struct module_qstate* qstate); struct mesh_state* mesh_state_create(struct module_env* env, struct query_info* qinfo, uint16_t qflags, int prime, int valrec); +/** + * Check if the mesh state is unique. + * A unique mesh state uses it's unique member to point to itself, else NULL. + * @param mstate: mesh state to check. + * @return true if the mesh state is unique, false otherwise. + */ +int mesh_state_is_unique(struct mesh_state* mstate); + +/** + * Make a mesh state unique. + * A unique mesh state uses it's unique member to point to itself. + * @param mstate: mesh state to check. + */ +void mesh_state_make_unique(struct mesh_state* mstate); + /** * Cleanup a mesh state and its query state. Does not do rbtree or * reference cleanup. diff --git a/services/outside_network.c b/services/outside_network.c index 6aaa7962f..eba019520 100644 --- a/services/outside_network.c +++ b/services/outside_network.c @@ -1986,17 +1986,22 @@ serviced_udp_callback(struct comm_point* c, void* arg, int error, struct serviced_query* outnet_serviced_query(struct outside_network* outnet, - uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, - uint16_t flags, int dnssec, int want_dnssec, int nocaps, - int tcp_upstream, int ssl_upstream, struct edns_option* opt_list, + struct query_info* qinfo, uint16_t flags, int dnssec, int want_dnssec, + int nocaps, int tcp_upstream, int ssl_upstream, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, - size_t zonelen, comm_point_callback_t* callback, void* callback_arg, - sldns_buffer* buff) + size_t zonelen, struct module_qstate* qstate, + comm_point_callback_t* callback, void* callback_arg, sldns_buffer* buff, + struct module_env* env) { struct serviced_query* sq; struct service_callback* cb; - serviced_gen_query(buff, qname, qnamelen, qtype, qclass, flags); - sq = lookup_serviced(outnet, buff, dnssec, addr, addrlen, opt_list); + if(!inplace_cb_query_call(env, qinfo, flags, addr, addrlen, zone, zonelen, + qstate, qstate->region)) + return NULL; + serviced_gen_query(buff, qinfo->qname, qinfo->qname_len, qinfo->qtype, + qinfo->qclass, flags); + sq = lookup_serviced(outnet, buff, dnssec, addr, addrlen, + qstate->edns_opts_back_out); /* duplicate entries are included in the callback list, because * there is a counterpart registration by our caller that needs to * be doubly-removed (with callbacks perhaps). */ @@ -2006,7 +2011,7 @@ outnet_serviced_query(struct outside_network* outnet, /* make new serviced query entry */ sq = serviced_create(outnet, buff, dnssec, want_dnssec, nocaps, tcp_upstream, ssl_upstream, addr, addrlen, zone, - zonelen, (int)qtype, opt_list); + zonelen, (int)qinfo->qtype, qstate->edns_opts_back_out); if(!sq) { free(cb); return NULL; diff --git a/services/outside_network.h b/services/outside_network.h index d6a448c0b..f006b04cb 100644 --- a/services/outside_network.h +++ b/services/outside_network.h @@ -59,6 +59,9 @@ struct sldns_buffer; struct serviced_query; struct dt_env; struct edns_option; +struct module_env; +struct module_qstate; +struct query_info; /** * Send queries to outside servers and wait for answers from servers. @@ -471,10 +474,7 @@ void pending_delete(struct outside_network* outnet, struct pending* p); * Perform a serviced query to the authoritative servers. * Duplicate efforts are detected, and EDNS, TCP and UDP retry is performed. * @param outnet: outside network, with rbtree of serviced queries. - * @param qname: what qname to query. - * @param qnamelen: length of qname in octets including 0 root label. - * @param qtype: rrset type to query (host format) - * @param qclass: query class. (host format) + * @param qinfo: query info. * @param flags: flags u16 (host format), includes opcode, CD bit. * @param dnssec: if set, DO bit is set in EDNS queries. * If the value includes BIT_CD, CD bit is set when in EDNS queries. @@ -484,27 +484,28 @@ void pending_delete(struct outside_network* outnet, struct pending* p); * @param nocaps: ignore use_caps_for_id and use unperturbed qname. * @param tcp_upstream: use TCP for upstream queries. * @param ssl_upstream: use SSL for upstream queries. - * @param opt_list: pass edns option list (deep copied into serviced query) - * these options are set on the outgoing packets. - * @param callback: callback function. - * @param callback_arg: user argument to callback function. * @param addr: to which server to send the query. * @param addrlen: length of addr. * @param zone: name of the zone of the delegation point. wireformat dname. This is the delegation point name for which the server is deemed authoritative. * @param zonelen: length of zone. + * @param qstate: module qstate. Mainly for inspecting the available + * edns_opts_lists. + * @param callback: callback function. + * @param callback_arg: user argument to callback function. * @param buff: scratch buffer to create query contents in. Empty on exit. + * @param env: the module environment. * @return 0 on error, or pointer to serviced query that is used to answer * this serviced query may be shared with other callbacks as well. */ struct serviced_query* outnet_serviced_query(struct outside_network* outnet, - uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, - uint16_t flags, int dnssec, int want_dnssec, int nocaps, - int tcp_upstream, int ssl_upstream, struct edns_option* opt_list, + struct query_info* qinfo, uint16_t flags, int dnssec, int want_dnssec, + int nocaps, int tcp_upstream, int ssl_upstream, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, - size_t zonelen, comm_point_callback_t* callback, void* callback_arg, - struct sldns_buffer* buff); + size_t zonelen, struct module_qstate* qstate, + comm_point_callback_t* callback, void* callback_arg, + struct sldns_buffer* buff, struct module_env* env); /** * Remove service query callback. diff --git a/smallapp/unbound-checkconf.c b/smallapp/unbound-checkconf.c index 6670d87dc..eebc0e76e 100644 --- a/smallapp/unbound-checkconf.c +++ b/smallapp/unbound-checkconf.c @@ -118,12 +118,15 @@ check_mod(struct config_file* cfg, struct module_func_block* fb) env.scratch_buffer = sldns_buffer_new(BUFSIZ); if(!env.scratch || !env.scratch_buffer) fatal_exit("out of memory"); + if(!edns_known_options_init(&env)) + fatal_exit("out of memory"); if(!(*fb->init)(&env, 0)) { fatal_exit("bad config for %s module", fb->name); } (*fb->deinit)(&env, 0); sldns_buffer_free(env.scratch_buffer); regional_destroy(env.scratch); + edns_known_options_delete(&env); } /** check localzones */ diff --git a/smallapp/worker_cb.c b/smallapp/worker_cb.c index efeef37cd..e88e8c8d7 100644 --- a/smallapp/worker_cb.c +++ b/smallapp/worker_cb.c @@ -99,12 +99,10 @@ void worker_sighandler(int ATTR_UNUSED(sig), void* ATTR_UNUSED(arg)) log_assert(0); } -struct outbound_entry* worker_send_query(uint8_t* ATTR_UNUSED(qname), - size_t ATTR_UNUSED(qnamelen), uint16_t ATTR_UNUSED(qtype), - uint16_t ATTR_UNUSED(qclass), uint16_t ATTR_UNUSED(flags), +struct outbound_entry* worker_send_query( + struct query_info* ATTR_UNUSED(qinfo), uint16_t ATTR_UNUSED(flags), int ATTR_UNUSED(dnssec), int ATTR_UNUSED(want_dnssec), - int ATTR_UNUSED(nocaps), struct edns_option* ATTR_UNUSED(opt_list), - struct sockaddr_storage* ATTR_UNUSED(addr), + int ATTR_UNUSED(nocaps), struct sockaddr_storage* ATTR_UNUSED(addr), socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone), size_t ATTR_UNUSED(zonelen), int ATTR_UNUSED(ssl_upstream), struct module_qstate* ATTR_UNUSED(q)) @@ -133,12 +131,10 @@ worker_alloc_cleanup(void* ATTR_UNUSED(arg)) log_assert(0); } -struct outbound_entry* libworker_send_query(uint8_t* ATTR_UNUSED(qname), - size_t ATTR_UNUSED(qnamelen), uint16_t ATTR_UNUSED(qtype), - uint16_t ATTR_UNUSED(qclass), uint16_t ATTR_UNUSED(flags), +struct outbound_entry* libworker_send_query( + struct query_info* ATTR_UNUSED(qinfo), uint16_t ATTR_UNUSED(flags), int ATTR_UNUSED(dnssec), int ATTR_UNUSED(want_dnssec), - int ATTR_UNUSED(nocaps), struct edns_option* ATTR_UNUSED(opt_list), - struct sockaddr_storage* ATTR_UNUSED(addr), + int ATTR_UNUSED(nocaps), struct sockaddr_storage* ATTR_UNUSED(addr), socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone), size_t ATTR_UNUSED(zonelen), int ATTR_UNUSED(ssl_upstream), struct module_qstate* ATTR_UNUSED(q)) diff --git a/testcode/fake_event.c b/testcode/fake_event.c index 7e0d075cd..bc6588617 100644 --- a/testcode/fake_event.c +++ b/testcode/fake_event.c @@ -1036,13 +1036,13 @@ pending_tcp_query(struct serviced_query* sq, sldns_buffer* packet, } struct serviced_query* outnet_serviced_query(struct outside_network* outnet, - uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, - uint16_t flags, int dnssec, int ATTR_UNUSED(want_dnssec), - int ATTR_UNUSED(nocaps), int ATTR_UNUSED(tcp_upstream), - int ATTR_UNUSED(ssl_upstream), struct edns_option* opt_list, + struct query_info* qinfo, uint16_t flags, int dnssec, + int ATTR_UNUSED(want_dnssec), int ATTR_UNUSED(nocaps), + int ATTR_UNUSED(tcp_upstream), int ATTR_UNUSED(ssl_upstream), struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, - size_t zonelen, comm_point_callback_t* callback, void* callback_arg, - sldns_buffer* ATTR_UNUSED(buff)) + size_t zonelen, struct module_qstate* qstate, + comm_point_callback_t* callback, void* callback_arg, + sldns_buffer* ATTR_UNUSED(buff), struct module_env* ATTR_UNUSED(env)) { struct replay_runtime* runtime = (struct replay_runtime*)outnet->base; struct fake_pending* pend = (struct fake_pending*)calloc(1, @@ -1050,7 +1050,7 @@ struct serviced_query* outnet_serviced_query(struct outside_network* outnet, char z[256]; log_assert(pend); log_nametypeclass(VERB_OPS, "pending serviced query", - qname, qtype, qclass); + qinfo->qname, qinfo->qtype, qinfo->qclass); dname_str(zone, z); verbose(VERB_OPS, "pending serviced query zone %s flags%s%s%s%s", z, (flags&BIT_RD)?" RD":"", (flags&BIT_CD)?" CD":"", @@ -1065,9 +1065,9 @@ struct serviced_query* outnet_serviced_query(struct outside_network* outnet, sldns_buffer_write_u16(pend->buffer, 0); /* ancount */ sldns_buffer_write_u16(pend->buffer, 0); /* nscount */ sldns_buffer_write_u16(pend->buffer, 0); /* arcount */ - sldns_buffer_write(pend->buffer, qname, qnamelen); - sldns_buffer_write_u16(pend->buffer, qtype); - sldns_buffer_write_u16(pend->buffer, qclass); + sldns_buffer_write(pend->buffer, qinfo->qname, qinfo->qname_len); + sldns_buffer_write_u16(pend->buffer, qinfo->qtype); + sldns_buffer_write_u16(pend->buffer, qinfo->qclass); sldns_buffer_flip(pend->buffer); if(1) { /* add edns */ @@ -1077,7 +1077,7 @@ struct serviced_query* outnet_serviced_query(struct outside_network* outnet, edns.edns_version = EDNS_ADVERTISED_VERSION; edns.udp_size = EDNS_ADVERTISED_SIZE; edns.bits = 0; - edns.opt_list = opt_list; + edns.opt_list = qstate->edns_opts_back_out; if(dnssec) edns.bits = EDNS_DO; attach_edns_record(pend->buffer, &edns); @@ -1086,7 +1086,7 @@ struct serviced_query* outnet_serviced_query(struct outside_network* outnet, pend->addrlen = addrlen; pend->zone = memdup(zone, zonelen); pend->zonelen = zonelen; - pend->qtype = (int)qtype; + pend->qtype = (int)qinfo->qtype; log_assert(pend->zone); pend->callback = callback; pend->cb_arg = callback_arg; diff --git a/util/data/msgparse.c b/util/data/msgparse.c index 1d565c1ea..3774054f5 100644 --- a/util/data/msgparse.c +++ b/util/data/msgparse.c @@ -1072,3 +1072,22 @@ parse_edns_from_pkt(sldns_buffer* pkt, struct edns_data* edns, return 0; } + +void +log_edns_opt_list(enum verbosity_value level, const char* info_str, + struct edns_option* list) +{ + if(verbosity >= level && list) { + char str[128], *s; + size_t slen; + verbose(level, "%s", info_str); + while(list) { + s = str; + slen = sizeof(str); + (void)sldns_wire2str_edns_option_print(&s, &slen, list->opt_code, + list->opt_data, list->opt_len); + verbose(level, " %s", str); + list = list->next; + } + } +} diff --git a/util/data/msgparse.h b/util/data/msgparse.h index cae988ff9..594517b2d 100644 --- a/util/data/msgparse.h +++ b/util/data/msgparse.h @@ -322,4 +322,13 @@ struct rrset_parse* msgparse_hashtable_lookup(struct msg_parse* msg, */ void msgparse_bucket_remove(struct msg_parse* msg, struct rrset_parse* rrset); +/** + * Log the edns options in the edns option list. + * @param level: the verbosity level. + * @param info_str: the informational string to be printed before the options. + * @param list: the edns option list. + */ +void log_edns_opt_list(enum verbosity_value level, const char* info_str, + struct edns_option* list); + #endif /* UTIL_DATA_MSGPARSE_H */ diff --git a/util/data/msgreply.c b/util/data/msgreply.c index 1f02eac33..2caee7789 100644 --- a/util/data/msgreply.c +++ b/util/data/msgreply.c @@ -52,6 +52,8 @@ #include "util/data/msgencode.h" #include "sldns/sbuffer.h" #include "sldns/wire2str.h" +#include "util/module.h" +#include "util/fptr_wlist.h" /** MAX TTL default for messages and rrsets */ time_t MAX_TTL = 3600 * 24 * 10; /* ten days */ @@ -874,9 +876,12 @@ int edns_opt_append(struct edns_data* edns, struct regional* region, opt->next = NULL; opt->opt_code = code; opt->opt_len = len; - opt->opt_data = regional_alloc_init(region, data, len); - if(!opt->opt_data) - return 0; + opt->opt_data = NULL; + if(len > 0) { + opt->opt_data = regional_alloc_init(region, data, len); + if(!opt->opt_data) + return 0; + } /* append at end of list */ prevp = &edns->opt_list; @@ -886,13 +891,138 @@ int edns_opt_append(struct edns_data* edns, struct regional* region, return 1; } -int edns_opt_inplace_reply(struct edns_data* edns, struct regional* region) +int edns_opt_list_append(struct edns_option** list, uint16_t code, size_t len, + uint8_t* data, struct regional* region) +{ + struct edns_option** prevp; + struct edns_option* opt; + + /* allocate new element */ + opt = (struct edns_option*)regional_alloc(region, sizeof(*opt)); + if(!opt) + return 0; + opt->next = NULL; + opt->opt_code = code; + opt->opt_len = len; + opt->opt_data = NULL; + if(len > 0) { + opt->opt_data = regional_alloc_init(region, data, len); + if(!opt->opt_data) + return 0; + } + + /* append at end of list */ + prevp = list; + while(*prevp != NULL) { + prevp = &((*prevp)->next); + } + *prevp = opt; + return 1; +} + +int edns_opt_list_remove(struct edns_option** list, uint16_t code) +{ + /* The list should already be allocated in a region. Freeing the + * allocated space in a region is not possible. We just unlink the + * required elements and they will be freed together with the region. */ + + struct edns_option* prev; + struct edns_option* curr; + if(!list || !(*list)) return 0; + + /* Unlink and repoint if the element(s) are first in list */ + while(list && *list && (*list)->opt_code == code) { + *list = (*list)->next; + } + + if(!list || !(*list)) return 1; + /* Unlink elements and reattach the list */ + prev = *list; + curr = (*list)->next; + while(curr != NULL) { + if(curr->opt_code == code) { + prev->next = curr->next; + curr = curr->next; + } else { + prev = curr; + curr = curr->next; + } + } + return 1; +} + +static int inplace_cb_reply_call_generic( + struct inplace_cb_reply* callback_list, enum inplace_cb_list_type type, + struct query_info* qinfo, struct module_qstate* qstate, + struct reply_info* rep, int rcode, struct edns_data* edns, + struct regional* region) { - (void)region; - /* remove all edns options from the reply, because only the - * options that we understand should be in the reply - * (sec 6.1.2 RFC 6891) */ - edns->opt_list = NULL; + struct inplace_cb_reply* cb; + struct edns_option* opt_list_out = NULL; + if(qstate) + opt_list_out = qstate->edns_opts_front_out; + for(cb=callback_list; cb; cb=cb->next) { + fptr_ok(fptr_whitelist_inplace_cb_reply_generic(cb->cb, type)); + (void)(*cb->cb)(qinfo, qstate, rep, rcode, edns, &opt_list_out, region, + cb->cb_arg); + } + edns->opt_list = opt_list_out; + return 1; +} + +int inplace_cb_reply_call(struct module_env* env, struct query_info* qinfo, + struct module_qstate* qstate, struct reply_info* rep, int rcode, + struct edns_data* edns, struct regional* region) +{ + return inplace_cb_reply_call_generic( + env->inplace_cb_lists[inplace_cb_reply], inplace_cb_reply, qinfo, + qstate, rep, rcode, edns, region); +} + +int inplace_cb_reply_cache_call(struct module_env* env, + struct query_info* qinfo, struct module_qstate* qstate, + struct reply_info* rep, int rcode, struct edns_data* edns, + struct regional* region) +{ + return inplace_cb_reply_call_generic( + env->inplace_cb_lists[inplace_cb_reply_cache], inplace_cb_reply_cache, + qinfo, qstate, rep, rcode, edns, region); +} + +int inplace_cb_reply_local_call(struct module_env* env, + struct query_info* qinfo, struct module_qstate* qstate, + struct reply_info* rep, int rcode, struct edns_data* edns, + struct regional* region) +{ + return inplace_cb_reply_call_generic( + env->inplace_cb_lists[inplace_cb_reply_local], inplace_cb_reply_local, + qinfo, qstate, rep, rcode, edns, region); +} + +int inplace_cb_reply_servfail_call(struct module_env* env, + struct query_info* qinfo, struct module_qstate* qstate, + struct reply_info* rep, int rcode, struct edns_data* edns, + struct regional* region) +{ + /* We are going to servfail. Remove any potential edns options. */ + if(qstate) + qstate->edns_opts_front_out = NULL; + return inplace_cb_reply_call_generic( + env->inplace_cb_lists[inplace_cb_reply_servfail], + inplace_cb_reply_servfail, qinfo, qstate, rep, rcode, edns, region); +} + +int inplace_cb_query_call(struct module_env* env, struct query_info* qinfo, + uint16_t flags, struct sockaddr_storage* addr, socklen_t addrlen, + uint8_t* zone, size_t zonelen, struct module_qstate* qstate, + struct regional* region) +{ + struct inplace_cb_query* cb = env->inplace_cb_lists[inplace_cb_query]; + for(; cb; cb=cb->next) { + fptr_ok(fptr_whitelist_inplace_cb_query(cb->cb)); + (void)(*cb->cb)(qinfo, flags, qstate, addr, addrlen, zone, zonelen, + region, cb->cb_arg); + } return 1; } @@ -1003,7 +1133,7 @@ struct edns_option* edns_opt_copy_alloc(struct edns_option* list) return result; } -struct edns_option* edns_opt_find(struct edns_option* list, uint16_t code) +struct edns_option* edns_opt_list_find(struct edns_option* list, uint16_t code) { struct edns_option* p; for(p=list; p; p=p->next) { diff --git a/util/data/msgreply.h b/util/data/msgreply.h index 729e35573..cc0216133 100644 --- a/util/data/msgreply.h +++ b/util/data/msgreply.h @@ -49,6 +49,11 @@ struct alloc_cache; struct iovec; struct regional; struct edns_data; +struct edns_option; +struct inplace_cb_reply; +struct inplace_cb_query; +struct module_qstate; +struct module_env; struct msg_parse; struct rrset_parse; struct local_rrset; @@ -457,29 +462,133 @@ void log_query_info(enum verbosity_value v, const char* str, /** * Append edns option to edns data structure + * @param edns: the edns data structure to append the edns option to. + * @param region: region to allocate the new edns option. + * @param code: the edns option's code. + * @param len: the edns option's length. + * @param data: the edns option's data. + * @return false on failure. */ int edns_opt_append(struct edns_data* edns, struct regional* region, uint16_t code, size_t len, uint8_t* data); +/** + * Append edns option to edns option list + * @param list: the edns option list to append the edns option to. + * @param code: the edns option's code. + * @param len: the edns option's length. + * @param data: the edns option's data. + * @param region: region to allocate the new edns option. + * @return false on failure. + */ +int edns_opt_list_append(struct edns_option** list, uint16_t code, size_t len, + uint8_t* data, struct regional* region); + +/** + * Remove any option found on the edns option list that matches the code. + * @param list: the list of edns options. + * @param code: the opt code to remove. + * @return true when at least one edns option was removed, false otherwise. + */ +int edns_opt_list_remove(struct edns_option** list, uint16_t code); + /** * Find edns option in edns list * @param list: list of edns options (eg. edns.opt_list) * @param code: opt code to find. * @return NULL or the edns_option element. */ -struct edns_option* edns_opt_find(struct edns_option* list, uint16_t code); +struct edns_option* edns_opt_list_find(struct edns_option* list, uint16_t code); /** - * Transform edns data structure from query structure into reply structure. - * In place transform, for errors and cache replies. - * @param edns: on input contains the edns from the query. On output contains - * the edns for the answer. Add new options to the opt_list to put them - * in the answer (allocated in the region, with edns_opt_append). - * @param region: to allocate stuff in. - * @return false on failure (servfail to client, or for some error encodings, - * no EDNS options in the answer). - */ -int edns_opt_inplace_reply(struct edns_data* edns, struct regional* region); + * Call the registered functions in the inplace_cb_reply linked list. + * This function is going to get called while answering with a resolved query. + * @param env: module environment. + * @param qinfo: query info. + * @param qstate: module qstate. + * @param rep: Reply info. Could be NULL. + * @param rcode: return code. + * @param edns: edns data of the reply. + * @param region: region to store data. + * @return false on failure (a callback function returned an error). + */ +int inplace_cb_reply_call(struct module_env* env, struct query_info* qinfo, + struct module_qstate* qstate, struct reply_info* rep, int rcode, + struct edns_data* edns, struct regional* region); + +/** + * Call the registered functions in the inplace_cb_reply_cache linked list. + * This function is going to get called while answering from cache. + * @param env: module environment. + * @param qinfo: query info. + * @param qstate: module qstate. NULL when replying from cache. + * @param rep: Reply info. + * @param rcode: return code. + * @param edns: edns data of the reply. Edns input can be found here. + * @param region: region to store data. + * @return false on failure (a callback function returned an error). + */ +int inplace_cb_reply_cache_call(struct module_env* env, + struct query_info* qinfo, struct module_qstate* qstate, + struct reply_info* rep, int rcode, struct edns_data* edns, + struct regional* region); + +/** + * Call the registered functions in the inplace_cb_reply_local linked list. + * This function is going to get called while answering with local data. + * @param env: module environment. + * @param qinfo: query info. + * @param qstate: module qstate. NULL when replying from cache. + * @param rep: Reply info. + * @param rcode: return code. + * @param edns: edns data of the reply. Edns input can be found here. + * @param region: region to store data. + * @return false on failure (a callback function returned an error). + */ +int inplace_cb_reply_local_call(struct module_env* env, + struct query_info* qinfo, struct module_qstate* qstate, + struct reply_info* rep, int rcode, struct edns_data* edns, + struct regional* region); + +/** + * Call the registered functions in the inplace_cb_reply linked list. + * This function is going to get called while answering with a servfail. + * @param env: module environment. + * @param qinfo: query info. + * @param qstate: module qstate. Contains the edns option lists. Could be NULL. + * @param rep: Reply info. NULL when servfail. + * @param rcode: return code. LDNS_RCODE_SERVFAIL. + * @param edns: edns data of the reply. Edns input can be found here if qstate + * is NULL. + * @param region: region to store data. + * @return false on failure (a callback function returned an error). + */ +int inplace_cb_reply_servfail_call(struct module_env* env, + struct query_info* qinfo, struct module_qstate* qstate, + struct reply_info* rep, int rcode, struct edns_data* edns, + struct regional* region); + +/** + * Call the registered functions in the inplace_cb_query linked list. + * This function is going to get called just before sending a query to a + * nameserver. + * @param env: module environment. + * @param qinfo: query info. + * @param flags: flags of the query. + * @param addr: to which server to send the query. + * @param addrlen: length of addr. + * @param zone: name of the zone of the delegation point. wireformat dname. + * This is the delegation point name for which the server is deemed + * authoritative. + * @param zonelen: length of zone. + * @param qstate: module qstate. + * @param region: region to store data. + * @return false on failure (a callback function returned an error). + */ +int inplace_cb_query_call(struct module_env* env, struct query_info* qinfo, + uint16_t flags, struct sockaddr_storage* addr, socklen_t addrlen, + uint8_t* zone, size_t zonelen, struct module_qstate* qstate, + struct regional* region); /** * Copy edns option list allocated to the new region diff --git a/util/fptr_wlist.c b/util/fptr_wlist.c index 581940d89..0d1cae40f 100644 --- a/util/fptr_wlist.c +++ b/util/fptr_wlist.c @@ -267,11 +267,9 @@ fptr_whitelist_hash_markdelfunc(lruhash_markdelfunc_t fptr) /** whitelist env->send_query callbacks */ int fptr_whitelist_modenv_send_query(struct outbound_entry* (*fptr)( - uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, - uint16_t flags, int dnssec, int want_dnssec, int nocaps, - struct edns_option* opt_list, struct sockaddr_storage* addr, - socklen_t addrlen, uint8_t* zone, size_t zonelen, int ssl_upstream, - struct module_qstate* q)) + struct query_info* qinfo, uint16_t flags, int dnssec, int want_dnssec, + int nocaps, struct sockaddr_storage* addr, socklen_t addrlen, + uint8_t* zone, size_t zonelen, int ssl_upstream, struct module_qstate* q)) { if(fptr == &worker_send_query) return 1; else if(fptr == &libworker_send_query) return 1; @@ -434,3 +432,31 @@ int fptr_whitelist_print_func(void (*fptr)(char*,void*)) else if(fptr == &remote_get_opt_ssl) return 1; return 0; } + +int fptr_whitelist_inplace_cb_reply_generic(inplace_cb_reply_func_t* fptr, + enum inplace_cb_list_type type) +{ + if(type == inplace_cb_reply) { +#ifdef WITH_PYTHONMODULE + if(fptr == &python_inplace_cb_reply_generic) return 1; +#endif + } else if(type == inplace_cb_reply_cache) { +#ifdef WITH_PYTHONMODULE + if(fptr == &python_inplace_cb_reply_generic) return 1; +#endif + } else if(type == inplace_cb_reply_local) { +#ifdef WITH_PYTHONMODULE + if(fptr == &python_inplace_cb_reply_generic) return 1; +#endif + } else if(type == inplace_cb_reply_servfail) { +#ifdef WITH_PYTHONMODULE + if(fptr == &python_inplace_cb_reply_generic) return 1; +#endif + } + return 0; +} + +int fptr_whitelist_inplace_cb_query(inplace_cb_query_func_t* fptr) +{ + return 0; +} diff --git a/util/fptr_wlist.h b/util/fptr_wlist.h index aba5f8f92..57eec99a8 100644 --- a/util/fptr_wlist.h +++ b/util/fptr_wlist.h @@ -210,11 +210,9 @@ int fptr_whitelist_hash_markdelfunc(lruhash_markdelfunc_t fptr); * @return false if not in whitelist. */ int fptr_whitelist_modenv_send_query(struct outbound_entry* (*fptr)( - uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, - uint16_t flags, int dnssec, int want_dnssec, int nocaps, - struct edns_option*, struct sockaddr_storage* addr, socklen_t addrlen, - uint8_t* zone, size_t zonelen, int ssl_upstream, - struct module_qstate* q)); + struct query_info* qinfo, uint16_t flags, int dnssec, int want_dnssec, + int nocaps, struct sockaddr_storage* addr, socklen_t addrlen, + uint8_t* zone, size_t zonelen, int ssl_upstream, struct module_qstate* q)); /** * Check function pointer whitelist for module_env detach_subs callback values. @@ -335,6 +333,24 @@ int fptr_whitelist_mesh_cb(mesh_cb_func_t fptr); */ int fptr_whitelist_print_func(void (*fptr)(char*,void*)); +/** + * Check function pointer whitelist for inplace_cb_reply, + * inplace_cb_reply_cache, inplace_cb_reply_local and inplace_cb_reply_servfail + * func values. + * @param fptr: function pointer to check. + * @param type: the type of the callback function. + * @return false if not in whitelist. + */ +int fptr_whitelist_inplace_cb_reply_generic(inplace_cb_reply_func_t* fptr, + enum inplace_cb_list_type type); + +/** + * Check function pointer whitelist for inplace_cb_query func values. + * @param fptr: function pointer to check. + * @return false if not in whitelist. + */ +int fptr_whitelist_inplace_cb_query(inplace_cb_query_func_t* fptr); + /** Due to module breakage by fptr wlist, these test app declarations * are presented here */ /** diff --git a/util/module.c b/util/module.c index 09e276c30..9c3651dd6 100644 --- a/util/module.c +++ b/util/module.c @@ -39,6 +39,7 @@ #include "config.h" #include "util/module.h" +#include "sldns/wire2str.h" const char* strextstate(enum module_ext_state s) @@ -69,3 +70,256 @@ strmodulevent(enum module_ev e) } return "bad_event_value"; } + +int +edns_known_options_init(struct module_env* env) +{ + env->edns_known_options_num = 0; + env->edns_known_options = (struct edns_known_option*)calloc( + MAX_KNOWN_EDNS_OPTS, sizeof(struct edns_known_option)); + if(!env->edns_known_options) return 0; + return 1; +} + +void +edns_known_options_delete(struct module_env* env) +{ + free(env->edns_known_options); + env->edns_known_options = NULL; + env->edns_known_options_num = 0; +} + +int +edns_register_option(uint16_t opt_code, int bypass_cache_stage, + int no_aggregation, struct module_env* env) +{ + int i; + if(env->worker) { + log_err("invalid edns registration: " + "trying to register option after module init phase"); + return 0; + } + + /** + * Checking if we are full first is faster but it does not provide + * the option to change the flags when the array is full. + * It only impacts unbound initialization, leave it for now. + */ + /* Check if the option is already registered. */ + for(i=0; iedns_known_options_num; i++) + if(env->edns_known_options[i].opt_code == opt_code) + break; + /* If it is not yet registered check if we have space to add a new one. */ + if(i == env->edns_known_options_num) { + if(env->edns_known_options_num >= MAX_KNOWN_EDNS_OPTS) { + log_err("invalid edns registration: maximum options reached"); + return 0; + } + env->edns_known_options_num++; + } + env->edns_known_options[i].opt_code = opt_code; + env->edns_known_options[i].bypass_cache_stage = bypass_cache_stage; + env->edns_known_options[i].no_aggregation = no_aggregation; + return 1; +} + +static int +inplace_cb_reply_register_generic(inplace_cb_reply_func_t* cb, + enum inplace_cb_list_type type, void* cb_arg, struct module_env* env) +{ + struct inplace_cb_reply* callback; + struct inplace_cb_reply** prevp; + if(env->worker) { + log_err("invalid edns callback registration: " + "trying to register callback after module init phase"); + return 0; + } + + callback = (struct inplace_cb_reply*)calloc(1, sizeof(*callback)); + if(callback == NULL) { + log_err("out of memory during edns callback registration."); + return 0; + } + callback->next = NULL; + callback->cb = cb; + callback->cb_arg = cb_arg; + + prevp = (struct inplace_cb_reply**) &env->inplace_cb_lists[type]; + /* append at end of list */ + while(*prevp != NULL) + prevp = &((*prevp)->next); + *prevp = callback; + return 1; +} + +int +inplace_cb_reply_register(inplace_cb_reply_func_t* cb, void* cb_arg, + struct module_env* env) +{ + return inplace_cb_reply_register_generic(cb, inplace_cb_reply, cb_arg, + env); +} + +int +inplace_cb_reply_cache_register(inplace_cb_reply_func_t* cb, void* cb_arg, + struct module_env* env) +{ + return inplace_cb_reply_register_generic(cb, inplace_cb_reply_cache, + cb_arg, env); +} + +int +inplace_cb_reply_local_register(inplace_cb_reply_func_t* cb, void* cb_arg, + struct module_env* env) +{ + return inplace_cb_reply_register_generic(cb, inplace_cb_reply_local, + cb_arg, env); +} + +int +inplace_cb_reply_servfail_register(inplace_cb_reply_func_t* cb, void* cb_arg, + struct module_env* env) +{ + return inplace_cb_reply_register_generic(cb, inplace_cb_reply_servfail, + cb_arg, env); +} + +static void +inplace_cb_reply_delete_generic(struct module_env* env, + enum inplace_cb_list_type type) +{ + struct inplace_cb_reply* curr = env->inplace_cb_lists[type]; + struct inplace_cb_reply* tmp; + /* delete list */ + while(curr) { + tmp = curr->next; + free(curr); + curr = tmp; + } + /* update head pointer */ + env->inplace_cb_lists[type] = NULL; +} + +void inplace_cb_reply_delete(struct module_env* env) +{ + inplace_cb_reply_delete_generic(env, inplace_cb_reply); +} + +void inplace_cb_reply_cache_delete(struct module_env* env) +{ + inplace_cb_reply_delete_generic(env, inplace_cb_reply_cache); +} + +void inplace_cb_reply_servfail_delete(struct module_env* env) +{ + inplace_cb_reply_delete_generic(env, inplace_cb_reply_servfail); +} + +int +inplace_cb_query_register(inplace_cb_query_func_t* cb, void* cb_arg, + struct module_env* env) +{ + struct inplace_cb_query* callback; + struct inplace_cb_query** prevp; + if(env->worker) { + log_err("invalid edns callback registration: " + "trying to register callback after module init phase"); + return 0; + } + + callback = (struct inplace_cb_query*)calloc(1, sizeof(*callback)); + if(callback == NULL) { + log_err("out of memory during edns callback registration."); + return 0; + } + callback->next = NULL; + callback->cb = cb; + callback->cb_arg = cb_arg; + + prevp = (struct inplace_cb_query**) + &env->inplace_cb_lists[inplace_cb_query]; + /* append at end of list */ + while(*prevp != NULL) + prevp = &((*prevp)->next); + *prevp = callback; + return 1; +} + +void +inplace_cb_query_delete(struct module_env* env) +{ + struct inplace_cb_query* curr = env->inplace_cb_lists[inplace_cb_query]; + struct inplace_cb_query* tmp; + /* delete list */ + while(curr) { + tmp = curr->next; + free(curr); + curr = tmp; + } + /* update head pointer */ + env->inplace_cb_lists[inplace_cb_query] = NULL; +} + +void +inplace_cb_lists_delete(struct module_env* env) +{ + inplace_cb_reply_delete(env); + inplace_cb_reply_cache_delete(env); + inplace_cb_reply_servfail_delete(env); + inplace_cb_query_delete(env); +} + +struct edns_known_option* +edns_option_is_known(uint16_t opt_code, struct module_env* env) +{ + int i; + for(i=0; iedns_known_options_num; i++) + if(env->edns_known_options[i].opt_code == opt_code) + return env->edns_known_options + i; + return NULL; +} + +int +edns_bypass_cache_stage(struct edns_option* list, struct module_env* env) +{ + int i; + for(; list; list=list->next) + for(i=0; iedns_known_options_num; i++) + if(env->edns_known_options[i].opt_code == list->opt_code && + env->edns_known_options[i].bypass_cache_stage == 1) + return 1; + return 0; +} + +int +edns_unique_mesh_state(struct edns_option* list, struct module_env* env) +{ + int i; + for(; list; list=list->next) + for(i=0; iedns_known_options_num; i++) + if(env->edns_known_options[i].opt_code == list->opt_code && + env->edns_known_options[i].no_aggregation == 1) + return 1; + return 0; +} + +void +log_edns_known_options(enum verbosity_value level, struct module_env* env) +{ + int i; + char str[32], *s; + size_t slen; + if(env->edns_known_options_num > 0 && verbosity >= level) { + verbose(level, "EDNS known options:"); + verbose(level, " Code: Bypass_cache_stage: Aggregate_mesh:"); + for(i=0; iedns_known_options_num; i++) { + s = str; + slen = sizeof(str); + (void)sldns_wire2str_edns_option_code_print(&s, &slen, + env->edns_known_options[i].opt_code); + verbose(level, " %-8.8s %-19s %-15s", str, + env->edns_known_options[i].bypass_cache_stage?"YES":"NO", + env->edns_known_options[i].no_aggregation?"NO":"YES"); + } + } +} diff --git a/util/module.h b/util/module.h index 97dc4c717..0e27e6a56 100644 --- a/util/module.h +++ b/util/module.h @@ -178,6 +178,115 @@ struct iter_hints; /** Maximum number of modules in operation */ #define MAX_MODULE 5 +/** Maximum number of known edns options */ +#define MAX_KNOWN_EDNS_OPTS 256 + +enum inplace_cb_list_type { + /* Inplace callbacks for when a resolved reply is ready to be sent to the + * front.*/ + inplace_cb_reply = 0, + /* Inplace callbacks for when a reply is given from the cache. */ + inplace_cb_reply_cache, + /* Inplace callbacks for when a reply is given with local data + * (or Chaos reply). */ + inplace_cb_reply_local, + /* Inplace callbacks for when the reply is servfail. */ + inplace_cb_reply_servfail, + /* Inplace callbacks for when a query is ready to be sent to the back.*/ + inplace_cb_query, + /* Total number of types. Used for array initialization. + * Should always be last. */ + inplace_cb_types_total +}; + + +/** Known edns option. Can be populated during modules' init. */ +struct edns_known_option { + /** type of this edns option */ + uint16_t opt_code; + /** whether the option needs to bypass the cache stage */ + int bypass_cache_stage; + /** whether the option needs mesh aggregation */ + int no_aggregation; +}; + +/** + * Inplace callback function called before replying. + * Called as func(edns, qstate, opt_list_out, qinfo, reply_info, rcode, + * region, python_callback) + * Where: + * qinfo: the query info. + * qstate: the module state. NULL when calling before the query reaches the + * mesh states. + * rep: reply_info. Could be NULL. + * rcode: the return code. + * edns: the edns_data of the reply. When qstate is NULL, it is also used as + * the edns input. + * opt_list_out: the edns options list for the reply. + * region: region to store data. + * python_callback: only used for registering a python callback function. + */ +typedef int inplace_cb_reply_func_t(struct query_info* qinfo, + struct module_qstate* qstate, struct reply_info* rep, int rcode, + struct edns_data* edns, struct edns_option** opt_list_out, + struct regional* region, void* python_callback); + +/** + * Inplace callback list of registered routines to be called before replying + * with a resolved query. + */ +struct inplace_cb_reply { + /** next in list */ + struct inplace_cb_reply* next; + /** + * Inplace callback routine for cache stage response. + * called as cb(qinfo, qstate, qinfo, reply_info, rcode, edns, + * opt_list_out, region, python_callback); + * python_callback is only used for registering a python callback function. + */ + inplace_cb_reply_func_t* cb; + void* cb_arg; +}; + +/** + * Inplace callback function called before sending the query to a nameserver. + * Called as func(qinfo, flags, qstate, addr, addrlen, zone, zonelen, region, + * python_callback) + * Where: + * qinfo: query info. + * flags: flags of the query. + * qstate: query state. + * addr: to which server to send the query. + * addrlen: length of addr. + * zone: name of the zone of the delegation point. wireformat dname. + * This is the delegation point name for which the server is deemed + * authoritative. + * zonelen: length of zone. + * region: region to store data. + * python_callback: only used for registering a python callback function. + */ +typedef int inplace_cb_query_func_t(struct query_info* qinfo, uint16_t flags, + struct module_qstate* qstate, struct sockaddr_storage* addr, + socklen_t addrlen, uint8_t* zone, size_t zonelen, struct regional* region, + void* python_callback); + +/** + * Inplace callback list of registered routines to be called before quering a + * nameserver. + */ +struct inplace_cb_query { + /** next in list */ + struct inplace_cb_query* next; + /** + * Inplace callback routine for cache stage response. + * called as cb(qinfo, flags, qstate, addr, addrlen, zone, zonelen, + * region, python_callback); + * python_callback is only used for registering a python callback function. + */ + inplace_cb_query_func_t* cb; + void* cb_arg; +}; + /** * Module environment. * Services and data provided to the module. @@ -202,10 +311,7 @@ struct module_env { * will cause operate() to be called with event timeout or reply. * The time until a timeout is calculated from roundtrip timing, * several UDP retries are attempted. - * @param qname: query name. (host order) - * @param qnamelen: length in bytes of qname, including trailing 0. - * @param qtype: query type. (host order) - * @param qclass: query class. (host order) + * @param qinfo: query info. * @param flags: host order flags word, with opcode and CD bit. * @param dnssec: if set, EDNS record will have bits set. * If EDNS_DO bit is set, DO bit is set in EDNS records. @@ -214,8 +320,6 @@ struct module_env { * EDNS, the answer is likely to be useless for this domain. * @param nocaps: do not use caps_for_id, use the qname as given. * (ignored if caps_for_id is disabled). - * @param opt_list: set these EDNS options on the outgoing packet. - * or NULL if none (the list is deep-copied). * @param addr: where to. * @param addrlen: length of addr. * @param zone: delegation point name. @@ -227,9 +331,8 @@ struct module_env { * This outbound_entry will be used on later module invocations * that involve this query (timeout, error or reply). */ - struct outbound_entry* (*send_query)(uint8_t* qname, size_t qnamelen, - uint16_t qtype, uint16_t qclass, uint16_t flags, int dnssec, - int want_dnssec, int nocaps, struct edns_option* opt_list, + struct outbound_entry* (*send_query)(struct query_info* qinfo, + uint16_t flags, int dnssec, int want_dnssec, int nocaps, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, size_t zonelen, int ssl_upstream, struct module_qstate* q); @@ -337,6 +440,17 @@ struct module_env { struct iter_hints* hints; /** module specific data. indexed by module id. */ void* modinfo[MAX_MODULE]; + + /* Shared linked list of inplace callback functions */ + void* inplace_cb_lists[inplace_cb_types_total]; + + /** + * Shared array of known edns options (size MAX_KNOWN_EDNS_OPTS). + * Filled by edns literate modules during init. + */ + struct edns_known_option* edns_known_options; + /* Number of known edns options */ + size_t edns_known_options_num; }; /** @@ -435,6 +549,19 @@ struct module_qstate { struct mesh_state* mesh_info; /** how many seconds before expiry is this prefetched (0 if not) */ time_t prefetch_leeway; + + /** incoming edns options from the front end */ + struct edns_option* edns_opts_front_in; + /** outgoing edns options to the back end */ + struct edns_option* edns_opts_back_out; + /** incoming edns options from the back end */ + struct edns_option* edns_opts_back_in; + /** outgoing edns options to the front end */ + struct edns_option* edns_opts_front_out; + /** whether modules should answer from the cache */ + int no_cache_lookup; + /** whether modules should store answer in the cache */ + int no_cache_store; }; /** @@ -524,4 +651,156 @@ const char* strextstate(enum module_ext_state s); */ const char* strmodulevent(enum module_ev e); +/** + * Initialize the edns known options by allocating the required space. + * @param env: the module environment. + * @return false on failure (no memory). + */ +int edns_known_options_init(struct module_env* env); + +/** + * Free the allocated space for the known edns options. + * @param env: the module environment. + */ +void edns_known_options_delete(struct module_env* env); + +/** + * Register a known edns option. Overwrite the flags if it is already + * registered. Used before creating workers to register known edns options. + * @param opt_code: the edns option code. + * @param bypass_cache_stage: whether the option interacts with the cache. + * @param no_aggregation: whether the option implies more specific + * aggregation. + * @param env: the module environment. + * @return true on success, false on failure (registering more options than + * allowed or trying to register after the environment is copied to the + * threads.) + */ +int edns_register_option(uint16_t opt_code, int bypass_cache_stage, + int no_aggregation, struct module_env* env); + +/** + * Register an inplace callback function called before replying with a resolved + * query. + * @param cb: pointer to the callback function. + * @param cb_arg: optional argument for the callback function. + * @param env: the module environment. + * @return true on success, false on failure (out of memory or trying to + * register after the environment is copied to the threads.) + */ +int inplace_cb_reply_register(inplace_cb_reply_func_t* cb, void* cb_arg, + struct module_env* env); + +/** + * Register an inplace callback function called before replying from the cache. + * @param cb: pointer to the callback function. + * @param cb_arg: optional argument for the callback function. + * @param env: the module environment. + * @return true on success, false on failure (out of memory or trying to + * register after the environment is copied to the threads.) + */ +int inplace_cb_reply_cache_register(inplace_cb_reply_func_t* cb, void* cb_arg, + struct module_env* env); + +/** + * Register an inplace callback function called before replying with local + * data or Chaos reply. + * @param cb: pointer to the callback function. + * @param cb_arg: optional argument for the callback function. + * @param env: the module environment. + * @return true on success, false on failure (out of memory or trying to + * register after the environment is copied to the threads.) + */ +int inplace_cb_reply_local_register(inplace_cb_reply_func_t* cb, void* cb_arg, + struct module_env* env); + +/** + * Register an inplace callback function called before replying with servfail. + * @param cb: pointer to the callback function. + * @param cb_arg: optional argument for the callback function. + * @param env: the module environment. + * @return true on success, false on failure (out of memory or trying to + * register after the environment is copied to the threads.) + */ +int inplace_cb_reply_servfail_register(inplace_cb_reply_func_t* cb, + void* cb_arg, struct module_env* env); + +/** + * Delete the inplace_cb_reply callback linked list. + * @param env: the module environment. + */ +void inplace_cb_reply_delete(struct module_env* env); + +/** + * Delete the inplace_cb_reply_cache callback linked list. + * @param env: the module environment. + */ +void inplace_cb_reply_cache_delete(struct module_env* env); + +/** + * Delete the inplace_cb_reply_servfail callback linked list. + * @param env: the module environment. + */ +void inplace_cb_reply_servfail_delete(struct module_env* env); + +/** + * Register an inplace callback function called before quering a nameserver. + * @param cb: pointer to the callback function. + * @param cb_arg: optional argument for the callback function. + * @param env: the module environment. + * @return true on success, false on failure (out of memory or trying to + * register after the environment is copied to the threads.) + */ +int inplace_cb_query_register(inplace_cb_query_func_t* cb, void* cb_arg, + struct module_env* env); + +/** + * Delete the inplace_cb_query callback linked list. + * @param env: the module environment. + */ +void inplace_cb_query_delete(struct module_env* env); + +/** + * Delete all the inplace callback linked lists. + * @param env: the module environment. + */ +void inplace_cb_lists_delete(struct module_env* env); + +/** + * Check if an edns option is known. + * @param opt_code: the edns option code. + * @param env: the module environment. + * @return pointer to registered option if the edns option is known, + * NULL otherwise. + */ +struct edns_known_option* edns_option_is_known(uint16_t opt_code, + struct module_env* env); + +/** + * Check if an edns option needs to bypass the reply from cache stage. + * @param list: the edns options. + * @param env: the module environment. + * @return true if an edns option needs to bypass the cache stage, + * false otherwise. + */ +int edns_bypass_cache_stage(struct edns_option* list, + struct module_env* env); + +/** + * Check if an edns option needs a unique mesh state. + * @param list: the edns options. + * @param env: the module environment. + * @return true if an edns option needs a unique mesh state, + * false otherwise. + */ +int edns_unique_mesh_state(struct edns_option* list, struct module_env* env); + +/** + * Log the known edns options. + * @param level: the desired verbosity level. + * @param env: the module environment. + */ +void log_edns_known_options(enum verbosity_value level, + struct module_env* env); + #endif /* UTIL_MODULE_H */ diff --git a/validator/validator.c b/validator/validator.c index 362cd2a39..676dcdfe4 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -181,6 +181,7 @@ val_init(struct module_env* env, int id) log_err("validator: could not apply configuration settings."); return 0; } + return 1; } @@ -2088,7 +2089,7 @@ processFinished(struct module_qstate* qstate, struct val_qstate* vq, } /* store results in cache */ - if(qstate->query_flags&BIT_RD) { + if(!qstate->no_cache_store && qstate->query_flags&BIT_RD) { /* if secure, this will override cache anyway, no need * to check if from parentNS */ if(!dns_cache_store(qstate->env, &vq->orig_msg->qinfo, @@ -2281,6 +2282,7 @@ val_operate(struct module_qstate* qstate, enum module_ev event, int id, (void)outbound; if(event == module_event_new || (event == module_event_pass && vq == NULL)) { + /* pass request to next module, to get it */ verbose(VERB_ALGO, "validator: pass to next module"); qstate->ext_state[id] = module_wait_module; @@ -2289,6 +2291,7 @@ val_operate(struct module_qstate* qstate, enum module_ev event, int id, if(event == module_event_moddone) { /* check if validation is needed */ verbose(VERB_ALGO, "validator: nextmodule returned"); + if(!needs_validation(qstate, qstate->return_rcode, qstate->return_msg)) { /* no need to validate this */