/* * Adapted from W.R. Stevens * "Unix Network Programming", 1st ed. */ #include #include #include #include #include #include #include // bzero #include // strncpy #include // exit #include // close #include // uint8_t int print_dns_name(char *, char *); #define SERV_PORT 53 #define SERV_HOST_ADDR "129.170.170.2" // Pasted from Wireshark's Analyze > Follow > UDP stream > "C arrays" // This represents the UDP DNS payload for an NS query for cs.dartmouth.edu // as generated by "dig ns cs.dartmouth.edu" char req[] = { 0xa8, 0x3c, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x63, 0x73, 0x09, 0x64, 0x61, 0x72, 0x74, 0x6d, 0x6f, 0x75, 0x74, 0x68, 0x03, 0x65, 0x64, 0x75, 0x00, 0x00, 0x02, 0x00, 0x01 }; #define MAX_MSG 1500 // DNS header with fixed-width fields // See http://unixwiz.net/techtips/iguide-kaminsky-dns-vuln.html for images. typedef struct dns_resp_hdr { uint16_t tx_id; uint8_t flags[2]; uint16_t n_q; uint16_t n_a; uint16_t n_auth; uint16_t n_add; } dns_resp_hdr_t; int main(argc, argv) int argc; char *argv[]; { int sockfd; struct sockaddr_in serv_addr; char buff[1500]; int n, i; uint16_t type, class, data_len; uint32_t ttl; dns_resp_hdr_t *r; char *p; /* * Fill in the structure "serv_addr" with the address of the * server that we want to connect with. */ bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr(SERV_HOST_ADDR); serv_addr.sin_port = htons(SERV_PORT); /* * Open a UDP socket (a "datagram" socket, i.e., UDP; recall that TCP was SOCK_STREAM). */ if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){ perror("udp client: can't open DGRAM socket"); exit(1); } /* * Connect to the server. Unlike TCP, no packets are sent; only kernel's * data structures are filled in, so that next time you write/send to * this socket, the IP/UDP packet knows where to go. */ if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0){ perror("udp client: can't connect to server"); exit(1); } // Compare with sendto(), which does not need connect() above, because // it specifies the sockaddr with every datagram. if( (n = write(sockfd, (void*) req, sizeof(req))) < 0 ){ perror("udp client: write error"); exit(1); } // Compare with recvfrom, which does not need a connect() either. n = recv(sockfd, (void*) buff, MAX_MSG, 0); if( n < 0 ){ perror("Truncated response received."); goto out; } // This is the key to our parsing of the header: the struct gives us a "mask" for memory accesses. r = (dns_resp_hdr_t *) buff; printf( "Response length: %d\n", n ); // Print packet in hex for (i = 0; i < n; i++) { printf("%02X%s", (uint8_t)buff[i], (i + 1)%16 ? " " : "\n"); } printf("\n"); // Printing out the fixed-length parts of the common DNS header: printf("tx_id 0x%x, flags %02X %02X, questions %d, answers %d, authorities %d, additional %d\n", ntohs(r->tx_id), r->flags[0], r->flags[1], ntohs(r->n_q), ntohs(r->n_a), ntohs(r->n_auth), ntohs(r->n_add) ); p = buff + sizeof(dns_resp_hdr_t); // Print queries for( i = 0 ; i < ntohs(r->n_q); i++ ){ // parse RR starting from name printf( "Query RR name: " ); p += print_dns_name( p, buff ); // then print type and class type = ntohs( *(uint16_t *)p ); p += 2; class = ntohs (*(uint16_t *)p ); p += 2; printf( " type %d, class %d\n", type, class ); } // Print answers for( i = 0 ; i < ntohs(r->n_a); i++ ){ // parse RR starting from name printf( "Answer RR name: " ); p += print_dns_name(p, buff); type = ntohs( *(uint16_t *)p ); p += 2; class = ntohs (*(uint16_t *)p ); p += 2; printf( " type %d, class %d\n", type, class ); ttl = ntohs( *(uint32_t *)p ); p += 4; data_len = ntohs( *(uint16_t *)p ); p += 2; printf( " ttl %d, data_len %d ", ttl, data_len ); // Data interpretation varies by type if( type == 1 && data_len == 4){ printf( " %s\n", inet_ntoa( *(struct in_addr*) p )); p += 4; } else if( type == 2 ){ print_dns_name( p, buff ); } else{ printf( "WARN: don't know how to parse RR data for type %d length %d\n", type, data_len); } p += data_len; } // done printing answers for( i = 0 ; i < ntohs(r->n_auth); i++ ){ printf( "Skipping authority RR: not implemented\n"); } for( i = 0 ; i < ntohs(r->n_add); i++ ){ printf( "Skipping additional RR: not implemented\n"); } out: close(sockfd); return 0; } /* * Args: pointer to a DNS-encoded name (or backreference), pointer to * the start of the DNS payload (to resolve backreferences). * Returns: the number of bytes occupied by the name, to skip over. */ int print_dns_name(char *p, char *buff) { char *p0 = p; int8_t len = *(uint8_t*)p; p++; // write out parts of the name from this record while( len > 0 ){ fwrite( p, len, 1, stdout ); printf("."); p += len; len = *(uint8_t*)p; p++; } // at this point we either hit len==0 (the end of name) // or a negative back-pointer if( len < 0 ){ // back-pointer, starts with 0xc0 uint16_t offset; offset = ntohs(*(uint16_t *)(p-1)) & ~0xc000; // scan back one byte printf( "", offset ); print_dns_name( buff+offset, buff ); // ignore return value printf("\n"); p += 1; // skip offset } return p-p0; }