dinoco

Query DNS records
git clone git://git.relim.de/dinoco.git
Log | Files | Refs | README | LICENSE

commit e69f284e7f970cdff2647198b03b38f78e88ca55
parent 3c0a63afbbd8509a597a584ec333426ae371a590
Author: Nibo <kroekerrobin@gmail.com>
Date:   Sun, 20 Apr 2025 09:06:37 +0200

Improve various things

Diffstat:
MMakefile | 4++--
Mdinoco.1 | 14++++++++++----
Mdinoco.c | 289+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mdinoco.h | 3++-
4 files changed, 187 insertions(+), 123 deletions(-)

diff --git a/Makefile b/Makefile @@ -2,9 +2,9 @@ PREFIX = /usr/local MANPREFIX = $(PREFIX)/share/man dinoco: - $(CC) -O -Wall -Wextra -pedantic -o dinoco dinoco.c + $(CC) -std=gnu23 -O -Wall -Wextra -pedantic -o dinoco dinoco.c debug: - $(CC) -DDEBUG -g -Wall -Wextra -pedantic -o dinoco dinoco.c + $(CC) -std=gnu23 -DDEBUG -g -Wall -Wextra -pedantic -o dinoco dinoco.c clean: rm dinoco install: all diff --git a/dinoco.1 b/dinoco.1 @@ -3,15 +3,18 @@ dinoco - Query DNS records .SH SYNOPSIS .B dinoco +[\fB\,-s\/\fR \fI\,host\/\fR] +[\fB\,-h\/\fR] .B -t \fI\,type\/\fR \fI\,domain\/\fR -[ -.B -h -] .SH DESCRIPTION .PP -Query a DNS record or possibly multiple records. The DNS class will always be IN (the Internet). Only the RDATA field of the answer will be printed to stdout according to the DNS type. \fI\,domain\/\fR represents a domain except in the case of the \fB\,--type\/\fR PTR. In that case you provide an IP address. +Query DNS records. The DNS class will always be IN (the Internet). +Only the answer sections of a response will be read. +Only the RDATA field of the answer will be printed to stdout according to the DNS type. +\fI\,domain\/\fR represents a domain except in the case of the \fB\,--type\/\fR PTR. +In that case you provide an IP address. .TP \fB\,-t\/\fR, \fB\,--type\/\fR \fI\,type\/\fR A DNS type. One of the following: @@ -27,6 +30,9 @@ A DNS type. One of the following: Not supported types: WKS, MB, MD, MF, MG, MR, MINFO, NULL. .TP +\fB\,-s\/\fR, \fB\,--dns-server\/\fR \fI\,host\/\fR +Specify the domain or IP address of the DNS server. +.TP \fB\,-h\/\fR Disable the printing of the header line. .SH EXAMPLES diff --git a/dinoco.c b/dinoco.c @@ -92,76 +92,20 @@ response_code_to_string(enum ResponseCode code) return ""; } -/* TODO: Find a better way to get a dns server ip. This is ugly. */ -static char * -dns_server_ip_get(void) +static struct addrinfo *get_addr_info(const char *host) { - FILE *fp; - char *ipv4; - size_t bytes_read; - const char *cmd = "cat /etc/resolv.conf | grep 'nameserver.*\\..*' | cut -d' ' -f2 | tr -d '\n'"; - fp = popen(cmd, "r"); - if (!fp) { - LOG_DEBUG("popen failed."); - return NULL; - } - ipv4 = malloc(16 * sizeof(char)); - bytes_read = fread(ipv4, 1, 15, fp); - pclose(fp); - if (bytes_read >= MIN_IP_LENGTH) { - ipv4[bytes_read] = 0; - struct sockaddr_in sa; - if (inet_pton(AF_INET, ipv4, &sa.sin_addr) != 0) { - return ipv4; - } - } - return NULL; -} - -static struct ByteArray * -dns_server_request(char *request, size_t request_len) -{ - int port = 53; - int fd; - size_t bytes_sent, bytes_received; - socklen_t size; - struct ByteArray *res; - char *ip = dns_server_ip_get(); - if (!ip) { - LOG_DEBUG("dns_server_ip_get failed."); - return NULL; - } - struct hostent *host = (struct hostent *)gethostbyname(ip); - free(ip); - fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd == -1) { - perror("socket failed"); - return NULL; - } - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - addr.sin_addr = *((struct in_addr *)host->h_addr); - memset(&addr.sin_zero, 0, 8); - size = (socklen_t)sizeof(addr); - bytes_sent = sendto(fd, request, request_len, 0, (struct sockaddr *)&addr, size); - if (bytes_sent == request_len) { - char response[MAX_UDP_MSG_LENGTH * sizeof(char)]; - bytes_received = recvfrom(fd, response, MAX_UDP_MSG_LENGTH, 0, (struct sockaddr *)&addr, &size); - if (bytes_received > 0) { - res = malloc(sizeof(struct ByteArray)); - res->b = malloc(bytes_received * sizeof(char)); - memcpy(res->b, &response, bytes_received); - res->len = bytes_received; - return res; - } else { - fprintf(stderr, "Didn't receive a response.\n"); - return NULL; - } - } else { - fprintf(stderr, "Didn't send whole request.\n"); + struct addrinfo *addr_info, hints; + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_NUMERICSERV; + hints.ai_protocol = 0; + int ret = getaddrinfo(host, "53", &hints, &addr_info); + if (ret != 0) { + fprintf(stderr, "getaddrinfo failed. %s.\n", gai_strerror(ret)); return NULL; } + return addr_info; } static enum Type @@ -463,7 +407,7 @@ name_count_bytes(struct ByteArray *bytes, int start) } static struct DnsResourceRecord * -answer_parse(struct ByteArray *res, int *start) +answer_parse(struct ByteArray *res, size_t *start) { struct DnsResourceRecord *answer = malloc(sizeof(struct DnsResourceRecord)); answer->domain = domain_parse(res, *start); @@ -489,7 +433,7 @@ answer_parse_by_type( struct DnsResourceRecord *answer, struct ByteArray *res, enum Type type, - int *start_of_next_answer + size_t *start_of_next_answer ) { union DnsTypeResult *ur = malloc(sizeof(union DnsTypeResult)); @@ -865,7 +809,16 @@ answers_free( static bool response_is_valid(struct DnsHeader *req_header, struct DnsHeader *res_header) { + if (res_header->tc) { + LOG_ERR("Response is marked as truncated."); + return false; + } + if (!res_header->qr) { + LOG_ERR("Response is marked as request (QR). That makes no sense."); + return false; + } if (req_header->id != res_header->id) { + LOG_ERR("Response has a different id than the request."); return false; } if (res_header->rcode != RCODE_NO_ERROR) { @@ -879,10 +832,149 @@ response_is_valid(struct DnsHeader *req_header, struct DnsHeader *res_header) return true; } +/* TODO: Find a better way to get a dns server ip. This is ugly. */ +static char * +dns_server_ip_get(void) +{ + FILE *fp; + char *ipv4; + size_t bytes_read; + const char *cmd = "cat /etc/resolv.conf | grep 'nameserver.*\\..*' | cut -d' ' -f2 | tr -d '\n'"; + fp = popen(cmd, "r"); + if (!fp) { + LOG_DEBUG("popen failed."); + return NULL; + } + ipv4 = malloc(16 * sizeof(char)); + bytes_read = fread(ipv4, 1, 15, fp); + pclose(fp); + if (bytes_read >= MIN_IP_LENGTH) { + ipv4[bytes_read] = 0; + struct sockaddr_in sa; + if (inet_pton(AF_INET, ipv4, &sa.sin_addr) != 0) { + return ipv4; + } + } + return NULL; +} + +static struct ByteArray * +dns_server_request(char *request, size_t request_len, const char *dns_server) +{ + int fd; + size_t bytes_sent, bytes_received; + struct addrinfo *addr_info; + struct ByteArray *res; + if (dns_server) { + addr_info = get_addr_info(dns_server); + } else { + char *ip = dns_server_ip_get(); + if (!ip) { + LOG_DEBUG("dns_server_ip_get failed."); + return NULL; + } + addr_info = get_addr_info(ip); + free(ip); + } + if (!addr_info) { + LOG_DEBUG("get_addr_info failed."); + return NULL; + } + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd == -1) { + perror("socket failed"); + return NULL; + } + bytes_sent = sendto(fd, request, request_len, 0, addr_info->ai_addr, addr_info->ai_addrlen); + if (bytes_sent == request_len) { + char response[MAX_UDP_MSG_LENGTH * sizeof(char)]; + bytes_received = recvfrom(fd, response, MAX_UDP_MSG_LENGTH, 0, addr_info->ai_addr, &addr_info->ai_addrlen); + if (bytes_received > 0) { + res = malloc(sizeof(struct ByteArray)); + res->b = malloc(bytes_received * sizeof(char)); + memcpy(res->b, &response, bytes_received); + res->len = bytes_received; + freeaddrinfo(addr_info); + return res; + } else { + fprintf(stderr, "Didn't receive a response.\n"); + return NULL; + } + } else { + fprintf(stderr, "Didn't send whole request.\n"); + return NULL; + } +} + +static bool +dns_server_ask( + const char *dns_server, + const char *domain, + enum Type dns_type, + bool disable_header +) +{ + int answer_count, i; + size_t start_of_answer; + struct ByteArray *header, *question, *res; + struct DnsHeader *res_header; + struct DnsResourceRecord *answer; + union DnsTypeResult **answers; + struct DnsQuestion qu = { + .domain = domain, + .type = dns_type, + .class = CLASS_IN + }; + + dns_header_default.id += 1; + header = header_form(&dns_header_default); + question = question_form(&qu); + char req[header->len + question->len]; + memcpy(req, header->b, header->len); + memcpy(&req[header->len], question->b, question->len); + res = dns_server_request((char *)&req, header->len + question->len, dns_server); + if (!res) { + LOG_DEBUG("dns_server_request failed."); + return false; + } + byte_array_free(header); + if (res->len <= DNS_HEADER_LENGTH) { + fprintf(stderr, "Response is too short.\n"); + return false; + } + res_header = header_parse(res->b); + if (!response_is_valid(&dns_header_default, res_header)) { + return false; + } + if (memcmp(question->b, &res->b[DNS_HEADER_LENGTH], question->len)) { + fprintf(stderr, "The question section differs in the request and the response.\n"); + return false; + } + answer_count = res_header->ancount; + free(res_header); + start_of_answer = DNS_HEADER_LENGTH + question->len; + byte_array_free(question); + if (res->len < start_of_answer) { + fprintf(stderr, "Response doesn't include an answer.\n"); + return false; + } + answers = malloc(answer_count * sizeof(union DnsTypeResult *)); + for (i = 0; i<answer_count; i++) { + answer = answer_parse(res, &start_of_answer); + answers[i] = answer_parse_by_type(answer, res, dns_type, &start_of_answer); + resource_record_free(answer); + } + answers_print(answers, answer_count, dns_type, disable_header); + answers_free(answers, answer_count, dns_type); + byte_array_free(res); + return true; +} + int main(int argc, char *argv[]) { static struct option options[] = { + { "dns-server", required_argument, 0, 's' }, { "type", required_argument, 0, 't' }, { "header", no_argument, 0, 'h' }, { 0, 0, 0, 0 } @@ -892,11 +984,20 @@ main(int argc, char *argv[]) char *domain, *upper_type; char type[5+1]; type[0] = 0; + char dns_server[MAX_DOMAIN_LENGTH+1]; + dns_server[0] = 0; bool disable_header = false; bool error; enum Type dns_type; - while ((o = getopt_long(argc, argv, "t:h", options, &option_index)) != -1) { + while ((o = getopt_long(argc, argv, "s:t:h", options, &option_index)) != -1) { switch (o) { + case 's': + if (strlen(optarg) > MAX_DOMAIN_LENGTH) { + fprintf(stderr, "The provided dns server ip is too long.\n"); + return 1; + } + strcpy(dns_server, optarg); + break; case 't': if (strlen(optarg) > 5) { fprintf(stderr, "The provided type is too long.\n"); @@ -933,53 +1034,9 @@ main(int argc, char *argv[]) return 1; } free(upper_type); - struct ByteArray *header, *question, *res; - struct DnsHeader *res_header; - header = header_form(&dns_header_default); - struct DnsQuestion qu = { - .domain = domain, - .type = dns_type, - .class = CLASS_IN - }; - question = question_form(&qu); - char req[header->len + question->len]; - memcpy(req, header->b, header->len); - memcpy(&req[header->len], question->b, question->len); - res = dns_server_request((char *)&req, header->len + question->len); - if (!res) { - LOG_DEBUG("dns_server_request failed."); + if (!dns_server_ask(dns_server[0] == 0 ? NULL : dns_server, domain, dns_type, disable_header)) { + LOG_DEBUG("dns_server_ask failed."); return 1; } - byte_array_free(header); - if (res->len <= DNS_HEADER_LENGTH) { - fprintf(stderr, "Response is too short.\n"); - return 1; - } - res_header = header_parse(res->b); - if (!response_is_valid(&dns_header_default, res_header)) { - fprintf(stderr, "Response isn't valid.\n"); - return 1; - } - int answer_count = res_header->ancount; - free(res_header); - int *start_of_answer = malloc(sizeof(int)); - *start_of_answer = DNS_HEADER_LENGTH + question->len; - byte_array_free(question); - if (res->len < *start_of_answer) { - fprintf(stderr, "Response doesn't include an answer.\n"); - return 1; - } - struct DnsResourceRecord *answer; - union DnsTypeResult **answers = malloc(answer_count * sizeof(union DnsTypeResult *)); - int i; - for (i = 0; i<answer_count; i++) { - answer = answer_parse(res, start_of_answer); - answers[i] = answer_parse_by_type(answer, res, dns_type, start_of_answer); - resource_record_free(answer); - } - answers_print(answers, answer_count, dns_type, disable_header); - answers_free(answers, answer_count, dns_type); - free(start_of_answer); - byte_array_free(res); return 0; } diff --git a/dinoco.h b/dinoco.h @@ -7,6 +7,7 @@ #define ANSI_COLOR_GREEN "\x1b[32m" #define ANSI_COLOR_RESET "\x1b[0m" +#define LOG_ERR(msg) fprintf(stderr, "ERR: "msg"\n"); #ifdef DEBUG #define LOG_DEBUG(msg) fprintf(stderr, msg"\n"); #else @@ -76,7 +77,7 @@ struct DnsHeader { }; struct DnsQuestion { - char *domain; + const char *domain; enum Type type; enum Class class; };