// // Copyright (c) 2016 by Carsten Larsen // Copyright (c) 2002 by Edwin Groothuis, edwin@mavetju.org. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. 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. // // THIS SOFTWARE IS PROVIDED BY FATAL DIMENSIONS 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 // STAFF OF FATAL DIMENSIONS 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. // // // Original file version: // $Id: dnstracer.c, v 1.48 2004/07/08 11:15:17 mavetju Exp $ // #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dnstracer.h" #include "dnstracer_broken.h" #include "getaddrinfo.h" #include "mem.h" #ifdef NOIPV6 #define gethostbyname2(a, b) gethostbyname(a) #endif int verbose = 0; int global_overview = DEFAULT_OVERVIEW; int global_retries = DEFAULT_RETRIES; int global_caching = DEFAULT_CACHING; int global_negative_caching = DEFAULT_NEGATIVE_CACHING; int global_querytype = DEFAULT_QUERYTYPE; int global_noipv6 = DEFAULT_NOIPV6; int global_timeout = 0; char * global_source_address = NULL; /*****************************************************************************/ struct arecord { char * server_name; char * server_ip; int type; char * rr_name; char * rr_data; struct arecord * next; }; struct busy { char * server; struct busy * next; }; struct answer { char * server; struct answer * next; }; struct dnssession { struct dnsheader * send_header; // The DNS header being sent struct dnsquestion * send_question; // The DNS question being sent struct dnsheader * recv_header; // The DNS header received struct dnsquestion * recv_question; // The DNS question received struct dnsrr * answer; // First Answer RR received struct dnsrr * authority; // First Authority RR received struct dnsrr * additional; // First Additional RR received struct dnssession * next; char * server; // First server being queried char * host; // Hostname being queried int recv_len; // Raw packet with char * recv_pkt; // with pointers to... char * recv_pkt_header; // - header char * recv_pkt_question; // - question char * recv_pkt_answer; // - first answer RR char * recv_pkt_authority; // - first authority RR char * recv_pkt_additional; // - first additional RR int socket; int ipv6; }; struct dnsheader { unsigned short identification; union { unsigned short flags; struct bit { #ifdef WORDS_BIGENDIAN unsigned char qr:1; unsigned char opcode:4; unsigned char aa:1; unsigned char tc:1; unsigned char rd:1; unsigned char ra:1; unsigned char zero:3; unsigned char rcode:4; #else unsigned char rcode:4; unsigned char zero:3; unsigned char ra:1; unsigned char rd:1; unsigned char tc:1; unsigned char aa:1; unsigned char opcode:4; unsigned char qr:1; #endif } bit; } flags; unsigned short nquestions; unsigned short nanswerRR; unsigned short nauthorityRR; unsigned short nadditionalRR; }; struct dnsquestion { unsigned int querylength; unsigned char * query; unsigned short type; unsigned short class; }; struct dnsrr { unsigned char * domainname; unsigned char * domainname_string; unsigned short type; unsigned short class; unsigned int ttl; unsigned short datalength; unsigned char * data; unsigned char * data_string; // parsed version of data if possible struct dnsrr * next; }; char *rr_types[256] = { "#0x00", "a", "ns", "#0x03", "#0x04", "cname", "soa", "#0x07", "#0x08", "#0x09", "#0x0a", "#0x0b", "#ptr", "#hinfo", "#0x0e", "#mx", "#txt", "#0x11", "#0x12", "#0x13", "#0x14", "#0x15", "#0x16", "#0x17", "#0x18", "#0x19", "#0x1a", "#0x1b", "#0x1c", "#0x1d", "#0x1e", "#0x1f", "#0x20", "#0x21", "#0x22", "#0x23", "#0x24", "#0x25", "#0x26", "#0x27", "#0x28", "#0x29", "#0x2a", "#0x2b", "#0x2c", "#0x2d", "#0x2e", "#0x2f", "#0x30", "#0x31", "#0x32", "#0x33", "#0x34", "#0x35", "#0x36", "#0x37", "#0x38", "#0x39", "#0x3a", "#0x3b", "#0x3c", "#0x3d", "#0x3e", "#0x3f", "#0x40", "#0x41", "#0x42", "#0x43", "#0x44", "#0x45", "#0x46", "#0x47", "#0x48", "#0x49", "#0x4a", "#0x4b", "#0x4c", "#0x4d", "#0x4e", "#0x4f", "#0x50", "#0x51", "#0x52", "#0x53", "#0x54", "#0x55", "#0x56", "#0x57", "#0x58", "#0x59", "#0x5a", "#0x5b", "#0x5c", "#0x5d", "#0x5e", "#0x5f", "#0x60", "#0x61", "#0x62", "#0x63", "#0x64", "#0x65", "#0x66", "#0x67", "#0x68", "#0x69", "#0x6a", "#0x6b", "#0x6c", "#0x6d", "#0x6e", "#0x6f", "#0x70", "#0x71", "#0x72", "#0x73", "#0x74", "#0x75", "#0x76", "#0x77", "#0x78", "#0x79", "#0x7a", "#0x7b", "#0x7c", "#0x7d", "#0x7e", "#0x7f", "#0x80", "#0x81", "#0x82", "#0x83", "#0x84", "#0x85", "#0x86", "#0x87", "#0x88", "#0x89", "#0x8a", "#0x8b", "#0x8c", "#0x8d", "#0x8e", "#0x8f", "#0x90", "#0x91", "#0x92", "#0x93", "#0x94", "#0x95", "#0x96", "#0x97", "#0x98", "#0x99", "#0x9a", "#0x9b", "#0x9c", "#0x9d", "#0x9e", "#0x9f", "#0xa0", "#0xa1", "#0xa2", "#0xa3", "#0xa4", "#0xa5", "#0xa6", "#0xa7", "#0xa8", "#0xa9", "#0xaa", "#0xab", "#0xac", "#0xad", "#0xae", "#0xaf", "#0xb0", "#0xb1", "#0xb2", "#0xb3", "#0xb4", "#0xb5", "#0xb6", "#0xb7", "#0xb8", "#0xb9", "#0xba", "#0xbb", "#0xbc", "#0xbd", "#0xbe", "#0xbf", "#0xc0", "#0xc1", "#0xc2", "#0xc3", "#0xc4", "#0xc5", "#0xc6", "#0xc7", "#0xc8", "#0xc9", "#0xca", "#0xcb", "#0xcc", "#0xcd", "#0xce", "#0xcf", "#0xd0", "#0xd1", "#0xd2", "#0xd3", "#0xd4", "#0xd5", "#0xd6", "#0xd7", "#0xd8", "#0xd9", "#0xda", "#0xdb", "#0xdc", "#0xdd", "#0xde", "#0xdf", "#0xe0", "#0xe1", "#0xe2", "#0xe3", "#0xe4", "#0xe5", "#0xe6", "#0xe7", "#0xe8", "#0xe9", "#0xea", "#0xeb", "#0xec", "#0xed", "#0xee", "#0xef", "#0xf0", "#0xf1", "#0xf2", "#0xf3", "#0xf4", "#0xf5", "#0xf6", "#0xf7", "#0xf8", "#0xf9", "#0xfa", "#0xfb", "#0xfc", "#0xfd", "#0xfe", "#0xff" }; /*****************************************************************************/ char *get_resource(int type, struct dnssession *session, char *buffer, int dots); char *printablename(char *name, int withdots); unsigned int debugging = 0; /*****************************************************************************/ // // Extract information from received packets. // int strnrcasecmp(const char *big, const char *little, size_t len) { char *p; size_t lenl, lenb; lenl = strlen(little); lenb = strlen(big); if (lenl > lenb) return -1; if (len > lenl) return -1; p = (char *)big + lenb - len; return strncasecmp(p, little, len); } unsigned int getlong(char *s) { unsigned char *t = (unsigned char*)s; return 256*256*256*t[0] + 256*256*t[1] + 256*t[2] + t[3]; } unsigned short getshort(char *s) { unsigned char *t = (unsigned char*)s; return 256*t[0] + t[1]; } char * getname(struct dnssession *session, char **thisname) { int compressing = 0; char * p; static char hostname[NS_MAXDNAME]; // // Copy the name out of a received packet. It can be compressed. // // // If the name is empty, just return a dot. // if (*thisname[0] == 0) { strcpy(hostname, "\1."); (*thisname)++; return hostname; } p = *thisname; memset(hostname, 0, sizeof(hostname)); while (p[0] != 0) { // // If a name is being compressed, set the pointer to the start of // the packet plus the offset. If this is the first compression, // move the end-of-this-name pointer two places further and stop // changing it from now. // if ((p[0]&0xc0) != 0) { unsigned int offset; char oldp; oldp = p[0]; p[0] &= 0x3f; offset = getshort(p); p[0] = oldp; p = session->recv_pkt + offset; if (compressing == 0) *thisname += 2; compressing = 1; continue; } // // Just copy p[0]+1 bytes (+1 to save the length-byte). If // compression is not going on, move the end-of-this-name // pointer. The check for the next character being 0 is a // hack to make sure the end-of-name marked is skipped over // before returning the packet. // // memcpy(hostname + strlen(hostname), p, p[0] + 1); if (compressing == 0) { *thisname += p[0] + 1; if (*thisname[0] == 0) *thisname += 1; } p += p[0] + 1; } return hostname; } char * extract_rr(struct dnssession *session, char *thisrr, struct dnsrr **rr) { struct dnsrr * RR; char * domainname; char * p; RR = (struct dnsrr *)calloc(1, sizeof(struct dnsrr)); RR->next = *rr; // // Extract just one resource-record. // If the record is a ns_t_ns or ns_t_cname, cache the name // into data_string. // domainname = getname(session, &thisrr); RR->domainname = (unsigned char*)strdup(domainname); RR->domainname_string = (unsigned char*)strdup(printablename(domainname, 1)); RR->type = getshort(thisrr); RR->class = getshort(thisrr + 2); RR->ttl = getlong(thisrr + 4); RR->datalength = getshort(thisrr + 8); RR->data = (unsigned char *)calloc(1, RR->datalength); memcpy(RR->data, thisrr + 10, RR->datalength); p = thisrr + 10; RR->data_string = (unsigned char*)strdup(get_resource(RR->type, session, p, 1)); thisrr += 10 + RR->datalength; *rr = RR; return thisrr; } void extract_data(struct dnssession *session) { struct dnsheader * header = NULL; struct dnsquestion *question = NULL; struct dnsrr * answer = NULL; struct dnsrr * authority = NULL; struct dnsrr * additional = NULL; char * pquestion; char * panswer; char * pauthority; char * padditional; char *pbuffer; int i; pbuffer = session->recv_pkt; // // Extract the header. // session->recv_pkt_header = pbuffer; header = (struct dnsheader *)calloc(1, sizeof(struct dnsheader)); memcpy(header, session->recv_pkt, sizeof(struct dnsheader)); header->identification = ntohs(header->identification); header->flags.flags = ntohs(header->flags.flags); header->nquestions = ntohs(header->nquestions); header->nanswerRR = ntohs(header->nanswerRR); header->nauthorityRR = ntohs(header->nauthorityRR); header->nadditionalRR = ntohs(header->nadditionalRR); pbuffer += sizeof(struct dnsheader); // // Extract the questions RR. // session->recv_pkt_question = pbuffer; pquestion = pbuffer; question = (struct dnsquestion *)calloc(1, sizeof(struct dnsquestion)); question->query = (unsigned char*)strdup(getname(session, &pquestion)); question->type = getshort(pquestion); question->class = getshort(pquestion + 2); pbuffer = pquestion + 4; // // Extract the answer RR // session->recv_pkt_answer = pbuffer; for (i = 0; i < header->nanswerRR; i++) { panswer = pbuffer; pbuffer = extract_rr(session, panswer, &answer); } // // Extract the authority RR // session->recv_pkt_authority = pbuffer; for (i = 0; i < header->nauthorityRR; i++) { pauthority = pbuffer; pbuffer = extract_rr(session, pauthority, &authority); } // // Extract the additional RR // session->recv_pkt_additional = pbuffer; for (i = 0; i < header->nadditionalRR; i++) { padditional = pbuffer; pbuffer = extract_rr(session, padditional, &additional); } session->recv_header = header; session->recv_question = question; session->answer = answer; session->additional = additional; session->authority = authority; } /*****************************************************************************/ // // Dump verbose data to the screen // char * printablename(char *name, int withdots) { static char hostname[NS_MAXDNAME]; char *p, q; // // Convert the name from label-format into 'human' readable format, // either with dots or the size of the label as seperators. // if (name == NULL || name[0] == 0) { if (withdots == 0) strcpy(hostname, "(0)root"); else strcpy(hostname, "."); return hostname; } hostname[0] = 0; p = name; while (p[0] != 0) { if (withdots == 0) sprintf(hostname + strlen(hostname), "(%d)", p[0]); else strcat(hostname, "."); q = p[p[0] + 1]; p[p[0] + 1] = 0; sprintf(hostname + strlen(hostname), "%s", p + 1); p = p + p[0] + 1; p[0] = q; } if (withdots == 0) return hostname; else return hostname + 1; // ignore the dot at the beginning of the string } char * get_class(int class) { switch (class) { case ns_c_in: return "Internet"; case ns_c_chaos: return "MIT Chaos-net"; case ns_c_hs: return "MIT Hesiod"; case ns_c_none: return "Pre-req in update"; case ns_c_any: return "Wildcard"; default: return "unknown"; } } char * get_type(int type) { switch (type) { case ns_t_a: return "A"; case ns_t_ns: return "NS"; case ns_t_cname: return "CNAME"; case ns_t_soa: return "SOA"; case ns_t_ptr: return "PTR"; default: return "unknown"; } } char * get_ttl(int ttl) { static char retval[NS_MAXDNAME]; // // Return the TTL as a weeks/days/hours/minuts/seconds value // retval[0] = 0; if (ttl > 7*24*60*60) { sprintf(retval, "%dw", ttl/(7*24*60*60)); ttl %= 7*24*60*60; } if (ttl > 24*60*60) { sprintf(retval + strlen(retval), "%dd", ttl/(24*60*60)); ttl %= 24*60*60; } if (ttl > 60*60) { sprintf(retval + strlen(retval), "%dh", ttl/(60*60)); ttl %= 60*60; } if (ttl > 60) { sprintf(retval + strlen(retval), "%dm", ttl/(60)); ttl %= 60; } if (ttl > 0) { sprintf(retval + strlen(retval), "%ds", ttl); } return retval; } char * get_resource(int type, struct dnssession *session, char *buffer, int dots) { static char retval[NS_MAXDNAME]; // // Returns a parsed resource-data string. Only needed for // A, NS and CNAME records. // switch (type) { case ns_t_a: sprintf(retval, "%hu.%hu.%hu.%hu", (unsigned char)buffer[0], (unsigned char)buffer[1], (unsigned char)buffer[2], (unsigned char)buffer[3]); return retval; case ns_t_aaaa: sprintf(retval, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:" "%02x%02x:%02x%02x:%02x%02x:%02x%02x", (unsigned char)buffer[ 0], (unsigned char)buffer[ 1], (unsigned char)buffer[ 2], (unsigned char)buffer[ 3], (unsigned char)buffer[ 4], (unsigned char)buffer[ 5], (unsigned char)buffer[ 6], (unsigned char)buffer[ 7], (unsigned char)buffer[ 8], (unsigned char)buffer[ 9], (unsigned char)buffer[10], (unsigned char)buffer[11], (unsigned char)buffer[12], (unsigned char)buffer[13], (unsigned char)buffer[14], (unsigned char)buffer[15] ); return retval; case ns_t_cname: strcpy(retval, printablename(getname(session, &buffer), dots)); return retval; case ns_t_txt: strcpy(retval, printablename(getname(session, &buffer), dots)); return retval; case ns_t_mx: { unsigned short us; us = getshort(buffer); buffer += 2; sprintf(retval, "%hu %s", us, printablename(getname(session, &buffer), dots)); return retval; } case ns_t_hinfo: strcpy(retval, printablename(getname(session, &buffer), dots)); return retval; case ns_t_ns: strcpy(retval, printablename(getname(session, &buffer), dots)); return retval; case ns_t_ptr: strcpy(retval, printablename(getname(session, &buffer), dots)); return retval; case ns_t_soa: { static char retval[3*NS_MAXDNAME]; char mname[NS_MAXDNAME]; char rname[NS_MAXDNAME]; unsigned long ul; strcpy(mname, printablename(getname(session, &buffer), dots)); strcpy(rname, printablename(getname(session, &buffer), dots)); ul = getlong(buffer); sprintf(retval, "serial: %ld mname: %s rname: %s", ul, mname, rname); return retval; } default: return "unknown"; } } void dump_question(struct dnsquestion *question) { printf("- Queryname: %s\n", printablename((char*)question->query, 0)); printf("- Type: %hu (%s)\n", question->type, get_type(question->type)); printf("- Class: %hu (%s)\n", question->class, get_class(question->class)); } void dump_header(struct dnsheader *header) { printf("- Identifier: 0x%04hX\n", header->identification); printf("- Flags: 0x%02hX (", header->flags.flags); if (header->flags.bit.qr) printf("R "); else printf("Q "); if (header->flags.bit.aa) printf("AA "); if (header->flags.bit.tc) printf("TC "); if (header->flags.bit.rd) printf("RD "); if (header->flags.bit.ra) printf("RA "); printf(")\n"); printf("- Opcode: %hu ", header->flags.bit.opcode); switch (header->flags.bit.opcode) { case 0: printf("(Standard query)\n"); break; case 1: printf("(Inverse query)\n"); break; case 2: printf("(Server status request)\n"); break; case 4: printf("(Notify)\n"); break; case 5: printf("(Update)\n"); break; case 14: printf("(Zone init)\n"); break; case 15: printf("(Zone Ref)\n"); break; default: printf("(unknown)\n"); } printf("- Return code: %hu ", header->flags.bit.rcode); switch (header->flags.bit.rcode) { case 0: printf("(No error)\n"); break; case 1: printf("(Format error)\n"); break; case 2: printf("(Server failure)\n"); break; case 3: printf("(Name error)\n"); break; case 4: printf("(Not implemented)\n"); break; case 5: printf("(Refused)\n"); break; case 6: printf("(Name exists)\n"); break; case 7: printf("(RRset exists)\n"); break; case 8: printf("(RRset does not exist)\n"); break; case 9: printf("(Not authoritive)\n"); break; case 10: printf("(Zone of record different from zone section)\n"); break; default: printf("(unknown)\n"); break; } printf("- Number questions: %hu\n", header->nquestions); printf("- Number answer RR: %hu\n", header->nanswerRR); printf("- Number authority RR: %hu\n", header->nauthorityRR); printf("- Number additional RR: %hu\n", header->nadditionalRR); } void dump_rr(struct dnsrr *rr, struct dnssession *session) { printf("- Domainname: %s\n", printablename((char*)rr->domainname, 0)); printf("- Type: %hu (%s)\n", rr->type, get_type(rr->type)); printf("- Class: %hu (%s)\n", rr->class, get_class(rr->class)); printf("- TTL: %u (%s)\n", rr->ttl, get_ttl(rr->ttl)); printf("- Resource length: %hu\n", rr->datalength); printf("- Resource data: %s\n", get_resource(rr->type, session, (char*)rr->data, 0)); } void dump_data(struct sockaddr_in *dest4, struct sockaddr_in6 *dest6, struct dnssession *session) { struct dnsrr *answerrr; struct dnsrr *authorityrr; struct dnsrr *additionalrr; if (verbose == 0) return; // // Dumps the output of session on the screen. // if (dest4 != NULL) { printf("IP HEADER\n"); printf("- Destination address: %s\n", inet_ntoa(dest4->sin_addr)); } if (dest6 != NULL) { printf("IP HEADER\n"); printf("- Destination address: %s\n", "XXX"); } if (session->send_header != NULL && session->recv_header == NULL) { printf("DNS HEADER (send)\n"); dump_header(session->send_header); } if (session->recv_header != NULL) { printf("DNS HEADER (recv)\n"); dump_header(session->recv_header); } if (session->send_question != NULL && session->recv_question == NULL) { printf("QUESTIONS (send)\n"); dump_question(session->send_question); } if (session->recv_question != NULL) { printf("QUESTIONS (recv)\n"); dump_question(session->recv_question); } answerrr = session->answer; while (answerrr != NULL) { printf("ANSWER RR\n"); dump_rr(answerrr, session); answerrr = answerrr->next; } authorityrr = session->authority; while (authorityrr != NULL) { printf("AUTHORITY RR\n"); dump_rr(authorityrr, session); authorityrr = authorityrr->next; } additionalrr = session->additional; while (additionalrr != NULL) { printf("ADDITIONAL RR\n"); dump_rr(additionalrr, session); additionalrr = additionalrr->next; } } /*****************************************************************************/ // // Packet creation routines // unsigned char * create_packet(struct dnssession *session) { unsigned char * pkt; struct dnsheader nheader; // networked header struct dnsquestion nquestion; // networked question int len; // // Transform a "host" packet into a "network" packet. // In other words, copy everything and byte-swap some field. // len = sizeof(struct dnsheader) + session->send_question->querylength + 4; pkt = (unsigned char *)calloc(1, len); memcpy(&nheader, session->send_header, sizeof(struct dnsheader)); memcpy(&nquestion, session->send_question, sizeof(struct dnsquestion)); nheader.identification = htons(session->send_header->identification); nheader.flags.flags = htons(session->send_header->flags.flags); nheader.nquestions = htons(session->send_header->nquestions); nheader.nanswerRR = htons(session->send_header->nanswerRR); nheader.nauthorityRR = htons(session->send_header->nauthorityRR); nheader.nadditionalRR = htons(session->send_header->nadditionalRR); nquestion.type = htons(session->send_question->type); nquestion.class = htons(session->send_question->class); memcpy(pkt, &nheader, sizeof(struct dnsheader)); memcpy(pkt + sizeof(struct dnsheader), nquestion.query, nquestion.querylength); memcpy(pkt + sizeof(struct dnsheader) + nquestion.querylength, &(nquestion.type), 4); return pkt; } /*****************************************************************************/ // // Network routines // int create_socket(int PF) { int s; if ((s = Socket(PF, SOCK_DGRAM, 0)) < 0) { perror("create_socket/socket"); //printf("If this is an IPv6 problem, run configure with --disable-ipv6\n"); exit(1); } if (global_source_address != NULL) { struct addrinfo hints, *src_res; int error; // thanks to the src/usr.bin/telnet/commands.c of FreeBSD 4.7! memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_NUMERICHOST; hints.ai_family = PF_INET; hints.ai_socktype = SOCK_DGRAM; error = getaddrinfo(global_source_address, 0, &hints, &src_res); if (error == EAI_NONAME) { hints.ai_flags = 0; error = getaddrinfo(global_source_address, 0, &hints, &src_res); } if (error != 0) { perror(global_source_address); if (error == EAI_SYSTEM) perror(global_source_address); exit(1); } if (Bind(s, src_res->ai_addr, src_res->ai_addrlen) < 0) { perror("create_socket/bind"); exit(1); } } return s; } int send_data(char *server, struct dnssession *session) { int cc; char * pkt; int len; struct sockaddr_in dest4; #ifndef NOIPV6 struct sockaddr_in6 dest6; #endif len = sizeof(struct dnsheader) + session->send_question->querylength + 4; #ifndef NOIPV6 if (session->ipv6) { memset(&dest6, 0, sizeof(struct sockaddr_in6)); dest6.sin6_family = AF_INET6; dest6.sin6_port = htons(53); inet_pton(AF_INET6, server, &dest6.sin6_addr); dump_data(NULL, &dest6, session); } else #endif { memset(&dest4, 0, sizeof(struct sockaddr_in)); dest4.sin_family = AF_INET; dest4.sin_port = htons(53); dest4.sin_addr.s_addr = inet_addr(server); dump_data(&dest4, NULL, session); } pkt = (char*)create_packet(session); if ((cc = SendTo(session->socket, pkt, len, 0, //#ifndef NOIPV6 // session->ipv6 ? (struct sockaddr *)&dest6 : (struct sockaddr *)&dest4, // session->ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in) //#else (struct sockaddr *)&dest4, sizeof(struct sockaddr_in) //#endif )) == -1) { perror("send_data/sendto"); } free(pkt); return cc; } int receive_data(struct dnssession *session, int retry) { char buffer[2048]; int len; fd_set in_set; struct timeval timeout; timeout.tv_sec = 5 * (1 << retry); timeout.tv_usec = 0; if (global_timeout && timeout.tv_sec > global_timeout) timeout.tv_sec = global_timeout; FD_ZERO(&in_set); FD_SET(session->socket, &in_set); if (WaitSelect(session->socket + 1, &in_set, NULL, NULL, &timeout, NULL) < 0) return 2; if (!FD_ISSET(session->socket, &in_set)) return 3; if ((len = Recv(session->socket, buffer, sizeof(buffer), 0)) == -1) return 1; if (getshort(buffer) != session->send_header->identification) { fprintf(stderr, "Expected id: %hx, received id: %hx\n", session->send_header->identification, getshort(buffer)); return 4; } session->recv_len = len; session->recv_pkt = (char *)calloc(1, len); memcpy(session->recv_pkt, buffer, len); extract_data(session); dump_data(NULL, NULL, session); return 0; } /*****************************************************************************/ // // Record creation routines // void create_header(struct dnssession *session) { session->send_header = (struct dnsheader *)calloc(1, sizeof(struct dnsheader)); // // Create a random identifier between 0 and 32675. It could be up to // 65535, but the high bit sometimes screws things up when comparing // the value received. Looks like it has something to do with // one-complement and two-complement, but don't know how to solve it. // session->send_header->identification = random() & 0x7F7F; session->send_header->nquestions = 1; } void create_question(struct dnssession *session, char *name) { unsigned char *p, *q; session->send_question = (struct dnsquestion *)calloc(1, sizeof(struct dnsquestion)); session->send_question->querylength = strlen(name) + 2; session->send_question->query = (unsigned char*)calloc(1, session->send_question->querylength + 2); strcpy((char*)session->send_question->query + 1, name); p = session->send_question->query + 1; q = session->send_question->query; while (p[0] != 0) { if (p[0] == '.') { q[0] = p - q - 1; q = p; } p++; } q[0] = p - q - 1; session->send_question->type = global_querytype; session->send_question->class = ns_c_in; } /*****************************************************************************/ // // A record caching routines. // It's just a linked list which hold a bunch of IP address from which // we have gotten answers. // struct arecord *arecords = NULL; void add_arecord(struct dnssession *session, struct dnsrr *rr, char *server_name, char *server_ip) { struct arecord * arecord; arecord = (struct arecord *)calloc(1, sizeof(struct arecord)); arecord->server_name = strdup(server_name); arecord->server_ip = strdup(server_ip); arecord->rr_name = strdup(printablename((char*)rr->domainname, 1)); if (rr->data_string == NULL) arecord->rr_data = NULL; else arecord->rr_data = strdup((char*)rr->data_string); arecord->next = arecords; arecords = arecord; } void display_arecords(void) { struct arecord * arecord; int i; char s[10]; arecord = arecords; while (arecord != NULL) { printf("%s (%s)%n", arecord->server_name, arecord->server_ip, &i); if (40 - i < 1) printf(" "); else { sprintf(s, "%%%ds", 40 - i); printf(s, " "); } printf("%s -> %s\n", arecord->rr_name, arecord->rr_data); arecord = arecord->next; } } /*****************************************************************************/ // // Answer caching routines. // It's just a linked list which hold a bunch of IP address from which // we have gotten answers. // struct answer *answers = NULL; void add_answer(char *server) { struct answer *answer; answer = (struct answer *)calloc(1, sizeof(struct answer)); answer->server = strdup(server); answer->next = answers; answers = answer; } int has_answer(char *server) { struct answer *answer; answer = answers; while (answer != NULL) { if (strcmp(answer->server, server) == 0) return 1; answer = answer->next; } return 0; } /*****************************************************************************/ // // Busy signal routines. // It's just a linked list which hold a bunch of IP address of servers // we are currently querying. Just to prevent that we query until we're // out of memory. // struct busy *busies = NULL; void add_busy(char *server) { struct busy *busy; busy = (struct busy *)calloc(1, sizeof(struct busy)); busy->server = strdup(server); busy->next = busies; busies = busy; } void remove_busy(char *server) { struct busy *busy, *prev; if (strcmp(busies->server, server) == 0) { busy = busies; busies = busies->next; free(busy); return; } prev = busies; busy = prev->next; while (busy != NULL) { if (strcmp(busy->server, server) == 0) { prev->next = busy->next; free(busy); return; } } } int is_busy(char *server) { struct busy *busy; busy = busies; while (busy != NULL) { if (strcmp(busy->server, server) == 0) return 1; busy = busy->next; } return 0; } /*****************************************************************************/ // // The core of this program // int create_session(char *host, char *server_ip, int ipv6, char *server_name, char *server_authfor, int depth, char *prefix, int last) { struct dnssession * session; struct dnsrr * rrauth; struct dnsrr * rradd; int i, retval, errorcode, refersbackwards = 0; char s[NS_MAXDNAME]; // // Print the graphs in front of the servernames // if (depth != 0) { printf("%s%c", prefix, last == 1 ? ' ' : '|'); printf("\\___ "); } printf("%s ", server_name); if (server_authfor != NULL) printf("[%s] ", server_authfor); if (server_ip[0] == 0) { printf("(No IP address)"); return 1; } if (global_noipv6 && ipv6) { printf("(%s) Not queried", server_ip); return 1; } #ifdef NOIPV6 if (ipv6) { printf("(%s) Not queried", server_ip); return 1; } #endif printf("(%s) ", server_ip); fflush(stdout); // // If this address is already being queried, ignore it to prevent // recursion. Do not add it as being cached, because one or more // records in it might be unreachable. // if (is_busy(server_ip) == 1) { printf("Lame server "); fflush(stdout); return 0; } // // Ignore things we have already displayed // if (global_caching && has_answer(server_ip)) { printf("(cached)"); return 0; } // // To prevent infinite recursion, mark this server as being probed // add_busy(server_ip); // // Create a nice DNS packet. Each session has its own port, so we // don't have to worry about packets received from previous sessions. // session = (struct dnssession *)calloc(1, sizeof(struct dnssession)); session->socket = create_socket(ipv6 ? AF_INET6 : AF_INET); session->ipv6 = ipv6; session->server = strdup(server_ip); session->host = strdup(host); create_header(session); create_question(session, host); // // Send the packet and wait for an answer. // errorcode = 0; for (i = 0; i < global_retries; i++) { send_data(server_ip, session); if ((errorcode = receive_data(session, i)) == 0) break; printf("* "); fflush(stdout); } CloseSocket(session->socket); // // Timeouts and other weird stuff. Make sure we remove the busy-flag. // if (errorcode != 0) { remove_busy(server_ip); if (global_negative_caching) add_answer(server_ip); return 1; } // // We have an answerRR from this server, print a message. // Also cache it for later. // if (session->recv_header->nanswerRR != 0) { struct dnsrr *answer; if (session->recv_header->flags.bit.aa) printf("Got authoritative answer "); else printf("Got answer "); answer = session->answer; while (answer != NULL) { if (answer->type != global_querytype) printf("[received type is %s] ", rr_types[answer->type]); add_arecord(session, answer, server_name, server_ip); answer = answer->next; } if (global_caching) add_answer(server_ip); } // // If the domainname in the authority section is the same as // the one from this server, mark it as lame. // if (session->authority != NULL && server_authfor != NULL) { if (session->recv_header->flags.bit.aa == 0 && strcasecmp(server_authfor, (char*)session->authority->domainname_string) == 0) { printf("Lame server "); remove_busy(server_ip); return 0; } } // // If the current server has an authoritative answer, don't go // further. // if (session->recv_header->flags.bit.aa) { remove_busy(server_ip); return 0; } // // When no answers were received, go through the list of authorities // and ask them for it. Maybe the IP address of the authority was // in the additional RRs, maybe there are two IP address in // the list of additional RRs, maybe there are none and a // gethostbyname() has to be done. // retval = 0; rrauth = session->authority; while (rrauth != NULL) { int found = 0; char nextserver_ip[NS_MAXDNAME]; char nextserver_name[NS_MAXDNAME]; // // Only serve NS records // if (rrauth->type != ns_t_ns) { rrauth = rrauth->next; continue; } // // If the domainname in the authority section is not a postfix // of what we have, don't go there. This might happen if we are // looking through cnames from different domains. // if (strcmp((char*)rrauth->domainname_string, ".") == 0) { rrauth = rrauth->next; if (!refersbackwards++) printf("Refers backwards "); continue; } if (server_authfor != NULL && strcmp(server_authfor, ".") != 0 && strnrcasecmp((char*)rrauth->domainname_string, server_authfor, strlen(server_authfor)) != 0) { if (!refersbackwards++) printf("Refers backwards "); rrauth = rrauth->next; continue; } // // Count the number of IP addresses in the additionalRR // for this authorityRR // rradd = session->additional; while (rradd != NULL) { if (strcmp(printablename((char*)rradd->domainname, 1), (char*)rrauth->data_string) == 0) found++; rradd = rradd->next; } rradd = session->additional; do { // // Find the first IP address // while (rradd != NULL) { if ((char*)strcmp(printablename((char*)rradd->domainname, 1), (char*)rrauth->data_string) == 0) break; rradd = rradd->next; } // // Prepare the graphs in front of the servers name // sprintf(s, "%s%c%s", prefix, last == 1 ? ' ' : '|', depth == 0 ? "" : " "); // // Recurse into this server... // if (rradd != NULL) { // This is easy, we got the IP address in the additional // section. Don't worry about additional records with the // same name, they're done automaticly after this one. printf("\n"); strcpy(nextserver_name, printablename((char*)rradd->domainname, 1)); strcpy(nextserver_ip, (char*)rradd->data_string); retval += create_session(host, nextserver_ip, (rradd->type == ns_t_aaaa) ? 1 : 0, nextserver_name, (char*)rrauth->domainname_string, depth + 1, s, (rrauth->next == NULL && found <= 1) ? 1 : 0); } else { int ip, ipfound = 0; strcpy(nextserver_name, (char*)rrauth->data_string); #ifdef NOIPV6 for (ip = 0; ip < 1; ip++) { #else for (ip = 0; ip < 2; ip++) { #endif int count, i; struct hostent *h; char **addr_list = NULL; h = gethostbyname2(nextserver_name, ip == 0 ? AF_INET : AF_INET6); if (h == NULL) continue; // // One or more IP address were found. Go through all // of them (make sure they're saved before calling // gethostbyname() again). // count = 0; while (h->h_addr_list[count] != NULL) count++; addr_list = (char **)calloc(count, sizeof(char *)); for (i = 0; i < count; i++) { addr_list[i] = (char *)calloc(1, h->h_length); memcpy(addr_list[i], h->h_addr_list[i], h->h_length); } for (i = 0; i < count; i++) { if (ip == 0) { unsigned char *s = (unsigned char*)addr_list[i]; sprintf(nextserver_ip, "%hu.%hu.%hu.%hu", s[0], s[1], s[2], s[3]); ipv6 = 0; } else { unsigned char *s = (unsigned char*)addr_list[i]; sprintf(nextserver_ip, "%02hx%02hx:%02hx%02hx:%02hx%02hx:%02hx%02hx:" "%02hx%02hx:%02hx%02hx:%02hx%02hx:%02hx%02hx", s[ 0], s[ 1], s[ 2], s[ 3], s[ 4], s[ 5], s[ 6], s[ 7], s[ 8], s[ 9], s[10], s[11], s[12], s[13], s[14], s[15]); ipv6 = 1; } printf("\n"); retval += create_session(host, nextserver_ip, ip == 1, nextserver_name, (char*)rrauth->domainname_string, depth + 1, s, (rrauth->next == NULL && found <= 1) ? 1 : 0); ipfound++; } } if (ipfound == 0) { // // No IP address was found for this hostname. // Just call the function and let them print // an error. // printf("\n"); nextserver_ip[0] = 0; retval += create_session(host, nextserver_ip, 0, nextserver_name, (char*)rrauth->domainname_string, depth + 1, s, (rrauth->next == NULL && found <= 1) ? 1 : 0); } } // // If there are no more IP addresses, then do the next // authorityRR. // if (--found <= 0) break; if (rradd != NULL) rradd = rradd->next; } while (rradd != NULL); rrauth = rrauth->next; } // // Cache it for later if there were no servers which went wrong. // Also remove the busy flag. // if (global_caching && retval == 0) add_answer(server_ip); remove_busy(server_ip); return retval; } /*****************************************************************************/ // // Startup and usage // void usage(void) { fprintf(stderr, #ifdef ANSI_CONSOLE "\33[33mDNSTRACER version " PACKAGE_VERSION DEBUG_TEXT " (c) Edwin Groothuis - http://www.mavetju.org\33[31m\n" #else "DNSTRACER version " PACKAGE_VERSION DEBUG_TEXT " (c) Edwin Groothuis - http://www.mavetju.org\n" #endif "Usage: dnstracer [options] [host]\n" "\t-c: disable local caching, default enabled\n" "\t-C: enable negative caching, default disabled\n" "\t-o: enable overview of received answers, default disabled\n" "\t-q : query-type to use for the DNS requests, default A\n" "\t-r : amount of retries for DNS requests, default 3\n" "\t-s : use this server for the initial request.\n" "\t Default is A.ROOT-SERVERS.NET.\n" "\t-t : Limit time to wait per try\n" "\t-v: verbose\n" "\t-S : use this source address.\n" ); #ifndef NOIPV6 fprintf(stderr, "\t-4: don't query IPv6 servers\n" ); #endif exit(1); } int main(int argc, char **argv) { int ch; // localhost as default does not make sense in AmigaOS //char * server_name = "127.0.0.1"; char * server_name = "A.ROOT-SERVERS.NET"; char ipaddress[NS_MAXDNAME]; char argv0[NS_MAXDNAME]; int server_root = 0; int ipv6 = 0; int init; prog = argv[0]; init = open_libs(); if (init != 0) return init; while ((ch = getopt(argc, argv, "4cCoqz:r:S:s:t:v")) != -1) { switch (ch) { case '4': #ifndef NOIPV6 global_noipv6 = 1; #else printf("Option -4 ignored\n"); #endif break; case 'c': global_caching = 0; break; case 'C': global_negative_caching = 1; break; case 'o': global_overview = 1; break; case 'q': if ((global_querytype = atoi(optarg)) < 1) { #define compare(s, v) \ if (strcmp((s), optarg) == 0) global_querytype = (v) compare("a", ns_t_a ); compare("aaaa", ns_t_aaaa ); compare("a6", ns_t_aaaa ); compare("ptr", ns_t_ptr ); compare("cname",ns_t_cname ); compare("hinfo",ns_t_hinfo ); compare("mx", ns_t_mx ); compare("ns", ns_t_ns ); compare("txt", ns_t_txt ); compare("soa", ns_t_soa ); if (global_querytype < 1) { fprintf(stderr, "Strange querytype, setting to default\n"); global_querytype = DEFAULT_QUERYTYPE; } } break; case 'r': if ((global_retries = atoi(optarg)) < 1) { fprintf(stderr, "Strange amount of retries, setting to default\n"); global_retries = DEFAULT_RETRIES; } break; case 'S': global_source_address = optarg; break; case 's': server_name = optarg; if (strcmp(server_name, ".") == 0) { server_name = strdup("A.ROOT-SERVERS.NET"); server_root = 1; } break; case 't': global_timeout = atoi(optarg); break; case 'v': verbose = 1; break; #if defined( DEBUG ) || defined( _DEBUG ) case 'z': if( ! ( debugging = ( unsigned int )atoi( optarg ) ) ) usage(); break; #endif default: usage(); } } argc -= optind; argv += optind; if (argv[0] == NULL) usage(); // check for a trailing dot strcpy(argv0, argv[0]); if (argv0[strlen(argv[0]) - 1] == '.') argv0[strlen(argv[0]) - 1] = 0; printf("Tracing to %s[%s] via %s, maximum of %d retries\n", argv0, rr_types[global_querytype], server_name, global_retries); { struct hostent *h = NULL; #ifndef NOIPV6 h = gethostbyname2(server_name, AF_INET6); #endif if (h == NULL || global_noipv6) h = gethostbyname2(server_name, AF_INET); if (h == NULL) { fprintf(stderr, "Cannot find IP address for %s\n", server_name); return 1; } if (h->h_addrtype == AF_INET) { unsigned char *s = (unsigned char*)h->h_addr_list[0]; sprintf(ipaddress, "%hu.%hu.%hu.%hu", s[0], s[1], s[2], s[3]); ipv6 = 0; } else { unsigned char *s = (unsigned char*)h->h_addr_list[0]; sprintf(ipaddress, "%02hx%02hx:%02hx%02hx:%02hx%02hx:%02hx%02hx:" "%02hx%02hx:%02hx%02hx:%02hx%02hx:%02hx%02hx", s[ 0], s[ 1], s[ 2], s[ 3], s[ 4], s[ 5], s[ 6], s[ 7], s[ 8], s[ 9], s[10], s[11], s[12], s[13], s[14], s[15]); ipv6 = 1; } } create_session(argv0, ipaddress, ipv6, server_name, server_root == 0 ? NULL : ".", 0, "", 1); printf("\n"); if (global_overview != 0) { printf("\n"); display_arecords(); } return 0; }