aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--LICENSE9
-rw-r--r--dns.c236
-rw-r--r--dns.h34
-rw-r--r--dns_example.c28
4 files changed, 307 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..4921ae2
--- /dev/null
+++ b/LICENSE
@@ -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.
diff --git a/dns.c b/dns.c
new file mode 100644
index 0000000..53be937
--- /dev/null
+++ b/dns.c
@@ -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);
+ }
+}
diff --git a/dns.h b/dns.h
new file mode 100644
index 0000000..788618d
--- /dev/null
+++ b/dns.h
@@ -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;
+}