diff options
-rw-r--r-- | LICENSE | 9 | ||||
-rw-r--r-- | dns.c | 236 | ||||
-rw-r--r-- | dns.h | 34 | ||||
-rw-r--r-- | dns_example.c | 28 |
4 files changed, 307 insertions, 0 deletions
@@ -0,0 +1,9 @@ +Copyright 2020 Aleksey Veresov + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from +the use of this software. + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. @@ -0,0 +1,236 @@ +#include "dns.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <unistd.h> +#include <fcntl.h> + + +struct header { + unsigned short id; + + char a; + /* + int is_response: 1; + int opcode: 4; + int is_authanswer: 1; + int is_recursion_desired: 1; + */ + + char b; + /* + int is_recursion_available: 1; + int: 3; + int response_code: 4; + */ + + unsigned short questions; + unsigned short answers; + unsigned short authorities; + unsigned short additionals; +}; + +struct question_header { + unsigned short type; + unsigned short class; +}; + +struct resource_header { + unsigned short type; + unsigned short class; + unsigned int ttl; + unsigned short datalen; +}; + + +static void fill_header(char **writer) +{ + struct header *head = (struct header *) *writer; + memset(head, 0, sizeof(*head)); + head->id = (unsigned short) htons(getpid()); + head->a = 0b00000001; + head->questions = htons(1); + *writer += sizeof(*head); +} + +static void fill_name(char **writer, const char *host) +{ + char *last = *writer; + do { + if (*host == '.' || !*host) { + *last = *writer - last; + last = ++*writer; + } else { + *++*writer = *host; + } + } while (*host++); + *last = 0; + ++*writer; +} + +static void fill_question(char **writer, int query_type) +{ + struct question_header *q = (struct question_header *) *writer; + q->type = htons(query_type); + q->class = htons(1); + *writer += sizeof(*q); +} + +static int ask(const char *s, const char *h, int qt, struct sockaddr_in *a) +{ + int sent; + int plen = sizeof(struct header) + strlen(h) + 2 + + sizeof(struct question_header); + char *packet = malloc(plen); + char *writer = packet; + int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + a->sin_family = AF_INET; + a->sin_port = htons(53); + a->sin_addr.s_addr = inet_addr(s); + + fill_header(&writer); + fill_name(&writer, h); + fill_question(&writer, qt); + + sent = sendto(sock, packet, plen, 0, (struct sockaddr *) a, sizeof(*a)); + free(packet); + if (sent != plen) { + return 0; + } + return sock; +} + + +static void skip_questions(char **reader, int num) +{ + for (; num; --num) { + *reader += strlen(*reader) + 1; + *reader += sizeof(struct question_header); + } +} + +static char *read_name(char **extrareader, char *packet) +{ + char *name = malloc(256); /* cannot be more =) */ + char *reader = *extrareader; + char *writer = name; + int jumped = 0; + if (!*reader) { + *name = 0; + return name; + } + while (*reader) { + unsigned char len = *reader++; + if (len >= 192) { + int jump = ((unsigned char) *reader) + (len - 192) * 256; + if (!jumped) { + *extrareader += 2; + jumped = 1; + } + reader = packet + jump; + continue; + } + memcpy(writer, reader, len); + writer += len; + reader += len; + if (!jumped) { + *extrareader += len + 1; + } + *writer++ = '.'; + } + if (!jumped) { + ++*extrareader; + } + *--writer = 0; + return name; +} + +static struct dns_answers *get_answers(char **reader, char *packet, int num) +{ + struct dns_answers *res; + struct resource_header *head; + if (!num) { + return 0; + } + res = malloc(sizeof(*res)); + res->host = read_name(reader, packet); + head = (struct resource_header *) *reader; + res->type = ntohs(head->type); + res->size = ntohs(head->datalen); + *reader += 10; + if (res->type == dns_type_cname) { + res->size--; + res->data = read_name(reader, packet); + } else if (res->type == dns_type_mx) { + char *name; + unsigned short pref = ntohs(*((unsigned short *) *reader)); + *reader += 2; + name = read_name(reader, packet); + res->data = malloc(3 + strlen(name)); + *((unsigned short *) res->data) = pref; + memcpy(res->data + 2, name, strlen(name) + 1); + free(name); + } else { + if (res->type == dns_type_txt) { + res->size--; + ++*reader; + } + res->data = malloc(res->size); + memcpy(res->data, *reader, res->size); + *reader += res->size; + } + res->next = get_answers(reader, packet, num - 1); + return res; +} + +static struct dns_answers *get(int sock, struct sockaddr_in *a) +{ + char packet[512]; + char *reader = packet; + struct header *head; + socklen_t slen = sizeof(*a); + if (!sock) { + return 0; + } + if (recvfrom(sock, packet, 512, 0, (struct sockaddr *) a, &slen) < 0) { + return 0; + } + head = (struct header *) reader; + head->questions = ntohs(head->questions); + head->answers = ntohs(head->answers); + reader += sizeof(*head); + skip_questions(&reader, head->questions); + return get_answers(&reader, packet, head->answers); +} + + +int dns_mx_preference(void *data) +{ + return *((unsigned short *) data); +} + +char *dns_mx_server(void *data) +{ + return ((char *) data) + 2; +} + + +struct dns_answers *dns_get(const char *server, const char *host, int query) +{ + struct sockaddr_in addr; + return get(ask(server, host, query, &addr), &addr); +} + +void dns_free(struct dns_answers *answers) +{ + if (answers) { + dns_free(answers->next); + free(answers->host); + free(answers->data); + free(answers); + } +} @@ -0,0 +1,34 @@ +#ifndef DNS_INCLUDED +#define DNS_INCLUDED + + +enum { /* Some of DNS types: */ + dns_type_a = 1, + dns_type_cname = 5, + dns_type_mx = 15, + dns_type_txt = 16 +}; + +struct dns_answers { + struct dns_answers *next; + char *host; + int type; + unsigned int size; + void *data; +}; + +/* For CNAME & TXT data is just 'char *', + * For A data is 'unsigned char *' of size 4. */ + +int dns_mx_preference(void *data); +char *dns_mx_server(void *data); + + +/* In case of error result is 0. + * It's not the-best-effort attempt to get answer, just simple working one. */ +struct dns_answers *dns_get(const char *server, const char *host, int query); + +void dns_free(struct dns_answers *answers); + + +#endif diff --git a/dns_example.c b/dns_example.c new file mode 100644 index 0000000..27eea8d --- /dev/null +++ b/dns_example.c @@ -0,0 +1,28 @@ +#include <dns.h> +#include <stdio.h> + + +int main() +{ + struct dns_answers *ans = dns_get("10.1.1.1", + "veresov.pro", + dns_type_mx); + struct dns_answers *cur; + for (cur = ans; cur; cur = cur->next) { + if (cur->type == dns_type_a) { + unsigned char *ip = cur->data; + printf("IP of %s is %d.%d.%d.%d.\n", + cur->host, ip[0], ip[1], ip[2], ip[3]); + } else if (cur->type == dns_type_cname) { + printf("Cannonical name of %s is %s.\n", cur->host, cur->data); + } else if (cur->type == dns_type_txt) { + printf("Text from %s: %s\n", cur->host, cur->data); + } else if (cur->type == dns_type_mx) { + printf("Mail exchange server for %s with preference %d is %s.\n", + cur->host, + dns_mx_preference(cur->data), dns_mx_server(cur->data)); + } + } + dns_free(ans); + return 0; +} |