/*
* testcode/fake_event.c - fake event handling that replays existing scenario.
*
* Copyright (c) 2007, NLnet Labs. All rights reserved.
*
* 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 NLNET LABS 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 COPYRIGHT
* HOLDER 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.
*/
/**
* \file
* Event service that replays a scenario.
* This implements the same exported symbols as the files:
* util/netevent.c
* services/listen_dnsport.c
* services/outside_network.c
* But these do not actually access the network or events, instead
* the scenario is played.
*/
/** unique code to check that fake_commpoint is that structure */
#define FAKE_COMMPOINT_TYPECODE 97347923
/** fake commpoint, stores information */
struct fake_commpoint {
/** typecode */
int typecode;
/** if this is a udp outgoing type of commpoint */
int type_udp_out;
/** if this is a tcp outgoing type of commpoint */
int type_tcp_out;
/** if this is a http outgoing type of commpoint. */
int type_http_out;
/** the callback, stored for usage */
comm_point_callback_type* cb;
/** the callback userarg, stored for usage */
void* cb_arg;
/** runtime ptr */
struct replay_runtime* runtime;
/** the pending entry for this commpoint (if any) */
struct fake_pending* pending;
};
/** Global variable: the scenario. Saved here for when event_init is done. */
static struct replay_scenario* saved_scenario = NULL;
/** helper function that logs a sldns_pkt packet to logfile */
static void
log_pkt(const char* desc, uint8_t* pkt, size_t len)
{
char* str = sldns_wire2str_pkt(pkt, len);
if(!str)
fatal_exit("%s: (failed out of memory wire2str_pkt)", desc);
else {
log_info("%s%s", desc, str);
free(str);
}
}
/**
* Returns a string describing the event type.
*/
static const char*
repevt_string(enum replay_event_type t)
{
switch(t) {
case repevt_nothing: return "NOTHING";
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_autotrust_check: return "CHECK_AUTOTRUST";
case repevt_tempfile_check: return "CHECK_TEMPFILE";
case repevt_error: return "ERROR";
case repevt_assign: return "ASSIGN";
case repevt_traffic: return "TRAFFIC";
case repevt_infra_rtt: return "INFRA_RTT";
case repevt_flush_message: return "FLUSH_MESSAGE";
case repevt_expire_message: return "EXPIRE_MESSAGE";
default: return "UNKNOWN";
}
}
/**
* return: true if pending query matches the now event.
*/
static int
pending_matches_current(struct replay_runtime* runtime,
struct entry** entry, struct fake_pending **pend)
{
struct fake_pending* p;
struct entry* e;
if(!runtime->now || runtime->now->evt_type != repevt_back_query
|| !runtime->pending_list)
return 0;
/* see if any of the pending queries matches */
for(p = runtime->pending_list; p; p = p->next) {
if(runtime->now->addrlen != 0 &&
sockaddr_cmp(&p->addr, p->addrlen, &runtime->now->addr,
runtime->now->addrlen) != 0)
continue;
if((e=find_match(runtime->now->match, p->pkt, p->pkt_len,
p->transport))) {
*entry = e;
*pend = p;
return 1;
}
}
return 0;
}
/**
* Find the range that matches this pending message.
* @param runtime: runtime with current moment, and range list.
* @param entry: returns the pointer to entry that matches.
* @param pend: the pending that the entry must match.
* @return: true if a match is found.
*/
static int
pending_find_match(struct replay_runtime* runtime, struct entry** entry,
struct fake_pending* pend)
{
int timenow = runtime->now->time_step;
struct replay_range* p = runtime->scenario->range_list;
while(p) {
if(p->start_step <= timenow && timenow <= p->end_step &&
(p->addrlen == 0 || sockaddr_cmp(&p->addr, p->addrlen,
&pend->addr, pend->addrlen) == 0) &&
(*entry = find_match(p->match, pend->pkt, pend->pkt_len,
pend->transport))) {
log_info("matched query time %d in range [%d, %d] "
"with entry line %d", timenow,
p->start_step, p->end_step, (*entry)->lineno);
if(p->addrlen != 0)
log_addr(0, "matched ip", &p->addr, p->addrlen);
log_pkt("matched pkt: ",
(*entry)->reply_list->reply_pkt,
(*entry)->reply_list->reply_len);
return 1;
}
p = p->next_range;
}
return 0;
}
/**
* See if outgoing pending query matches an entry.
* @param runtime: runtime.
* @param entry: if true, the entry that matches is returned.
* @param pend: if true, the outgoing message that matches is returned.
* @return: true if pending query matches the now event.
*/
static int
pending_matches_range(struct replay_runtime* runtime,
struct entry** entry, struct fake_pending** pend)
{
struct fake_pending* p = runtime->pending_list;
/* slow, O(N*N), but it works as advertised with weird matching */
while(p) {
if(p->tcp_pkt_counter != 0) {
/* continue tcp transfer */
*pend = p;
return 1;
}
if(pending_find_match(runtime, entry, p)) {
*pend = p;
return 1;
}
p = p->next;
}
return 0;
}
/**
* Remove the item from the pending list.
*/
static void
pending_list_delete(struct replay_runtime* runtime, struct fake_pending* pend)
{
struct fake_pending** prev = &runtime->pending_list;
struct fake_pending* p = runtime->pending_list;
/** perform exponential backoff on the timeout */
static void
expon_timeout_backoff(struct replay_runtime* runtime)
{
struct fake_pending* p = runtime->pending_list;
int rtt, vs;
uint8_t edns_lame_known;
int last_rtt, rto;
if(!p) return; /* no pending packet to backoff */
if(!infra_host(runtime->infra, &p->addr, p->addrlen, p->zone,
p->zonelen, runtime->now_secs, &vs, &edns_lame_known, &rtt))
return;
last_rtt = rtt;
rto = infra_rtt_update(runtime->infra, &p->addr, p->addrlen, p->zone,
p->zonelen, p->qtype, -1, last_rtt, runtime->now_secs);
log_info("infra_rtt_update returned rto %d", rto);
}
/**
* Advance to the next moment.
*/
static void
advance_moment(struct replay_runtime* runtime)
{
if(!runtime->now)
runtime->now = runtime->scenario->mom_first;
else runtime->now = runtime->now->mom_next;
}
/**
* Perform actions or checks determined by the moment.
* Also advances the time by one step.
* @param runtime: scenario runtime information.
*/
static void
do_moment_and_advance(struct replay_runtime* runtime)
{
struct replay_moment* mom;
if(!runtime->now) {
advance_moment(runtime);
return;
}
log_info("testbound: do STEP %d %s", runtime->now->time_step,
repevt_string(runtime->now->evt_type));
switch(runtime->now->evt_type) {
case repevt_nothing:
advance_moment(runtime);
break;
case repevt_front_query:
/* advance moment before doing the step, so that the next
moment which may check some result of the mom step
can catch those results. */
mom = runtime->now;
advance_moment(runtime);
fake_front_query(runtime, mom);
break;
case repevt_front_reply:
if(runtime->answer_list)
log_err("testbound: There are unmatched answers.");
fatal_exit("testbound: query answer not matched");
break;
case repevt_timeout:
mom = runtime->now;
advance_moment(runtime);
expon_timeout_backoff(runtime);
fake_pending_callback(runtime, mom, NETEVENT_TIMEOUT);
break;
case repevt_back_reply:
mom = runtime->now;
advance_moment(runtime);
fake_pending_callback(runtime, mom, NETEVENT_NOERROR);
break;
case repevt_back_query:
/* Back queries are matched when they are sent out. */
log_err("No query matching the current moment was sent.");
fatal_exit("testbound: back query not matched");
break;
case repevt_error:
mom = runtime->now;
advance_moment(runtime);
fake_pending_callback(runtime, mom, NETEVENT_CLOSED);
break;
case repevt_time_passes:
time_passes(runtime, runtime->now);
advance_moment(runtime);
break;
case repevt_autotrust_check:
autotrust_check(runtime, runtime->now);
advance_moment(runtime);
break;
case repevt_tempfile_check:
tempfile_check(runtime, runtime->now);
advance_moment(runtime);
break;
case repevt_assign:
moment_assign(runtime, runtime->now);
advance_moment(runtime);
break;
case repevt_traffic:
advance_moment(runtime);
break;
case repevt_infra_rtt:
do_infra_rtt(runtime);
advance_moment(runtime);
break;
case repevt_flush_message:
do_flush_message(runtime);
advance_moment(runtime);
break;
case repevt_expire_message:
do_expire_message(runtime);
advance_moment(runtime);
break;
default:
fatal_exit("testbound: unknown event type %d",
runtime->now->evt_type);
}
}
/** run the scenario in event callbacks */
static void
run_scenario(struct replay_runtime* runtime)
{
struct entry* entry = NULL;
struct fake_pending* pending = NULL;
int max_rounds = 5000;
int rounds = 0;
runtime->now = runtime->scenario->mom_first;
log_info("testbound: entering fake runloop");
do {
/* if moment matches pending query do it. */
/* else if moment matches given answer, do it */
/* else if precoded_range matches pending, do it */
/* else do the current moment */
if(pending_matches_current(runtime, &entry, &pending)) {
log_info("testbound: do STEP %d CHECK_OUT_QUERY",
runtime->now->time_step);
advance_moment(runtime);
if(entry->copy_id)
answer_callback_from_entry(runtime, entry,
pending);
} else if(runtime->answer_list && runtime->now &&
runtime->now->evt_type == repevt_front_reply) {
answer_check_it(runtime);
advance_moment(runtime);
} else if(pending_matches_range(runtime, &entry, &pending)) {
answer_callback_from_entry(runtime, entry, pending);
} else {
do_moment_and_advance(runtime);
}
log_info("testbound: end of event stage");
rounds++;
if(rounds > max_rounds)
fatal_exit("testbound: too many rounds, it loops.");
} while(runtime->now);
if(runtime->pending_list) {
struct fake_pending* p;
log_err("testbound: there are still messages pending.");
for(p = runtime->pending_list; p; p=p->next) {
log_pkt("pending msg", p->pkt, p->pkt_len);
log_addr(0, "pending to", &p->addr, p->addrlen);
}
fatal_exit("testbound: there are still messages pending.");
}
if(runtime->answer_list) {
fatal_exit("testbound: there are unmatched answers.");
}
log_info("testbound: exiting fake runloop.");
runtime->exit_cleanly = 1;
}
/* see if it matches the current moment */
if(runtime->now && runtime->now->evt_type == repevt_back_query &&
(runtime->now->addrlen == 0 || sockaddr_cmp(
&runtime->now->addr, runtime->now->addrlen,
&pend->addr, pend->addrlen) == 0) &&
find_match(runtime->now->match, pend->pkt, pend->pkt_len,
pend->transport)) {
log_info("testbound: matched pending to event. "
"advance time between events.");
log_info("testbound: do STEP %d %s", runtime->now->time_step,
repevt_string(runtime->now->evt_type));
advance_moment(runtime);
/* still create the pending, because we need it to callback */
}
log_info("testbound: created fake pending");
/* add to list */
pend->next = runtime->pending_list;
runtime->pending_list = pend;
return (struct pending*)pend;
}
/* see if it matches the current moment */
if(runtime->now && runtime->now->evt_type == repevt_back_query &&
(runtime->now->addrlen == 0 || sockaddr_cmp(
&runtime->now->addr, runtime->now->addrlen,
&pend->addr, pend->addrlen) == 0) &&
find_match(runtime->now->match, pend->pkt, pend->pkt_len,
pend->transport)) {
log_info("testbound: matched pending to event. "
"advance time between events.");
log_info("testbound: do STEP %d %s", runtime->now->time_step,
repevt_string(runtime->now->evt_type));
advance_moment(runtime);
/* still create the pending, because we need it to callback */
}
log_info("testbound: created fake pending");
/* add to list */
pend->next = runtime->pending_list;
runtime->pending_list = pend;
return (struct waiting_tcp*)pend;
}
/* see if it matches the current moment */
if(runtime->now && runtime->now->evt_type == repevt_back_query &&
(runtime->now->addrlen == 0 || sockaddr_cmp(
&runtime->now->addr, runtime->now->addrlen,
&pend->addr, pend->addrlen) == 0) &&
find_match(runtime->now->match, pend->pkt, pend->pkt_len,
pend->transport)) {
log_info("testbound: matched pending to event. "
"advance time between events.");
log_info("testbound: do STEP %d %s", runtime->now->time_step,
repevt_string(runtime->now->evt_type));
advance_moment(runtime);
/* still create the pending, because we need it to callback */
}
log_info("testbound: created fake pending");
/* add to list */
pend->next = runtime->pending_list;
runtime->pending_list = pend;
return (struct serviced_query*)pend;
}
struct comm_point* comm_point_create_raw(struct comm_base* ATTR_UNUSED(base),
int ATTR_UNUSED(fd), int ATTR_UNUSED(writing),
comm_point_callback_type* ATTR_UNUSED(callback),
void* ATTR_UNUSED(callback_arg))
{
/* no pipe comm possible */
struct fake_commpoint* fc = (struct fake_commpoint*)calloc(1,
sizeof(*fc));
if(!fc) return NULL;
fc->typecode = FAKE_COMMPOINT_TYPECODE;
return (struct comm_point*)fc;
}
void comm_point_start_listening(struct comm_point* ATTR_UNUSED(c),
int ATTR_UNUSED(newfd), int ATTR_UNUSED(sec))
{
/* no bg write pipe comm possible */
}
void comm_point_stop_listening(struct comm_point* ATTR_UNUSED(c))
{
/* no bg write pipe comm possible */
}
/* only cmd com _local gets deleted */
void comm_point_delete(struct comm_point* c)
{
struct fake_commpoint* fc = (struct fake_commpoint*)c;
if(c == NULL) return;
log_assert(fc->typecode == FAKE_COMMPOINT_TYPECODE);
if(fc->type_tcp_out) {
/* remove tcp pending, so no more callbacks to it */
pending_list_delete(fc->runtime, fc->pending);
}
free(c);
}
int create_udp_sock(int ATTR_UNUSED(family), int ATTR_UNUSED(socktype),
struct sockaddr* ATTR_UNUSED(addr), socklen_t ATTR_UNUSED(addrlen),
int ATTR_UNUSED(v6only), int* ATTR_UNUSED(inuse),
int* ATTR_UNUSED(noproto), int ATTR_UNUSED(rcv), int ATTR_UNUSED(snd),
int ATTR_UNUSED(listen), int* ATTR_UNUSED(reuseport),
int ATTR_UNUSED(transparent), int ATTR_UNUSED(freebind),
int ATTR_UNUSED(use_systemd), int ATTR_UNUSED(dscp))
{
/* if you actually print to this, it'll be stdout during test */
return 1;
}
log_info("testbound: created fake pending for send_udp_msg");
/* add to list */
pend->next = runtime->pending_list;
runtime->pending_list = pend;
return 1;
}
int outnet_get_tcp_fd(struct sockaddr_storage* ATTR_UNUSED(addr),
socklen_t ATTR_UNUSED(addrlen), int ATTR_UNUSED(tcp_mss),
int ATTR_UNUSED(dscp), int ATTR_UNUSED(nodelay))
{
log_assert(0);
return -1;
}