312 lines
7.7 KiB
C
312 lines
7.7 KiB
C
|
|
#ifdef HAVE_GETDELIM
|
||
|
|
#define _GNU_SOURCE
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#include "config.h"
|
||
|
|
#ifdef HAVE_GETOPT_H
|
||
|
|
# include <getopt.h>
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#include <ldns/ldns.h>
|
||
|
|
#include <errno.h>
|
||
|
|
#include <pcap.h>
|
||
|
|
|
||
|
|
#define FAILURE 10000
|
||
|
|
|
||
|
|
|
||
|
|
#ifndef ETHERTYPE_IPV6
|
||
|
|
#define ETHERTYPE_IPV6 0x86dd
|
||
|
|
#endif
|
||
|
|
#define DNS_UDP_OFFSET 42
|
||
|
|
|
||
|
|
#ifndef HAVE_GETDELIM
|
||
|
|
size_t getdelim(char **lineptr, size_t *n, int delim, FILE *stream);
|
||
|
|
#endif
|
||
|
|
|
||
|
|
/* output: see usage() */
|
||
|
|
|
||
|
|
void
|
||
|
|
usage(FILE *fp)
|
||
|
|
{
|
||
|
|
fprintf(fp, "pcat [-a IP] [-p PORT] [-r] PCAP_FILE\n\n");
|
||
|
|
fprintf(fp, " -a IP\tuse IP as nameserver, defaults to 127.0.0.1\n");
|
||
|
|
fprintf(fp, " -p PORT\tuse PORT as port, defaults to 53\n");
|
||
|
|
fprintf(fp, " -r \t\tthe file is a pcat output file to resend queries from\n");
|
||
|
|
fprintf(fp, " -h \t\tthis help\n");
|
||
|
|
fprintf(fp, " PCAP_FILE\tuse this file as source\n");
|
||
|
|
fprintf(fp, " If no file is given standard input is read\n");
|
||
|
|
fprintf(fp, "\nOUTPUT FORMAT:\n");
|
||
|
|
fprintf(fp, " Line based output format, each record consists of 3 lines:\n");
|
||
|
|
fprintf(fp, " 1. xxx\t\tdecimal sequence number\n");
|
||
|
|
fprintf(fp, " 2. hex dump\t\tquery in hex, network order\n");
|
||
|
|
fprintf(fp, " 3. hex dump\t\tanswer in hex, network order\n");
|
||
|
|
fprintf(fp, " 4. empty line\n");
|
||
|
|
fprintf(fp, " The reason for 4. is that pcat-print now can be used on the output of pcat.\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
void
|
||
|
|
data2hex(FILE *fp, u_char *p, size_t l)
|
||
|
|
{
|
||
|
|
size_t i;
|
||
|
|
for(i = 0; i < l; i++) {
|
||
|
|
fprintf(fp, "%02x", (unsigned int) p[i]);
|
||
|
|
}
|
||
|
|
fputs("\n", fp);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Converts a hex string to binary data
|
||
|
|
* len is the length of the string
|
||
|
|
* buf is the buffer to store the result in
|
||
|
|
* offset is the starting position in the result buffer
|
||
|
|
*
|
||
|
|
* 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;
|
||
|
|
}
|
||
|
|
|
||
|
|
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");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return bufpos;
|
||
|
|
}
|
||
|
|
|
||
|
|
u_char *
|
||
|
|
pcap2ldns_pkt_ip(const u_char *packet, struct pcap_pkthdr *h)
|
||
|
|
{
|
||
|
|
h->caplen -= DNS_UDP_OFFSET;
|
||
|
|
if (h->caplen < 0) {
|
||
|
|
return NULL;
|
||
|
|
} else {
|
||
|
|
return (u_char*)(packet + DNS_UDP_OFFSET);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
u_char *
|
||
|
|
pcap2ldns_pkt(const u_char *packet, struct pcap_pkthdr *h)
|
||
|
|
{
|
||
|
|
struct ether_header *eptr;
|
||
|
|
|
||
|
|
eptr = (struct ether_header *) h;
|
||
|
|
switch(eptr->ether_type) {
|
||
|
|
case ETHERTYPE_IP:
|
||
|
|
return pcap2ldns_pkt_ip(packet, h);
|
||
|
|
break;
|
||
|
|
case ETHERTYPE_IPV6:
|
||
|
|
break;
|
||
|
|
case ETHERTYPE_ARP:
|
||
|
|
fprintf(stderr, "ARP pkt, dropping\n");
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
fprintf(stderr, "Not IP pkt, dropping\n");
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
int
|
||
|
|
main(int argc, char **argv)
|
||
|
|
{
|
||
|
|
char errbuf[PCAP_ERRBUF_SIZE];
|
||
|
|
pcap_t *p = NULL;
|
||
|
|
struct pcap_pkthdr h;
|
||
|
|
const u_char *x;
|
||
|
|
size_t i = 0;
|
||
|
|
ldns_rdf *ip;
|
||
|
|
char *ip_str;
|
||
|
|
int c;
|
||
|
|
size_t failure;
|
||
|
|
FILE *infile = NULL;
|
||
|
|
int pcat_input_file = 0;
|
||
|
|
|
||
|
|
uint8_t *result;
|
||
|
|
uint16_t port;
|
||
|
|
ldns_buffer *qpkt;
|
||
|
|
u_char *q;
|
||
|
|
size_t size;
|
||
|
|
socklen_t tolen;
|
||
|
|
size_t query_pkt_len;
|
||
|
|
|
||
|
|
struct timeval timeout;
|
||
|
|
struct sockaddr_storage *data;
|
||
|
|
struct sockaddr_in *data_in;
|
||
|
|
|
||
|
|
ldns_status send_status;
|
||
|
|
|
||
|
|
port = 0;
|
||
|
|
ip = NULL;
|
||
|
|
ip_str = NULL;
|
||
|
|
failure = 0;
|
||
|
|
|
||
|
|
while ((c = getopt(argc, argv, "ha:p:r")) != -1) {
|
||
|
|
switch(c) {
|
||
|
|
case 'h':
|
||
|
|
usage(stdout);
|
||
|
|
exit(EXIT_SUCCESS);
|
||
|
|
case 'a':
|
||
|
|
ip_str = optarg;
|
||
|
|
ip = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_A, optarg);
|
||
|
|
if (!ip) {
|
||
|
|
fprintf(stderr, "-a requires an IP address\n");
|
||
|
|
exit(EXIT_FAILURE);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 'r':
|
||
|
|
pcat_input_file = 1;
|
||
|
|
break;
|
||
|
|
case 'p':
|
||
|
|
port = atoi(optarg);
|
||
|
|
if (port == 0) {
|
||
|
|
fprintf(stderr, "-p requires a port number\n");
|
||
|
|
exit(EXIT_FAILURE);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
usage(stdout);
|
||
|
|
exit(EXIT_FAILURE);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
argc -= optind;
|
||
|
|
argv += optind;
|
||
|
|
|
||
|
|
if (port == 0)
|
||
|
|
port = 53;
|
||
|
|
|
||
|
|
if (!ip) {
|
||
|
|
ip_str = "127.0.0.1";
|
||
|
|
ip = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_A, "127.0.0.1");
|
||
|
|
}
|
||
|
|
|
||
|
|
if (pcat_input_file) {
|
||
|
|
if (argc < 1) {
|
||
|
|
infile = fopen("/dev/stdin", "r");
|
||
|
|
} else {
|
||
|
|
infile = fopen(argv[0], "r");
|
||
|
|
}
|
||
|
|
if (!infile) {
|
||
|
|
fprintf(stderr, "Cannot open input file: %s\n", strerror(errno));
|
||
|
|
exit(EXIT_FAILURE);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
if (argc < 1) {
|
||
|
|
/* no file given - use standard input */
|
||
|
|
p = pcap_open_offline("/dev/stdin", errbuf);
|
||
|
|
} else {
|
||
|
|
p = pcap_open_offline(argv[0], errbuf);
|
||
|
|
}
|
||
|
|
if (!p) {
|
||
|
|
fprintf(stderr, "Cannot open pcap lib %s\n", errbuf);
|
||
|
|
exit(EXIT_FAILURE);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
qpkt = ldns_buffer_new(LDNS_MAX_PACKETLEN);
|
||
|
|
data = LDNS_MALLOC(struct sockaddr_storage);
|
||
|
|
timeout.tv_sec = 5;
|
||
|
|
timeout.tv_usec = 0;
|
||
|
|
|
||
|
|
/* setup the socket */
|
||
|
|
data->ss_family = AF_INET;
|
||
|
|
data_in = (struct sockaddr_in*) data;
|
||
|
|
data_in->sin_port = (in_port_t)htons(port);
|
||
|
|
memcpy(&(data_in->sin_addr), ldns_rdf_data(ip), ldns_rdf_size(ip));
|
||
|
|
tolen = sizeof(struct sockaddr_in);
|
||
|
|
|
||
|
|
i = 1; /* start counting at 1 */
|
||
|
|
while (1) {
|
||
|
|
if(pcat_input_file) {
|
||
|
|
/* read pcat format and repeat query in it */
|
||
|
|
char buf[65535*2+100];
|
||
|
|
uint8_t decoded[65535+100];
|
||
|
|
if(!fgets(buf, sizeof(buf), infile)) /* number */
|
||
|
|
break;
|
||
|
|
if(!fgets(buf, sizeof(buf), infile)) /* query */
|
||
|
|
break;
|
||
|
|
query_pkt_len = hexstr2bin(buf, strlen(buf)&0xfffffffe,
|
||
|
|
decoded, 0, sizeof(decoded));
|
||
|
|
ldns_buffer_write(qpkt, decoded, query_pkt_len);
|
||
|
|
|
||
|
|
if(!fgets(buf, sizeof(buf), infile)) /* answer */
|
||
|
|
break;
|
||
|
|
if(!fgets(buf, sizeof(buf), infile)) /* empty line */
|
||
|
|
break;
|
||
|
|
} else {
|
||
|
|
if(!(x = pcap_next(p, &h)))
|
||
|
|
break;
|
||
|
|
q = pcap2ldns_pkt_ip(x, &h);
|
||
|
|
ldns_buffer_write(qpkt, q, h.caplen);
|
||
|
|
query_pkt_len = h.caplen;
|
||
|
|
}
|
||
|
|
|
||
|
|
send_status =ldns_udp_send(&result, qpkt, data, tolen, timeout, &size);
|
||
|
|
if (send_status == LDNS_STATUS_OK) {
|
||
|
|
/* double check if we are dealing with correct replies
|
||
|
|
* by converting to a pkt... todo */
|
||
|
|
fprintf(stdout, "%d\n", (int)i);
|
||
|
|
/* query */
|
||
|
|
data2hex(stdout, ldns_buffer_begin(qpkt), query_pkt_len);
|
||
|
|
/* answer */
|
||
|
|
data2hex(stdout, result, size);
|
||
|
|
fflush(stdout);
|
||
|
|
} else {
|
||
|
|
/* todo print failure */
|
||
|
|
failure++;
|
||
|
|
fprintf(stderr, "Failure to send packet %u (attempt %u, error %s)\n", i, (unsigned int) failure, ldns_get_errorstr_by_id(send_status));
|
||
|
|
fprintf(stdout, "%d\n", (int)i);
|
||
|
|
/* query */
|
||
|
|
data2hex(stdout, ldns_buffer_begin(qpkt), query_pkt_len);
|
||
|
|
/* answer, thus empty */
|
||
|
|
fprintf(stdout, "\n");
|
||
|
|
}
|
||
|
|
fputs("\n", stdout);
|
||
|
|
ldns_buffer_clear(qpkt);
|
||
|
|
i++;
|
||
|
|
if (failure > FAILURE) {
|
||
|
|
fprintf(stderr, "More than %u failures, bailing out\n", FAILURE);
|
||
|
|
exit(EXIT_FAILURE);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if(pcat_input_file)
|
||
|
|
fclose(infile);
|
||
|
|
else pcap_close(p);
|
||
|
|
return 0;
|
||
|
|
}
|