/* * ldns-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 * 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? */ #include "config.h" struct sockaddr_storage; #include #include #include "ldns-testpkts.h" /** max line length */ #define MAX_LINE 10240 /** string to show in warnings and errors */ static const char* prog_name = "ldns-testpkts"; /** logging routine, provided by caller */ void verbose(int lvl, const char* msg, ...); /** print error and exit */ static void error(const char* msg, ...) { va_list args; va_start(args, msg); fprintf(stderr, "%s error: ", prog_name); vfprintf(stderr, msg, args); fprintf(stderr, "\n"); fflush(stderr); va_end(args); exit(EXIT_FAILURE); } /** return if string is empty or comment */ static bool isendline(char c) { if(c == ';' || c == '#' || c == '\n' || c == 0) return true; return false; } /** 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 bool str_keyword(const char** str, const char* keyword) { size_t len = strlen(keyword); assert(str && keyword); if(strncmp(*str, keyword, len) != 0) return false; *str += len; while(isspace(**str)) (*str)++; return true; } /** 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; pkt->next = NULL; pkt->packet_sleep = 0; pkt->reply = ldns_pkt_new(); pkt->reply_from_hex = NULL; /* link at end */ while(*p) p = &((*p)->next); *p = pkt; return pkt; } /** parse MATCH line */ static void matchline(const char* line, struct entry* e) { const char* parse = line; while(*parse) { if(isendline(*parse)) return; if(str_keyword(&parse, "opcode")) { e->match_opcode = true; } else if(str_keyword(&parse, "qtype")) { e->match_qtype = true; } else if(str_keyword(&parse, "qname")) { e->match_qname = true; } else if(str_keyword(&parse, "all")) { e->match_all = true; } else if(str_keyword(&parse, "ttl")) { e->match_ttl = true; } else if(str_keyword(&parse, "DO")) { e->match_do = true; } else if(str_keyword(&parse, "UDP")) { e->match_transport = transport_udp; } else if(str_keyword(&parse, "TCP")) { e->match_transport = transport_tcp; } else if(str_keyword(&parse, "serial")) { e->match_serial = true; if(*parse != '=' && *parse != ':') error("expected = or : in MATCH: %s", line); parse++; e->ixfr_soa_serial = (uint32_t)strtol(parse, (char**)&parse, 10); while(isspace(*parse)) parse++; } else { error("could not parse MATCH: '%s'", parse); } } } /** parse REPLY line */ static void replyline(const char* line, ldns_pkt *reply) { const char* parse = line; while(*parse) { if(isendline(*parse)) return; /* opcodes */ if(str_keyword(&parse, "QUERY")) { ldns_pkt_set_opcode(reply, LDNS_PACKET_QUERY); } else if(str_keyword(&parse, "IQUERY")) { ldns_pkt_set_opcode(reply, LDNS_PACKET_IQUERY); } else if(str_keyword(&parse, "STATUS")) { ldns_pkt_set_opcode(reply, LDNS_PACKET_STATUS); } else if(str_keyword(&parse, "NOTIFY")) { ldns_pkt_set_opcode(reply, LDNS_PACKET_NOTIFY); } else if(str_keyword(&parse, "UPDATE")) { ldns_pkt_set_opcode(reply, LDNS_PACKET_UPDATE); /* rcodes */ } else if(str_keyword(&parse, "NOERROR")) { ldns_pkt_set_rcode(reply, LDNS_RCODE_NOERROR); } else if(str_keyword(&parse, "FORMERR")) { ldns_pkt_set_rcode(reply, LDNS_RCODE_FORMERR); } else if(str_keyword(&parse, "SERVFAIL")) { ldns_pkt_set_rcode(reply, LDNS_RCODE_SERVFAIL); } else if(str_keyword(&parse, "NXDOMAIN")) { ldns_pkt_set_rcode(reply, LDNS_RCODE_NXDOMAIN); } else if(str_keyword(&parse, "NOTIMPL")) { ldns_pkt_set_rcode(reply, LDNS_RCODE_NOTIMPL); } else if(str_keyword(&parse, "REFUSED")) { ldns_pkt_set_rcode(reply, LDNS_RCODE_REFUSED); } else if(str_keyword(&parse, "YXDOMAIN")) { ldns_pkt_set_rcode(reply, LDNS_RCODE_YXDOMAIN); } else if(str_keyword(&parse, "YXRRSET")) { ldns_pkt_set_rcode(reply, LDNS_RCODE_YXRRSET); } else if(str_keyword(&parse, "NXRRSET")) { ldns_pkt_set_rcode(reply, LDNS_RCODE_NXRRSET); } else if(str_keyword(&parse, "NOTAUTH")) { ldns_pkt_set_rcode(reply, LDNS_RCODE_NOTAUTH); } else if(str_keyword(&parse, "NOTZONE")) { ldns_pkt_set_rcode(reply, LDNS_RCODE_NOTZONE); /* flags */ } else if(str_keyword(&parse, "QR")) { ldns_pkt_set_qr(reply, true); } else if(str_keyword(&parse, "AA")) { ldns_pkt_set_aa(reply, true); } else if(str_keyword(&parse, "TC")) { ldns_pkt_set_tc(reply, true); } else if(str_keyword(&parse, "RD")) { ldns_pkt_set_rd(reply, true); } else if(str_keyword(&parse, "CD")) { ldns_pkt_set_cd(reply, true); } else if(str_keyword(&parse, "RA")) { ldns_pkt_set_ra(reply, true); } else if(str_keyword(&parse, "AD")) { ldns_pkt_set_ad(reply, true); } else { error("could not parse REPLY: '%s'", parse); } } } /** parse ADJUST line */ static void adjustline(const char* line, struct entry* e, struct reply_packet* pkt) { const char* parse = line; while(*parse) { if(isendline(*parse)) return; if(str_keyword(&parse, "copy_id")) { e->copy_id = true; } else if(str_keyword(&parse, "sleep=")) { e->sleeptime = (unsigned int) strtol(parse, (char**)&parse, 10); while(isspace(*parse)) parse++; } else if(str_keyword(&parse, "packet_sleep=")) { pkt->packet_sleep = (unsigned int) strtol(parse, (char**)&parse, 10); while(isspace(*parse)) parse++; } else { error("could not parse ADJUST: '%s'", parse); } } } /** create new entry */ static struct entry* new_entry() { struct entry* e = LDNS_MALLOC(struct entry); memset(e, 0, sizeof(e)); e->match_opcode = false; e->match_qtype = false; e->match_qname = false; e->match_all = false; e->match_ttl = false; e->match_do = false; e->match_serial = false; e->ixfr_soa_serial = 0; e->match_transport = transport_any; e->reply_list = NULL; e->copy_id = false; e->sleeptime = 0; e->next = NULL; return e; } /** * 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. * * 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= '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; } if (sec == 0) { int8 = int8 << 4; sec = 1; } else { if (bufpos + offset + 1 <= buf_len) { buf[bufpos+offset] = int8; int8 = 0; sec = 0; bufpos++; } else { fprintf(stderr, "Buffer too small in hexstr2bin"); } } } } return bufpos; } /** convert hex buffer to binary buffer */ static ldns_buffer * data_buffer2wire(ldns_buffer *data_buffer) { ldns_buffer *wire_buffer = NULL; int c; /* stat hack * 0 = normal * 1 = comment (skip to end of line) * 2 = unprintable character found, read binary data directly */ size_t data_buf_pos = 0; int state = 0; uint8_t *hexbuf; int hexbufpos = 0; size_t wirelen; uint8_t *data_wire = (uint8_t *) ldns_buffer_export(data_buffer); uint8_t *wire = LDNS_XMALLOC(uint8_t, LDNS_MAX_PACKETLEN); hexbuf = LDNS_XMALLOC(uint8_t, LDNS_MAX_PACKETLEN); for (data_buf_pos = 0; data_buf_pos < ldns_buffer_position(data_buffer); data_buf_pos++) { c = (int) data_wire[data_buf_pos]; if (state < 2 && !isascii(c)) { /*verbose("non ascii character found in file: (%d) switching to raw mode\n", c);*/ state = 2; } switch (state) { case 0: if ( (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') ) { hexbuf[hexbufpos] = (uint8_t) c; hexbufpos++; } else if (c == ';') { state = 1; } else if (c == ' ' || c == '\t' || c == '\n') { /* skip whitespace */ } break; case 1: if (c == '\n' || c == EOF) { state = 0; } break; case 2: hexbuf[hexbufpos] = (uint8_t) c; hexbufpos++; break; default: error("unknown state while reading"); LDNS_FREE(hexbuf); return 0; break; } } if (hexbufpos >= LDNS_MAX_PACKETLEN) { /*verbose("packet size reached\n");*/ } /* lenient mode: length must be multiple of 2 */ if (hexbufpos % 2 != 0) { hexbuf[hexbufpos] = (uint8_t) '0'; hexbufpos++; } if (state < 2) { wirelen = hexstr2bin((char *) hexbuf, hexbufpos, wire, 0, LDNS_MAX_PACKETLEN); wire_buffer = ldns_buffer_new(wirelen); ldns_buffer_new_frm_data(wire_buffer, wire, wirelen); } else { error("Incomplete hex data, not at byte boundary\n"); } LDNS_FREE(wire); LDNS_FREE(hexbuf); return wire_buffer; } /** parse ORIGIN */ static void get_origin(const char* name, int lineno, ldns_rdf** origin, char* parse) { /* snip off rest of the text so as to make the parse work in ldns */ char* end; char store; ldns_status status; ldns_rdf_free(*origin); *origin = NULL; end=parse; while(!isspace(*end) && !isendline(*end)) end++; store = *end; *end = 0; verbose(3, "parsing '%s'\n", parse); status = ldns_str2rdf_dname(origin, parse); *end = store; if (status != LDNS_STATUS_OK) error("%s line %d:\n\t%s: %s", name, lineno, ldns_get_errorstr_by_id(status), parse); } /* Reads one entry from file. Returns entry or NULL on error. */ struct entry* read_entry(FILE* in, const char* name, int *lineno, uint16_t* default_ttl, ldns_rdf** origin, ldns_rdf** prev_rr) { struct entry* current = NULL; char line[MAX_LINE]; const char* parse; ldns_pkt_section add_section = LDNS_SECTION_QUESTION; struct reply_packet *cur_reply = NULL; bool reading_hex = false; ldns_buffer* hex_data_buffer = NULL; while(fgets(line, (int)sizeof(line), in) != NULL) { line[MAX_LINE-1] = 0; parse = line; (*lineno) ++; while(isspace(*parse)) parse++; /* test for keywords */ if(isendline(*parse)) continue; /* skip comment and empty lines */ if(str_keyword(&parse, "ENTRY_BEGIN")) { if(current) { error("%s line %d: previous entry does not ENTRY_END", name, *lineno); } current = new_entry(); current->lineno = *lineno; cur_reply = entry_add_reply(current); continue; } else if(str_keyword(&parse, "$ORIGIN")) { get_origin(name, *lineno, origin, (char*)parse); continue; } else if(str_keyword(&parse, "$TTL")) { *default_ttl = (uint16_t)atoi(parse); continue; } /* working inside an entry */ if(!current) { error("%s line %d: expected ENTRY_BEGIN but got %s", name, *lineno, line); } if(str_keyword(&parse, "MATCH")) { matchline(parse, current); } else if(str_keyword(&parse, "REPLY")) { replyline(parse, cur_reply->reply); } else if(str_keyword(&parse, "ADJUST")) { adjustline(parse, current, cur_reply); } else if(str_keyword(&parse, "EXTRA_PACKET")) { cur_reply = entry_add_reply(current); } else if(str_keyword(&parse, "SECTION")) { if(str_keyword(&parse, "QUESTION")) add_section = LDNS_SECTION_QUESTION; else if(str_keyword(&parse, "ANSWER")) add_section = LDNS_SECTION_ANSWER; else if(str_keyword(&parse, "AUTHORITY")) add_section = LDNS_SECTION_AUTHORITY; else if(str_keyword(&parse, "ADDITIONAL")) add_section = LDNS_SECTION_ADDITIONAL; else error("%s line %d: bad section %s", name, *lineno, parse); } else if(str_keyword(&parse, "HEX_ANSWER_BEGIN")) { hex_data_buffer = ldns_buffer_new(LDNS_MAX_PACKETLEN); reading_hex = true; } else if(str_keyword(&parse, "HEX_ANSWER_END")) { if (!reading_hex) { error("%s line %d: HEX_ANSWER_END read but no HEX_ANSWER_BEGIN keyword seen", name, *lineno); } reading_hex = false; cur_reply->reply_from_hex = data_buffer2wire(hex_data_buffer); ldns_buffer_free(hex_data_buffer); } else if(str_keyword(&parse, "ENTRY_END")) { return current; } else if(reading_hex) { ldns_buffer_printf(hex_data_buffer, line); } else { /* it must be a RR, parse and add to packet. */ ldns_rr* n = NULL; ldns_status status; status = ldns_rr_new_frm_str(&n, parse, *default_ttl, *origin, prev_rr); if (status != LDNS_STATUS_OK) error("%s line %d:\n\t%s: %s", name, *lineno, ldns_get_errorstr_by_id(status), parse); ldns_pkt_push_rr(cur_reply->reply, add_section, n); } } if (reading_hex) { error("%s: End of file reached while still reading hex, " "missing HEX_ANSWER_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) { struct entry* list = NULL; struct entry* last = NULL; struct entry* current = NULL; FILE *in; int lineno = 0; uint16_t default_ttl = 0; ldns_rdf* origin = NULL; ldns_rdf* prev_rr = NULL; int entry_num = 0; if((in=fopen(name, "r")) == NULL) { error("could not open file %s: %s", name, strerror(errno)); } while((current = read_entry(in, name, &lineno, &default_ttl, &origin, &prev_rr))) { if(last) last->next = current; else list = current; last = current; entry_num ++; } verbose(1, "%s: Read %d entries\n", prog_name, entry_num); fclose(in); return list; } /** get qtype from rr */ static ldns_rr_type get_qtype(ldns_pkt* p) { if(!ldns_rr_list_rr(ldns_pkt_question(p), 0)) return 0; return ldns_rr_get_type(ldns_rr_list_rr(ldns_pkt_question(p), 0)); } /** returns owner from rr */ static ldns_rdf* get_owner(ldns_pkt* p) { if(!ldns_rr_list_rr(ldns_pkt_question(p), 0)) return NULL; return ldns_rr_owner(ldns_rr_list_rr(ldns_pkt_question(p), 0)); } /** get authority section SOA serial value */ static uint32_t get_serial(ldns_pkt* p) { ldns_rr *rr = ldns_rr_list_rr(ldns_pkt_authority(p), 0); ldns_rdf *rdf; uint32_t val; if(!rr) return 0; rdf = ldns_rr_rdf(rr, 2); if(!rdf) return 0; val = ldns_rdf2native_int32(rdf); verbose(3, "found serial %u in msg. ", (int)val); return val; } /** match two rr lists */ static int match_list(ldns_rr_list* q, ldns_rr_list *p, bool mttl) { size_t i; if(ldns_rr_list_rr_count(q) != ldns_rr_list_rr_count(p)) return 0; for(i=0; inext) { verbose(3, "comparepkt: "); reply = p->reply_list->reply; if(p->match_opcode && ldns_pkt_get_opcode(query_pkt) != ldns_pkt_get_opcode(reply)) { verbose(3, "bad opcode\n"); continue; } if(p->match_qtype && get_qtype(query_pkt) != get_qtype(reply)) { verbose(3, "bad qtype\n"); continue; } if(p->match_qname) { if(!get_owner(query_pkt) || !get_owner(reply) || ldns_dname_compare( get_owner(query_pkt), get_owner(reply)) != 0) { verbose(3, "bad qname\n"); continue; } } if(p->match_serial && get_serial(query_pkt) != p->ixfr_soa_serial) { verbose(3, "bad serial\n"); continue; } if(p->match_do && !ldns_pkt_edns_do(query_pkt)) { verbose(3, "no DO bit set\n"); continue; } if(p->match_transport != transport_any && p->match_transport != transport) { verbose(3, "bad transport\n"); continue; } if(p->match_all && !match_all(query_pkt, reply, p->match_ttl)) { verbose(3, "bad allmatch\n"); continue; } verbose(3, "match!\n"); return p; } return NULL; } void adjust_packet(struct entry* match, ldns_pkt* answer_pkt, ldns_pkt* query_pkt) { /* copy & adjust packet */ if(match->copy_id) ldns_pkt_set_id(answer_pkt, ldns_pkt_id(query_pkt)); if(match->sleeptime > 0) { verbose(3, "sleeping for %d seconds\n", match->sleeptime); sleep(match->sleeptime); } } /* * Parses data buffer to a query, finds the correct answer * and calls the given function for every packet to send. */ void handle_query(uint8_t* inbuf, ssize_t inlen, struct entry* entries, int* count, enum transport_type transport, void (*sendfunc)(uint8_t*, size_t, void*), void* userdata, FILE* verbose_out) { ldns_status status; ldns_pkt *query_pkt = NULL; ldns_pkt *answer_pkt = NULL; struct reply_packet *p; ldns_rr *query_rr = NULL; uint8_t *outbuf = NULL; size_t answer_size = 0; struct entry* entry = NULL; ldns_rdf *stop_command = ldns_dname_new_frm_str("server.stop."); status = ldns_wire2pkt(&query_pkt, inbuf, (size_t)inlen); if (status != LDNS_STATUS_OK) { verbose(1, "Got bad packet: %s\n", ldns_get_errorstr_by_id(status)); ldns_rdf_free(stop_command); return; } query_rr = ldns_rr_list_rr(ldns_pkt_question(query_pkt), 0); verbose(1, "query %d: id %d: %s %d bytes: ", ++(*count), (int)ldns_pkt_id(query_pkt), (transport==transport_tcp)?"TCP":"UDP", inlen); if(verbose_out) ldns_rr_print(verbose_out, query_rr); if(verbose_out) ldns_pkt_print(verbose_out, query_pkt); if (ldns_rr_get_type(query_rr) == LDNS_RR_TYPE_TXT && ldns_rr_get_class(query_rr) == LDNS_RR_CLASS_CH && ldns_dname_compare(ldns_rr_owner(query_rr), stop_command) == 0) { exit(0); } /* fill up answer packet */ entry = find_match(entries, query_pkt, transport); if(!entry || !entry->reply_list) { verbose(1, "no answer packet for this query, no reply.\n"); ldns_pkt_free(query_pkt); ldns_rdf_free(stop_command); return; } for(p = entry->reply_list; p; p = p->next) { verbose(3, "Answer pkt:\n"); if (p->reply_from_hex) { /* try to parse the hex packet, if it can be * parsed, we can use adjust rules. if not, * send packet literally */ status = ldns_buffer2pkt_wire(&answer_pkt, p->reply_from_hex); if (status == LDNS_STATUS_OK) { adjust_packet(entry, answer_pkt, query_pkt); if(verbose_out) ldns_pkt_print(verbose_out, answer_pkt); status = ldns_pkt2wire(&outbuf, answer_pkt, &answer_size); verbose(2, "Answer packet size: %u bytes.\n", (unsigned int)answer_size); if (status != LDNS_STATUS_OK) { verbose(1, "Error creating answer: %s\n", ldns_get_errorstr_by_id(status)); ldns_pkt_free(query_pkt); ldns_rdf_free(stop_command); return; } ldns_pkt_free(answer_pkt); answer_pkt = NULL; } else { verbose(3, "Could not parse hex data (%s), sending hex data directly.\n", ldns_get_errorstr_by_id(status)); answer_size = ldns_buffer_capacity(p->reply_from_hex); outbuf = LDNS_XMALLOC(uint8_t, answer_size); memcpy(outbuf, ldns_buffer_export(p->reply_from_hex), answer_size); } } else { answer_pkt = ldns_pkt_clone(p->reply); adjust_packet(entry, answer_pkt, query_pkt); if(verbose_out) ldns_pkt_print(verbose_out, answer_pkt); status = ldns_pkt2wire(&outbuf, answer_pkt, &answer_size); verbose(1, "Answer packet size: %u bytes.\n", (unsigned int)answer_size); if (status != LDNS_STATUS_OK) { verbose(1, "Error creating answer: %s\n", ldns_get_errorstr_by_id(status)); ldns_pkt_free(query_pkt); ldns_rdf_free(stop_command); return; } ldns_pkt_free(answer_pkt); answer_pkt = NULL; } if(p->packet_sleep) { verbose(3, "sleeping for next packet %d secs\n", p->packet_sleep); sleep(p->packet_sleep); verbose(3, "wakeup for next packet " "(slept %d secs)\n", p->packet_sleep); } sendfunc(outbuf, answer_size, userdata); LDNS_FREE(outbuf); outbuf = NULL; answer_size = 0; } ldns_pkt_free(query_pkt); ldns_rdf_free(stop_command); } /** delete the list of reply packets */ void delete_replylist(struct reply_packet* replist) { struct reply_packet *p=replist, *np; while(p) { np = p->next; ldns_pkt_free(p->reply); ldns_buffer_free(p->reply_from_hex); free(p); p=np; } } void delete_entry(struct entry* list) { struct entry *p=list, *np; while(p) { np = p->next; delete_replylist(p->reply_list); free(p); p = np; } }