diff --git a/daemon/worker.c b/daemon/worker.c index e90508bc3..a2f91a218 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -826,13 +826,8 @@ worker_handle_request(struct comm_point* c, void* arg, int error, server_stats_querymiss(&worker->stats, worker); /* grab a work request structure for this new request */ - if(worker->env.mesh->all.count > worker->request_size) { - verbose(VERB_ALGO, "Too many requests active. " - "dropping incoming query."); - worker->stats.num_query_list_exceeded++; - comm_point_drop_reply(repinfo); - return 0; - } else if(worker->env.mesh->num_reply_addrs>worker->request_size*16) { + if(worker->env.mesh->num_reply_addrs>worker->request_size*16) { + /* protect our memory usage from storing reply addresses */ verbose(VERB_ALGO, "Too many requests queued. " "dropping incoming query."); worker->stats.num_query_list_exceeded++; diff --git a/doc/Changelog b/doc/Changelog index 874b19b59..61645438f 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +2 September 2008: Wouter + - DoS protection features. Queries are jostled out to make room. + - testbound can pass time, increasing the internal timer. + 1 September 2008: Wouter - disallow nonrecursive queries for cache snooping by default. You can allow is using access-control: allow_snoop. diff --git a/doc/plan b/doc/plan index a8d3b679f..f7aae9057 100644 --- a/doc/plan +++ b/doc/plan @@ -4,7 +4,7 @@ Plan for Unbound 1.1. - immediate attention: done - security issues: 1 week. - remote control: 2 week -- requested: 1 week +- improvements: 1 week - draft-mitigation: 2 week total 6 of 8 weeks; 2 weeks for maintenance activities. @@ -27,13 +27,13 @@ total 6 of 8 weeks; 2 weeks for maintenance activities. (done) *** Security issues -* current NS query retry is an option, default off, experimental on, ++ current NS query retry is an option, default off, experimental on, because of the added load to 3rd parties. -* block nonRD queries, acl like. ++ block nonRD queries, acl like. what about our authority features, those are allowed. -* DoS vector, flush more. ++ DoS vector, flush more. 50% of max is for run-to-completion - 50% rest is for lifo queue with 100 msec timeout. + 50% rest is for lifo queue with 100-200 msec timeout. * records in the additional section should not be marked bogus if they have no signer or a different signed. Validate if you can, otherwise leave unchecked. @@ -61,7 +61,7 @@ like dnswall does. Allow certain subdomains to do it, config options. * remote control to see delegation; what servers would be used to get data for a name. -*** Requested +*** Improvements * fallback to noEDNS if all queries are dropped. * dnssec lameness fixen. Check to make sure. * negative caching to avoid DS queries, NSEC, NSEC3 (w params). diff --git a/services/mesh.c b/services/mesh.c index d7ed4d9af..f9e304764 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -54,6 +54,54 @@ #include "util/timehist.h" #include "util/fptr_wlist.h" #include "util/alloc.h" +#include "util/config_file.h" + +/** subtract timers and the values do not overflow or become negative */ +static void +timeval_subtract(struct timeval* d, struct timeval* end, struct timeval* start) +{ +#ifndef S_SPLINT_S + d->tv_sec = end->tv_sec - start->tv_sec; + while(end->tv_usec < start->tv_usec) { + end->tv_usec += 1000000; + d->tv_sec--; + } + d->tv_usec = end->tv_usec - start->tv_usec; +#endif +} + +/** add timers and the values do not overflow or become negative */ +static void +timeval_add(struct timeval* d, struct timeval* add) +{ +#ifndef S_SPLINT_S + d->tv_sec += add->tv_sec; + d->tv_usec += add->tv_usec; + while(d->tv_usec > 1000000 ) { + d->tv_usec -= 1000000; + d->tv_sec++; + } +#endif +} + +/** divide sum of timers to get average */ +static void +timeval_divide(struct timeval* avg, struct timeval* sum, size_t d) +{ +#ifndef S_SPLINT_S + size_t leftover; + if(d == 0) { + avg->tv_sec = 0; + avg->tv_usec = 0; + return; + } + avg->tv_sec = sum->tv_sec / d; + avg->tv_usec = sum->tv_usec / d; + /* handle fraction from seconds divide */ + leftover = sum->tv_sec - avg->tv_sec*d; + avg->tv_usec += (leftover*1000000)/d; +#endif +} int mesh_state_compare(const void* ap, const void* bp) @@ -108,6 +156,11 @@ mesh_create(struct module_stack* stack, struct module_env* env) mesh->num_reply_addrs = 0; mesh->num_reply_states = 0; mesh->num_detached_states = 0; + mesh->num_forever_states = 0; + mesh->stats_jostled = 0; + mesh->stats_dropped = 0; + mesh->max_reply_states = env->cfg->num_queries_per_thread; + mesh->max_forever_states = (mesh->max_reply_states+1)/2; return mesh; } @@ -130,6 +183,42 @@ mesh_delete(struct mesh_area* mesh) free(mesh); } +int mesh_make_new_space(struct mesh_area* mesh) +{ + struct mesh_state* m = mesh->jostle_last; + /* free space is available */ + if(mesh->num_reply_states < mesh->max_reply_states) + return 1; + /* try to kick out a jostle-list item */ + if(m && m->reply_list && m->list_select == mesh_jostle_list) { + /* how old is it? */ + struct timeval age; + timeval_subtract(&age, mesh->env->now_tv, + &m->reply_list->start_time); +#ifndef S_SPLINT_S + if(age.tv_sec > 0 || age.tv_usec > MESH_JOSTLE_USEC) { + /* its a goner */ + log_nametypeclass(VERB_ALGO, "query jostled out to " + "make space for a new one", + m->s.qinfo.qname, m->s.qinfo.qtype, + m->s.qinfo.qclass); + /* notify supers */ + if(m->super_set.count > 0) { + verbose(VERB_ALGO, "notify supers of failure"); + m->s.return_msg = NULL; + m->s.return_rcode = LDNS_RCODE_SERVFAIL; + mesh_walk_supers(mesh, m); + } + mesh->stats_jostled ++; + mesh_state_delete(&m->s); + return 1; + } +#endif + } + /* no space for new item */ + return 0; +} + 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) @@ -138,6 +227,16 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, int was_detached = 0; int was_noreply = 0; int added = 0; + /* does this create a new reply state? */ + if(!s || s->list_select == mesh_no_list) { + if(!mesh_make_new_space(mesh)) { + verbose(VERB_ALGO, "Too many queries. dropping " + "incoming query."); + comm_point_drop_reply(rep); + mesh->stats_dropped ++; + return; + } + } /* see if it already exists, if not, create one */ if(!s) { struct rbnode_t* n; @@ -178,6 +277,19 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, mesh->num_reply_states ++; } mesh->num_reply_addrs++; + if(s->list_select == mesh_no_list) { + /* move to either the forever or the jostle_list */ + if(mesh->num_forever_states < mesh->max_forever_states) { + mesh->num_forever_states ++; + mesh_list_insert(s, &mesh->forever_first, + &mesh->forever_last); + s->list_select = mesh_forever_list; + } else { + mesh_list_insert(s, &mesh->jostle_first, + &mesh->jostle_last); + s->list_select = mesh_jostle_list; + } + } if(added) mesh_run(mesh, s, module_event_new, NULL); } @@ -191,6 +303,8 @@ mesh_new_callback(struct mesh_area* mesh, struct query_info* qinfo, int was_detached = 0; int was_noreply = 0; int added = 0; + /* there are no limits on the number of callbacks */ + /* see if it already exists, if not, create one */ if(!s) { struct rbnode_t* n; @@ -257,6 +371,7 @@ mesh_state_create(struct module_env* env, struct query_info* qinfo, mstate->node.key = mstate; mstate->run_node.key = mstate; mstate->reply_list = NULL; + mstate->list_select = mesh_no_list; rbtree_init(&mstate->super_set, &mesh_state_ref_compare); rbtree_init(&mstate->sub_set, &mesh_state_ref_compare); mstate->num_activated = 0; @@ -317,6 +432,14 @@ mesh_state_delete(struct module_qstate* qstate) mstate = qstate->mesh_info; mesh = mstate->s.env->mesh; mesh_detach_subs(&mstate->s); + if(mstate->list_select == mesh_forever_list) { + mesh->num_forever_states --; + mesh_list_remove(mstate, &mesh->forever_first, + &mesh->forever_last); + } else if(mstate->list_select == mesh_jostle_list) { + mesh_list_remove(mstate, &mesh->jostle_first, + &mesh->jostle_last); + } if(!mstate->reply_list && !mstate->cb_list && mstate->super_set.count == 0) { log_assert(mesh->num_detached_states > 0); @@ -414,53 +537,6 @@ int mesh_state_attachment(struct mesh_state* super, struct mesh_state* sub) return 1; } -/** subtract timers and the values do not overflow or become negative */ -static void -timeval_subtract(struct timeval* d, struct timeval* end, struct timeval* start) -{ -#ifndef S_SPLINT_S - d->tv_sec = end->tv_sec - start->tv_sec; - while(end->tv_usec < start->tv_usec) { - end->tv_usec += 1000000; - d->tv_sec--; - } - d->tv_usec = end->tv_usec - start->tv_usec; -#endif -} - -/** add timers and the values do not overflow or become negative */ -static void -timeval_add(struct timeval* d, struct timeval* add) -{ -#ifndef S_SPLINT_S - d->tv_sec += add->tv_sec; - d->tv_usec += add->tv_usec; - while(d->tv_usec > 1000000 ) { - d->tv_usec -= 1000000; - d->tv_sec++; - } -#endif -} - -/** divide sum of timers to get average */ -static void -timeval_divide(struct timeval* avg, struct timeval* sum, size_t d) -{ -#ifndef S_SPLINT_S - size_t leftover; - if(d == 0) { - avg->tv_sec = 0; - avg->tv_usec = 0; - return; - } - avg->tv_sec = sum->tv_sec / d; - avg->tv_usec = sum->tv_usec / d; - /* handle fraction from seconds divide */ - leftover = sum->tv_sec - avg->tv_sec*d; - avg->tv_usec += (leftover*1000000)/d; -#endif -} - /** * callback results to mesh cb entry * @param m: mesh state to send it for. @@ -785,11 +861,14 @@ mesh_stats(struct mesh_area* mesh, const char* str) { verbose(VERB_DETAIL, "%s %u recursion states (%u with reply, " "%u detached), %u waiting replies, %u recursion replies " - "sent", str, (unsigned)mesh->all.count, + "sent, %d replies dropped, %d states jostled out", + str, (unsigned)mesh->all.count, (unsigned)mesh->num_reply_states, (unsigned)mesh->num_detached_states, (unsigned)mesh->num_reply_addrs, - (unsigned)mesh->replies_sent); + (unsigned)mesh->replies_sent, + (unsigned)mesh->stats_dropped, + (unsigned)mesh->stats_jostled); if(mesh->replies_sent > 0) { struct timeval avg; timeval_divide(&avg, &mesh->replies_sum_wait, @@ -809,6 +888,8 @@ mesh_stats_clear(struct mesh_area* mesh) mesh->replies_sent = 0; mesh->replies_sum_wait.tv_sec = 0; mesh->replies_sum_wait.tv_usec = 0; + mesh->stats_jostled = 0; + mesh->stats_dropped = 0; timehist_clear(mesh->histogram); } @@ -850,3 +931,26 @@ mesh_detect_cycle(struct module_qstate* qstate, struct query_info* qinfo, return 1; return 0; } + +void mesh_list_insert(struct mesh_state* m, struct mesh_state** fp, + struct mesh_state** lp) +{ + /* insert as last element */ + m->prev = *lp; + m->next = NULL; + if(*lp) + (*lp)->next = m; + else *fp = m; + *lp = m; +} + +void mesh_list_remove(struct mesh_state* m, struct mesh_state** fp, + struct mesh_state** lp) +{ + if(m->next) + m->next->prev = m->prev; + else *lp = m->prev; + if(m->prev) + m->prev->next = m->next; + else *fp = m->next; +} diff --git a/services/mesh.h b/services/mesh.h index aded72703..cb3c9179e 100644 --- a/services/mesh.h +++ b/services/mesh.h @@ -65,6 +65,12 @@ struct timehist; */ #define MESH_MAX_ACTIVATION 1000 +/** + * Maximum time to live in the jostle list. usec. + * The entries older than this could be removed to make space for new ones. + */ +#define MESH_JOSTLE_USEC 200000 /* 0.200000 sec */ + /** * Mesh of query states */ @@ -89,13 +95,36 @@ struct mesh_area { * an empty set of super-states, thus are 'toplevel' or detached * internal opportunistic queries */ size_t num_detached_states; + /** number of reply states in the forever list */ + size_t num_forever_states; + /** max total number of reply states to have */ + size_t max_reply_states; + /** max forever number of reply states to have */ + size_t max_forever_states; + + /** stats, cumulative number of reply states jostled out */ + size_t stats_jostled; + /** stats, cumulative number of incoming client msgs dropped */ + size_t stats_dropped; /** number of replies sent */ size_t replies_sent; /** sum of waiting times for the replies */ struct timeval replies_sum_wait; /** histogram of time values */ struct timehist* histogram; + + /** double linked list of the run-to-completion query states. + * These are query states with a reply */ + struct mesh_state* forever_first; + /** last entry in run forever list */ + struct mesh_state* forever_last; + + /** double linked list of the query states that can be jostled out + * by new queries if too old. These are query states with a reply */ + struct mesh_state* jostle_first; + /** last entry in jostle list - this is the entry that is newest */ + struct mesh_state* jostle_last; }; /** @@ -127,6 +156,14 @@ struct mesh_state { rbtree_t sub_set; /** number of activations for the mesh state */ size_t num_activated; + + /** previous in linked list for reply states */ + struct mesh_state* prev; + /** next in linked list for reply states */ + struct mesh_state* next; + /** 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; }; /** @@ -457,4 +494,29 @@ int mesh_state_compare(const void* ap, const void* bp); /** compare two mesh references */ int mesh_state_ref_compare(const void* ap, const void* bp); +/** + * Make space for another recursion state for a reply in the mesh + * @param mesh: mesh area + * @return false if no space is available. + */ +int mesh_make_new_space(struct mesh_area* mesh); + +/** + * Insert mesh state into a double linked list. Inserted at end. + * @param m: mesh state. + * @param fp: pointer to the first-elem-pointer of the list. + * @param lp: pointer to the last-elem-pointer of the list. + */ +void mesh_list_insert(struct mesh_state* m, struct mesh_state** fp, + struct mesh_state** lp); + +/** + * Remove mesh state from a double linked list. Remove from any position. + * @param m: mesh state. + * @param fp: pointer to the first-elem-pointer of the list. + * @param lp: pointer to the last-elem-pointer of the list. + */ +void mesh_list_remove(struct mesh_state* m, struct mesh_state** fp, + struct mesh_state** lp); + #endif /* SERVICES_MESH_H */ diff --git a/testcode/fake_event.c b/testcode/fake_event.c index 70471c928..d6afae9d5 100644 --- a/testcode/fake_event.c +++ b/testcode/fake_event.c @@ -61,6 +61,20 @@ /** Global variable: the scenario. Saved here for when event_init is done. */ static struct replay_scenario* saved_scenario = NULL; +/** add timers and the values do not overflow or become negative */ +static void +timeval_add(struct timeval* d, struct timeval* add) +{ +#ifndef S_SPLINT_S + d->tv_sec += add->tv_sec; + d->tv_usec += add->tv_usec; + while(d->tv_usec > 1000000 ) { + d->tv_usec -= 1000000; + d->tv_sec++; + } +#endif +} + void fake_event_init(struct replay_scenario* scen) { @@ -98,6 +112,7 @@ repevt_string(enum replay_event_type t) case repevt_front_query: return "QUERY"; case repevt_front_reply: return "CHECK_ANSWER"; case repevt_timeout: return "TIMEOUT"; + case repevt_time_passes: return "TIME_PASSES"; case repevt_back_reply: return "REPLY"; case repevt_back_query: return "CHECK_OUT_QUERY"; case repevt_error: return "ERROR"; @@ -408,6 +423,19 @@ fake_pending_callback(struct replay_runtime* runtime, ldns_buffer_free(c.buffer); } +/** pass time */ +static void +time_passes(struct replay_runtime* runtime, struct replay_moment* mom) +{ + timeval_add(&runtime->now_tv, &mom->elapse); + runtime->now_secs = (uint32_t)runtime->now_tv.tv_sec; +#ifndef S_SPLINT_S + log_info("elapsed %d.%6.6d now %d.%6.6d", + (int)mom->elapse.tv_sec, (int)mom->elapse.tv_usec, + (int)runtime->now_tv.tv_sec, (int)runtime->now_tv.tv_usec); +#endif +} + /** * Advance to the next moment. */ @@ -471,6 +499,10 @@ do_moment_and_advance(struct replay_runtime* runtime) advance_moment(runtime); fake_pending_callback(runtime, mom, NETEVENT_CLOSED); break; + case repevt_time_passes: + time_passes(runtime, runtime->now); + advance_moment(runtime); + break; default: fatal_exit("testbound: unknown event type %d", runtime->now->evt_type); diff --git a/testcode/replay.c b/testcode/replay.c index 1de5b132e..8e3e03653 100644 --- a/testcode/replay.c +++ b/testcode/replay.c @@ -221,6 +221,8 @@ replay_moment_read(char* remain, FILE* in, const char* name, int* lineno, readentry = 1; } else if(parse_keyword(&remain, "TIMEOUT")) { mom->evt_type = repevt_timeout; + } else if(parse_keyword(&remain, "TIME_PASSES")) { + mom->evt_type = repevt_time_passes; } else if(parse_keyword(&remain, "ERROR")) { mom->evt_type = repevt_error; } else { @@ -242,6 +244,22 @@ replay_moment_read(char* remain, FILE* in, const char* name, int* lineno, return NULL; } } + if(parse_keyword(&remain, "ELAPSE")) { + double sec; + errno = 0; + sec = strtod(remain, &remain); + if(sec == 0. && errno != 0) { + log_err("line %d: could not parse ELAPSE: %s (%s)", + *lineno, remain, strerror(errno)); + free(mom); + return NULL; + } +#ifndef S_SPLINT_S + mom->elapse.tv_sec = (int)sec; + mom->elapse.tv_usec = (int)((sec - (double)mom->elapse.tv_sec) + *1000000. + 0.5); +#endif + } if(readentry) { mom->match = read_entry(in, name, lineno, ttl, or, prev); diff --git a/testcode/replay.h b/testcode/replay.h index 5873ad029..1c0478831 100644 --- a/testcode/replay.h +++ b/testcode/replay.h @@ -58,6 +58,8 @@ * o CHECK_OUT_QUERY - followed by entry (if copy-id it is also reply). * o REPLY - followed by entry * o TIMEOUT + * o TIME_PASSES ELAPSE [seconds] - increase 'now' time counter, can be + * a floating point number. * o ERROR * ; following entry starts on the next line, ENTRY_BEGIN. * ; more STEP items @@ -149,6 +151,8 @@ struct replay_moment { repevt_front_reply, /** timeout */ repevt_timeout, + /** time passes */ + repevt_time_passes, /** reply arrives from the network */ repevt_back_reply, /** test fails if query to the network does not match */ @@ -162,6 +166,9 @@ struct replay_moment { /** The sent packet must match this. Incoming events, the data. */ struct entry* match; + /** the amount of time that passes */ + struct timeval elapse; + /** address that must be matched, or packet remote host address. */ struct sockaddr_storage addr; /** length of addr, if 0, then any address will do */ diff --git a/testdata/fwd_droptoomany.rpl b/testdata/fwd_droptoomany.rpl new file mode 100644 index 000000000..26af5f3ca --- /dev/null +++ b/testdata/fwd_droptoomany.rpl @@ -0,0 +1,90 @@ +; config options go here. +server: + num-queries-per-thread: 1 +forward-zone: + name: "." + forward-addr: 216.0.0.1 +CONFIG_END +SCENARIO_BEGIN Test too many queries asked, last is dropped. + +; query responses from authority servers. +RANGE_BEGIN 0 100 +ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD RA NOERROR + SECTION QUESTION +www.example.net. IN A + SECTION ANSWER +www.example.net. IN A 10.20.30.40 + SECTION AUTHORITY +www.example.net. IN NS ns.example.net. + SECTION ADDITIONAL +ns.example.net. IN A 10.20.30.50 +ENTRY_END +RANGE_END + +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +STEP 2 CHECK_OUT_QUERY +ENTRY_BEGIN +MATCH qname qtype opcode +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +; NO REPLY (this step is not needed) +STEP 3 NOTHING + +; another query +STEP 4 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.net. IN A +ENTRY_END + +; reply from first query returns +STEP 5 REPLY +ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD RA NOERROR + SECTION QUESTION +www.example.com. IN A + SECTION ANSWER +www.example.com. IN A 10.20.30.40 + SECTION AUTHORITY +www.example.com. IN NS ns.example.com. + SECTION ADDITIONAL +ns.example.com. IN A 10.20.30.50 +ENTRY_END + +STEP 10 CHECK_ANSWER +ENTRY_BEGIN +MATCH opcode qname qtype +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. IN A 10.20.30.40 +ENTRY_END + +; This answer does not arrive, the query was dropped +;STEP 11 CHECK_ANSWER +;ENTRY_BEGIN +;MATCH opcode qname qtype +;SECTION QUESTION +;www.example.net. IN A +;SECTION ANSWER +;www.example.net. IN A 10.20.30.40 +;ENTRY_END +SCENARIO_END + +; testbound checks before exit: +; * no more pending queries outstanding. +; * and no answers that have not been checked. diff --git a/testdata/fwd_jostle.rpl b/testdata/fwd_jostle.rpl new file mode 100644 index 000000000..14e5e8635 --- /dev/null +++ b/testdata/fwd_jostle.rpl @@ -0,0 +1,110 @@ +; config options go here. +; This is one forever, one jostle. +server: + num-queries-per-thread: 2 +forward-zone: + name: "." + forward-addr: 216.0.0.1 +CONFIG_END +SCENARIO_BEGIN Test too many queries asked, last is too recent to be jostled + +; fill the forever slot. +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +STEP 2 CHECK_OUT_QUERY +ENTRY_BEGIN +MATCH qname qtype opcode +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +; NO REPLY (this step is not needed) +STEP 3 NOTHING + +;something enters the jostle slot. +STEP 4 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.net. IN A +ENTRY_END + +STEP 5 CHECK_OUT_QUERY +ENTRY_BEGIN +MATCH qname qtype opcode +SECTION QUESTION +www.example.net. IN A +ENTRY_END + +; something else tries to replace the entry in the jostle slot. +; but the entry in the jostle slot is too recent. +STEP 6 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.org. IN A +ENTRY_END + +; reply from latest query returns +STEP 7 REPLY +ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD RA NOERROR + SECTION QUESTION +www.example.net. IN A + SECTION ANSWER +www.example.net. IN A 10.20.30.42 + SECTION AUTHORITY +www.example.net. IN NS ns.example.net. + SECTION ADDITIONAL +ns.example.net. IN A 10.20.30.50 +ENTRY_END + +; answer to last query +STEP 8 CHECK_ANSWER +ENTRY_BEGIN +MATCH opcode qname qtype +SECTION QUESTION +www.example.net. IN A +SECTION ANSWER +www.example.net. IN A 10.20.30.42 +ENTRY_END + + +; reply from first query returns +STEP 10 REPLY +ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD RA NOERROR + SECTION QUESTION +www.example.com. IN A + SECTION ANSWER +www.example.com. IN A 10.20.30.40 + SECTION AUTHORITY +www.example.com. IN NS ns.example.com. + SECTION ADDITIONAL +ns.example.com. IN A 10.20.30.50 +ENTRY_END + +; answer to first query +STEP 11 CHECK_ANSWER +ENTRY_BEGIN +MATCH opcode qname qtype +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. IN A 10.20.30.40 +ENTRY_END + +SCENARIO_END + +; testbound checks before exit: +; * no more pending queries outstanding. +; * and no answers that have not been checked. diff --git a/testdata/fwd_jostle_out.rpl b/testdata/fwd_jostle_out.rpl new file mode 100644 index 000000000..7e01d9e43 --- /dev/null +++ b/testdata/fwd_jostle_out.rpl @@ -0,0 +1,113 @@ +; config options go here. +; This is one forever, one jostle. +server: + num-queries-per-thread: 2 +forward-zone: + name: "." + forward-addr: 216.0.0.1 +CONFIG_END +SCENARIO_BEGIN Test too many queries asked, last one jostled out to make space + +; fill the forever slot. +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +STEP 2 CHECK_OUT_QUERY +ENTRY_BEGIN +MATCH qname qtype opcode +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +; NO REPLY (this step is not needed) +STEP 3 NOTHING + +;something enters the jostle slot. +STEP 4 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.net. IN A +ENTRY_END + +STEP 5 CHECK_OUT_QUERY +ENTRY_BEGIN +MATCH qname qtype opcode +SECTION QUESTION +www.example.net. IN A +ENTRY_END + +; 300 msec passes +STEP 6 TIME_PASSES ELAPSE 0.300 + +; something else tries to replace the entry in the jostle slot. +; and it works because the entry is now too old. +STEP 8 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.org. IN A +ENTRY_END + +; reply from latest query returns +STEP 9 REPLY +ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD RA NOERROR + SECTION QUESTION +www.example.org. IN A + SECTION ANSWER +www.example.org. IN A 10.20.30.42 + SECTION AUTHORITY +www.example.org. IN NS ns.example.org. + SECTION ADDITIONAL +ns.example.org. IN A 10.20.30.50 +ENTRY_END + +; answer to last query +STEP 10 CHECK_ANSWER +ENTRY_BEGIN +MATCH opcode qname qtype +SECTION QUESTION +www.example.org. IN A +SECTION ANSWER +www.example.org. IN A 10.20.30.42 +ENTRY_END + + +; reply from first query returns +STEP 11 REPLY +ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR RD RA NOERROR + SECTION QUESTION +www.example.com. IN A + SECTION ANSWER +www.example.com. IN A 10.20.30.40 + SECTION AUTHORITY +www.example.com. IN NS ns.example.com. + SECTION ADDITIONAL +ns.example.com. IN A 10.20.30.50 +ENTRY_END + +; answer to first query +STEP 12 CHECK_ANSWER +ENTRY_BEGIN +MATCH opcode qname qtype +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. IN A 10.20.30.40 +ENTRY_END + +SCENARIO_END + +; testbound checks before exit: +; * no more pending queries outstanding. +; * and no answers that have not been checked. diff --git a/testdata/fwd_two.rpl b/testdata/fwd_two.rpl index 5243e331f..ca4d0658e 100644 --- a/testdata/fwd_two.rpl +++ b/testdata/fwd_two.rpl @@ -1,6 +1,6 @@ ; config options go here. server: - num-queries-per-thread: 1 + num-queries-per-thread: 2 forward-zone: name: "." forward-addr: 216.0.0.1