/*
* testpkts. Data file parse for test packets, and query matching.
*
* Data storage for specially crafted replies for testing purposes.
*
* (c) NLnet Labs, 2005, 2006, 2007, 2008
* See the file LICENSE for the license
*/
/**
* \file
* This is a debugging aid. It is not efficient, especially
* with a long config file, but it can give any reply to any query.
* This can help the developer pre-script replies for queries.
*
* You can specify a packet RR by RR with header flags to return.
*
* Missing features:
* - matching content different from reply content.
* - find way to adjust mangled packets?
*/
/** max size of a packet */
#define MAX_PACKETLEN 65536
/** max line length */
#define MAX_LINE 10240
/** string to show in warnings and errors */
static const char* prog_name = "testpkts";
/** return if string is empty or comment */
static int isendline(char c)
{
if(c == ';' || c == '#'
|| c == '\n' || c == 0)
return 1;
return 0;
}
/** true if the string starts with the keyword given. Moves the str ahead.
* @param str: before keyword, afterwards after keyword and spaces.
* @param keyword: the keyword to match
* @return: true if keyword present. False otherwise, and str unchanged.
*/
static int str_keyword(char** str, const char* keyword)
{
size_t len = strlen(keyword);
assert(str && keyword);
if(strncmp(*str, keyword, len) != 0)
return 0;
*str += len;
while(isspace((unsigned char)**str))
(*str)++;
return 1;
}
/** Add reply packet to entry */
static struct reply_packet*
entry_add_reply(struct entry* entry)
{
struct reply_packet* pkt = (struct reply_packet*)malloc(
sizeof(struct reply_packet));
struct reply_packet ** p = &entry->reply_list;
if(!pkt) error("out of memory");
pkt->next = NULL;
pkt->packet_sleep = 0;
pkt->reply_pkt = NULL;
pkt->reply_from_hex = NULL;
pkt->raw_ednsdata = NULL;
/* link at end */
while(*p)
p = &((*p)->next);
*p = pkt;
return pkt;
}
/**
* Converts a hex string to binary data
* @param hexstr: string of hex.
* @param len: is the length of the string
* @param buf: is the buffer to store the result in
* @param offset: is the starting position in the result buffer
* @param buf_len: is the length of buf.
* @return This function returns the length of the result
*/
static size_t
hexstr2bin(char *hexstr, int len, uint8_t *buf, size_t offset, size_t buf_len)
{
char c;
int i;
uint8_t int8 = 0;
int sec = 0;
size_t bufpos = 0;
if (len % 2 != 0) {
return 0;
}
for (i=0; i<len; i++) {
c = hexstr[i];
/* case insensitive, skip spaces */
if (c != ' ') {
if (c >= '0' && c <= '9') {
int8 += c & 0x0f;
} else if (c >= 'a' && c <= 'z') {
int8 += (c & 0x0f) + 9;
} else if (c >= 'A' && c <= 'Z') {
int8 += (c & 0x0f) + 9;
} else {
return 0;
}
/** parse ORIGIN */
static void
get_origin(const char* name, struct sldns_file_parse_state* pstate, char* parse)
{
/* snip off rest of the text so as to make the parse work in ldns */
char* end;
char store;
int status;
}
if(reading_hex) {
error("%s: End of file reached while still reading hex, "
"missing HEX_ANSWER_END\n", name);
}
if(reading_hex_ednsdata) {
error("%s: End of file reached while still reading edns data, "
"missing HEX_EDNSDATA_END\n", name);
}
if(current) {
error("%s: End of file reached while reading entry. "
"missing ENTRY_END\n", name);
}
return 0;
}
/* reads the canned reply file and returns a list of structs */
struct entry*
read_datafile(const char* name, int skip_whitespace)
{
struct entry* list = NULL;
struct entry* last = NULL;
struct entry* current = NULL;
FILE *in;
struct sldns_file_parse_state pstate;
int entry_num = 0;
memset(&pstate, 0, sizeof(pstate));
if((in=fopen(name, "r")) == NULL) {
error("could not open file %s: %s", name, strerror(errno));
}
/* skip other records with wire2str_scan */
for(i=0; i < LDNS_QDCOUNT(p); i++)
(void)sldns_wire2str_rrquestion_scan(&walk, &walk_len,
&snull, &sl, p, plen, &comprloop);
for(i=0; i < LDNS_ANCOUNT(p); i++)
(void)sldns_wire2str_rr_scan(&walk, &walk_len, &snull, &sl,
p, plen, &comprloop);
/* walk through authority section */
for(i=0; i < LDNS_NSCOUNT(p); i++) {
/* if this is SOA then get serial, skip compressed dname */
uint8_t* dstart = walk;
size_t dlen = walk_len;
(void)sldns_wire2str_dname_scan(&dstart, &dlen, &snull, &sl,
p, plen, &comprloop);
if(dlen >= 2 && sldns_read_uint16(dstart) == LDNS_RR_TYPE_SOA) {
/* skip type, class, TTL, rdatalen */
if(dlen < 10)
return 0;
if(dlen < 10 + (size_t)sldns_read_uint16(dstart+8))
return 0;
dstart += 10;
dlen -= 10;
/* check third rdf */
(void)sldns_wire2str_dname_scan(&dstart, &dlen, &snull,
&sl, p, plen, &comprloop);
(void)sldns_wire2str_dname_scan(&dstart, &dlen, &snull,
&sl, p, plen, &comprloop);
if(dlen < 4)
return 0;
verbose(3, "found serial %u in msg. ",
(int)sldns_read_uint32(dstart));
return sldns_read_uint32(dstart);
}
/* move to next RR */
(void)sldns_wire2str_rr_scan(&walk, &walk_len, &snull, &sl,
p, plen, &comprloop);
}
return 0;
}
/** get ptr to EDNS OPT record (and remaining length); after the type u16 */
static int
pkt_find_edns_opt(uint8_t** p, size_t* plen)
{
/* walk over the packet with scan routines */
uint8_t* w = *p;
size_t wlen = *plen, sl=0;
char* snull = NULL;
uint16_t i;
int comprloop = 0;
/* skip other records with wire2str_scan */
for(i=0; i < LDNS_QDCOUNT(*p); i++)
(void)sldns_wire2str_rrquestion_scan(&w, &wlen, &snull, &sl,
*p, *plen, &comprloop);
for(i=0; i < LDNS_ANCOUNT(*p); i++)
(void)sldns_wire2str_rr_scan(&w, &wlen, &snull, &sl, *p, *plen, &comprloop);
for(i=0; i < LDNS_NSCOUNT(*p); i++)
(void)sldns_wire2str_rr_scan(&w, &wlen, &snull, &sl, *p, *plen, &comprloop);
/* walk through additional section */
for(i=0; i < LDNS_ARCOUNT(*p); i++) {
/* if this is OPT then done */
uint8_t* dstart = w;
size_t dlen = wlen;
(void)sldns_wire2str_dname_scan(&dstart, &dlen, &snull, &sl,
*p, *plen, &comprloop);
if(dlen >= 2 && sldns_read_uint16(dstart) == LDNS_RR_TYPE_OPT) {
*p = dstart+2;
*plen = dlen-2;
return 1;
}
/* move to next RR */
(void)sldns_wire2str_rr_scan(&w, &wlen, &snull, &sl, *p, *plen, &comprloop);
}
return 0;
}
/** return true if the packet has EDNS OPT record */
static int
get_has_edns(uint8_t* pkt, size_t len)
{
/* use arguments as temporary variables */
return pkt_find_edns_opt(&pkt, &len);
}
/** return true if the DO flag is set */
static int
get_do_flag(uint8_t* pkt, size_t len)
{
uint16_t edns_bits;
uint8_t* walk = pkt;
size_t walk_len = len;
if(!pkt_find_edns_opt(&walk, &walk_len)) {
return 0;
}
if(walk_len < 6)
return 0; /* malformed */
edns_bits = sldns_read_uint16(walk+4);
return (int)(edns_bits&LDNS_EDNS_MASK_DO_BIT);
}
/** Snips the specified EDNS option out of the OPT record and puts it in the
* provided buffer. The buffer should be able to hold any opt data ie 65535.
* Returns the length of the option written,
* or 0 if not found, else -1 on error. */
static int
pkt_snip_edns_option(uint8_t* pkt, size_t len, sldns_edns_option code,
uint8_t* buf)
{
uint8_t *rdata, *opt_position = pkt;
uint16_t rdlen, optlen;
size_t remaining = len;
if(!pkt_find_edns_opt(&opt_position, &remaining)) return 0;
if(remaining < 8) return -1; /* malformed */
rdlen = sldns_read_uint16(opt_position+6);
rdata = opt_position + 8;
while(rdlen > 0) {
if(rdlen < 4) return -1; /* malformed */
optlen = sldns_read_uint16(rdata+2);
if(sldns_read_uint16(rdata) == code) {
/* save data to buf for caller inspection */
memmove(buf, rdata+4, optlen);
/* snip option from packet; assumes len is correct */
memmove(rdata, rdata+4+optlen,
(pkt+len)-(rdata+4+optlen));
/* update OPT size */
sldns_write_uint16(opt_position+6,
sldns_read_uint16(opt_position+6)-(4+optlen));
return optlen;
}
rdlen -= 4 + optlen;
rdata += 4 + optlen;
}
return 0;
}
/** Snips the EDE option out of the OPT record and returns the EDNS EDE
* INFO-CODE if found, else -1 */
static int
extract_ede(uint8_t* pkt, size_t len)
{
uint8_t buf[65535];
int buflen = pkt_snip_edns_option(pkt, len, LDNS_EDNS_EDE, buf);
if(buflen < 2 /*ede without text at minimum*/) return -1;
return sldns_read_uint16(buf);
}
/** Snips the DNS Cookie option out of the OPT record and puts it in the
* provided cookie buffer (should be at least 24 octets).
* Returns the length of the cookie if found, else -1. */
static int
extract_cookie(uint8_t* pkt, size_t len, uint8_t* cookie)
{
uint8_t buf[65535];
int buflen = pkt_snip_edns_option(pkt, len, LDNS_EDNS_COOKIE, buf);
if(buflen != 8 /*client cookie*/ &&
buflen != 8 + 16 /*server cookie*/) return -1;
memcpy(cookie, buf, buflen);
return buflen;
}
/** zero TTLs in packet */
static void
zerottls(uint8_t* pkt, size_t pktlen)
{
uint8_t* walk = pkt;
size_t walk_len = pktlen, sl=0;
char* snull = NULL;
uint16_t i;
uint16_t num = LDNS_ANCOUNT(pkt)+LDNS_NSCOUNT(pkt)+LDNS_ARCOUNT(pkt);
int comprloop = 0;
if(walk_len < LDNS_HEADER_SIZE)
return;
walk += LDNS_HEADER_SIZE;
walk_len -= LDNS_HEADER_SIZE;
for(i=0; i < LDNS_QDCOUNT(pkt); i++)
(void)sldns_wire2str_rrquestion_scan(&walk, &walk_len,
&snull, &sl, pkt, pktlen, &comprloop);
for(i=0; i < num; i++) {
/* wipe TTL */
uint8_t* dstart = walk;
size_t dlen = walk_len;
(void)sldns_wire2str_dname_scan(&dstart, &dlen, &snull, &sl,
pkt, pktlen, &comprloop);
if(dlen < 8)
return;
sldns_write_uint32(dstart+4, 0);
/* go to next RR */
(void)sldns_wire2str_rr_scan(&walk, &walk_len, &snull, &sl,
pkt, pktlen, &comprloop);
}
}
/** get one line (\n) from a string, move next to after the \n, zero \n */
static int
get_line(char** s, char** n)
{
/* at end of string? end */
if(*n == NULL || **n == 0)
return 0;
/* result starts at next string */
*s = *n;
/* find \n after that */
*n = strchr(*s, '\n');
if(*n && **n != 0) {
/* terminate line */
(*n)[0] = 0;
(*n)++;
}
return 1;
}
/** match two RR sections without ordering */
static int
match_noloc_section(char** q, char** nq, char** p, char** np, uint16_t num)
{
/* for max number of RRs in packet */
const uint16_t numarray = 3000;
char* qlines[numarray], *plines[numarray];
uint16_t i, j, numq=0, nump=0;
if(num > numarray) fatal_exit("too many RRs");
/* gather lines */
for(i=0; i<num; i++) {
get_line(q, nq);
get_line(p, np);
qlines[numq++] = *q;
plines[nump++] = *p;
}
/* see if they are all present in the other */
for(i=0; i<num; i++) {
int found = 0;
for(j=0; j<num; j++) {
if(strcmp(qlines[i], plines[j]) == 0) {
found = 1;
break;
}
}
if(!found) {
verbose(3, "comparenoloc: failed for %s", qlines[i]);
return 0;
}
}
return 1;
}
/** match two strings for unordered equality of RRs and everything else */
static int
match_noloc(char* q, char* p, uint8_t* q_pkt, size_t q_pkt_len,
uint8_t* p_pkt, size_t p_pkt_len)
{
char* nq = q, *np = p;
/* if no header, compare bytes */
if(p_pkt_len < LDNS_HEADER_SIZE || q_pkt_len < LDNS_HEADER_SIZE) {
if(p_pkt_len != q_pkt_len) return 0;
return memcmp(p, q, p_pkt_len);
}
/* compare RR counts */
if(LDNS_QDCOUNT(p_pkt) != LDNS_QDCOUNT(q_pkt))
return 0;
if(LDNS_ANCOUNT(p_pkt) != LDNS_ANCOUNT(q_pkt))
return 0;
if(LDNS_NSCOUNT(p_pkt) != LDNS_NSCOUNT(q_pkt))
return 0;
if(LDNS_ARCOUNT(p_pkt) != LDNS_ARCOUNT(q_pkt))
return 0;
/* get a line from both; compare; at sections do section */
get_line(&q, &nq);
get_line(&p, &np);
if(strcmp(q, p) != 0) {
/* header line opcode, rcode, id */
return 0;
}
get_line(&q, &nq);
get_line(&p, &np);
if(strcmp(q, p) != 0) {
/* header flags, rr counts */
return 0;
}
/* ;; QUESTION SECTION */
get_line(&q, &nq);
get_line(&p, &np);
if(strcmp(q, p) != 0) return 0;
if(!match_noloc_section(&q, &nq, &p, &np, LDNS_QDCOUNT(p_pkt)))
return 0;
/** ignore EDNS lines in the string by overwriting them with what's left or
* zero out if at end of the string */
static int
ignore_edns_lines(char* str) {
char* edns = str, *n;
size_t str_len = strlen(str);
while((edns = strstr(edns, "; EDNS"))) {
n = strchr(edns, '\n');
if(!n) {
/* EDNS at end of string; zero */
*edns = 0;
break;
}
memmove(edns, n+1, str_len-(n-str));
}
return 1;
}
/** match all of the packet */
int
match_all(uint8_t* q, size_t qlen, uint8_t* p, size_t plen, int mttl,
int noloc, int noedns)
{
char* qstr, *pstr;
uint8_t* qb = q, *pb = p;
int r;
qb = memdup(q, qlen);
pb = memdup(p, plen);
if(!qb || !pb) error("out of memory");
/* zero TTLs */
if(!mttl) {
zerottls(qb, qlen);
zerottls(pb, plen);
}
lowercase_pkt(qb, qlen);
lowercase_pkt(pb, plen);
qstr = sldns_wire2str_pkt(qb, qlen);
pstr = sldns_wire2str_pkt(pb, plen);
if(!qstr || !pstr) error("cannot pkt2string");
/* should we ignore EDNS lines? */
if(noedns) {
ignore_edns_lines(qstr);
ignore_edns_lines(pstr);
}
r = (strcmp(qstr, pstr) == 0);
if(!r) {
/* remove ;; MSG SIZE (at end of string) */
char* s = strstr(qstr, ";; MSG SIZE");
if(s) *s=0;
s = strstr(pstr, ";; MSG SIZE");
if(s) *s=0;
r = (strcmp(qstr, pstr) == 0);
if(!r && !noloc && !noedns) {
/* we are going to fail, see if the cause is EDNS */
char* a = strstr(qstr, "; EDNS");
char* b = strstr(pstr, "; EDNS");
if( (a&&!b) || (b&&!a) ) {
verbose(3, "mismatch in EDNS\n");
}
}
}
if(!r && noloc) {
/* check for reordered sections */
r = match_noloc(qstr, pstr, q, qlen, p, plen);
}
if(!r) {
verbose(3, "mismatch pkt '%s' and '%s'", qstr, pstr);
}
free(qstr);
free(pstr);
free(qb);
free(pb);
return r;
}
/* perform the copy; if possible; must be uncompressed */
if(match->copy_query && origlen >= LDNS_HEADER_SIZE &&
query_len >= LDNS_HEADER_SIZE && LDNS_QDCOUNT(query_pkt)!=0
&& LDNS_QDCOUNT(orig)==0) {
/* no qname in output packet, insert it */
size_t dlen = get_qname_len(query_pkt, query_len);
reslen = origlen + dlen + 4;
res = (uint8_t*)malloc(reslen);
if(!res) {
verbose(1, "out of memory; send without adjust\n");
return;
}
/* copy the header, query, remainder */
memcpy(res, orig, LDNS_HEADER_SIZE);
memmove(res+LDNS_HEADER_SIZE, query_pkt+LDNS_HEADER_SIZE,
dlen+4);
memmove(res+LDNS_HEADER_SIZE+dlen+4, orig+LDNS_HEADER_SIZE,
reslen-(LDNS_HEADER_SIZE+dlen+4));
/* set QDCOUNT */
sldns_write_uint16(res+4, 1);
} else if(match->copy_query && origlen >= LDNS_HEADER_SIZE &&
query_len >= LDNS_HEADER_SIZE && LDNS_QDCOUNT(query_pkt)!=0
&& get_qname_len(orig, origlen) == 0) {
/* QDCOUNT(orig)!=0 but qlen == 0, therefore, an error */
verbose(1, "error: malformed qname; send without adjust\n");
res = memdup(orig, origlen);
reslen = origlen;
} else if(match->copy_query && origlen >= LDNS_HEADER_SIZE &&
query_len >= LDNS_HEADER_SIZE && LDNS_QDCOUNT(query_pkt)!=0
&& LDNS_QDCOUNT(orig)!=0) {
/* in this case olen != 0 and QDCOUNT(orig)!=0 */
/* copy query section */
size_t dlen = get_qname_len(query_pkt, query_len);
size_t olen = get_qname_len(orig, origlen);
reslen = origlen + dlen - olen;
res = (uint8_t*)malloc(reslen);
if(!res) {
verbose(1, "out of memory; send without adjust\n");
return;
}
/* copy the header, query, remainder */
memcpy(res, orig, LDNS_HEADER_SIZE);
memmove(res+LDNS_HEADER_SIZE, query_pkt+LDNS_HEADER_SIZE,
dlen+4);
memmove(res+LDNS_HEADER_SIZE+dlen+4,
orig+LDNS_HEADER_SIZE+olen+4,
reslen-(LDNS_HEADER_SIZE+dlen+4));
} else {
res = memdup(orig, origlen);
reslen = origlen;
}
if(!res) {
verbose(1, "out of memory; send without adjust\n");
return;
}
/* copy the ID */
if(match->copy_id && reslen >= 2 && query_len >= 2)
res[1] = query_pkt[1];
if(match->copy_id && reslen >= 1 && query_len >= 1)
res[0] = query_pkt[0];
if(match->copy_ednsdata_assume_clientsubnet) {
/** Assume there is only one EDNS option, which is ECS.
* Copy source mask from query to scope mask in reply. Assume
* rest of ECS data in response (eg address) matches the query.
*/
uint8_t* walk_q = orig;
size_t walk_qlen = origlen;
uint8_t* walk_p = res;
size_t walk_plen = reslen;