aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile13
-rw-r--r--README27
-rw-r--r--dns.c89
-rw-r--r--dns.h18
-rw-r--r--dns_example.c7
5 files changed, 121 insertions, 33 deletions
diff --git a/Makefile b/Makefile
index 0cb1cbc..208f55e 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,14 @@
-dns_example: dns_example.c dns.c dns.h
- gcc dns_example.c dns.c -I. -o $@
+DEBUG ?= yes
+
+CFLAGS = -xc -ansi -Wall
+ifeq '$(DEBUG)' 'yes'
+CFLAGS += -g -O0
+else
+CFLAGS += -O3
+endif
+
+dns_example: dns_example.c dns.c
+ gcc $(CFLAGS) $^ -I. -o $@
.PHONY: clean
clean:
diff --git a/README b/README
new file mode 100644
index 0000000..dbd7e0b
--- /dev/null
+++ b/README
@@ -0,0 +1,27 @@
+ Description
+DNS made simple.
+
+ Overview
+NewbieDNS is FOSS: legal info is in the 'LICENSE' file.
+It is written in ANSI C for UNIX-like environment (Linux, OS X, ...).
+NewbieDNS is a module, not a library, so it is intended to be changed
+as you wish and included directly into code of your project.
+
+ Compiling
+Compilation is described in Makefile, however it is mostly about flags,
+since the thing is so simple. Type 'make' to produce working example.
+
+ Example
+The example prints response for A record from 1.1.1.1 DNS server for
+veresov.pro domain in human-readable form (so the response is parsed).
+
+ Usage
+The module presents response in dns_answers, which is a linked list.
+Function dns_get request given server about given domain name for
+given record type (dns_type_*) and returns parsed response.
+You can step through it to process all answers, each answer has
+some way of accessing its fields.
+Don't forget to free memory after use with dns_free.
+
+ Motivation
+DNS deserves understandable implementation.
diff --git a/dns.c b/dns.c
index 065634b..401980c 100644
--- a/dns.c
+++ b/dns.c
@@ -10,6 +10,10 @@
#include <fcntl.h>
+/* Entry point is dns_get. */
+
+
+/* Read about DNS in RFC 1034 & 1035. */
struct header {
unsigned short id;
@@ -48,18 +52,23 @@ struct resource_header {
};
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * get-related
+ */
static void fill_header(char **writer)
{
struct header *head = (struct header *) *writer;
memset(head, 0, sizeof(*head));
+ /* PID is unique enough to use it as ID: */
head->id = (unsigned short) htons(getpid());
- head->a = 0b00000001;
- head->questions = htons(1);
- *writer += sizeof(*head);
+ head->a = 0b00000001; /* recursion desired */
+ head->questions = htons(1); /* we have one question */
+ *writer += sizeof(*head);
}
static void fill_name(char **writer, const char *host)
-{
+{ /* In DNS names are always starting with dot,
+ * so 'example.com' need to become '.example.com'. */
char *last = *writer;
do {
if (*host == '.' || !*host) {
@@ -82,7 +91,7 @@ static void fill_question(char **writer, int query_type)
}
static int askudp(const char *s, const char *h, int qt, struct sockaddr_in *a)
-{
+{ /* Create socket, fill in the question and send it. */
int sent;
int plen = sizeof(struct header) + strlen(h) + 2 +
sizeof(struct question_header);
@@ -96,10 +105,12 @@ static int askudp(const char *s, const char *h, int qt, struct sockaddr_in *a)
a->sin_port = htons(53);
a->sin_addr.s_addr = inet_addr(s);
+ /* Socket created, now fill in the question. */
fill_header(&writer);
fill_name(&writer, h);
fill_question(&writer, qt);
+ /* Question is filled in, so send it. */
sent = sendto(sock, packet, plen, 0, (struct sockaddr *) a, sizeof(*a));
free(packet);
if (sent != plen) {
@@ -109,14 +120,14 @@ static int askudp(const char *s, const char *h, int qt, struct sockaddr_in *a)
}
static int asktcp(const char *s, const char *h, int qt)
-{
+{ /* Create socket, fill in the question and send it. */
struct sockaddr_in a;
int sent;
int plen = sizeof(struct header) + strlen(h) + 4 +
sizeof(struct question_header);
- char *packet = malloc(plen);
- char *writer = packet;
- int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ char *packet = malloc(plen);
+ char *writer = packet;
+ int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock < 0) {
return 0;
}
@@ -127,12 +138,15 @@ static int asktcp(const char *s, const char *h, int qt)
return 0;
}
+ /* Socket created, now fill in the question.
+ Note the difference from UDP: TCP has size in the start. */
writer += 2;
fill_header(&writer);
fill_name(&writer, h);
fill_question(&writer, qt);
*((unsigned short *) packet) = htons(writer - packet - 2);
+ /* Question is filled in, so send it. */
sent = write(sock, packet, plen);
free(packet);
if (sent != plen) {
@@ -164,7 +178,7 @@ static char *gettcp(int sock)
}
static char *get(const char *s, const char *h, int qt)
-{
+{ /* Get response in UDP, if it is truncated do it in TCP. */
struct sockaddr_in a;
char *packet;
socklen_t slen = sizeof(a);
@@ -172,12 +186,12 @@ static char *get(const char *s, const char *h, int qt)
if (!sock) {
return 0;
}
- packet = malloc(512);
+ packet = malloc(512); /* Max size of UDP is 512 bytes. */
if (recvfrom(sock, packet, 512, 0, (struct sockaddr *) &a, &slen) < 0) {
free(packet);
close(sock);
return 0;
- } else if (packet[2] & 0b10) {
+ } else if (packet[2] & 0b10) { /* is truncated */
free(packet);
close(sock);
return gettcp(asktcp(s, h, qt));
@@ -187,6 +201,9 @@ static char *get(const char *s, const char *h, int qt)
}
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * parse-related
+ */
static void skip_questions(char **reader, int num)
{
for (; num; --num) {
@@ -195,10 +212,14 @@ static void skip_questions(char **reader, int num)
}
}
-static char *read_name(char **extrareader, char *packet)
-{
+static char *read_name(char **extreader, char *packet)
+{ /* Names are stored in binary format:
+ * length followed by name part (string between dots),
+ * length of zero means end of domain name,
+ * and length >= 192 means it will be followed by absolute link,
+ * name readed from link should be inserted instead of this part. */
char *name = malloc(256); /* cannot be more =) */
- char *reader = *extrareader;
+ char *reader = *extreader;
char *writer = name;
int jumped = 0;
if (!*reader) {
@@ -210,7 +231,7 @@ static char *read_name(char **extrareader, char *packet)
if (len >= 192) {
int jump = ((unsigned char) *reader) + (len - 192) * 256;
if (!jumped) {
- *extrareader += 2;
+ *extreader += 2;
jumped = 1;
}
reader = packet + jump;
@@ -220,43 +241,49 @@ static char *read_name(char **extrareader, char *packet)
writer += len;
reader += len;
if (!jumped) {
- *extrareader += len + 1;
+ *extreader += len + 1;
}
*writer++ = '.';
}
if (!jumped) {
- ++*extrareader;
+ ++*extreader;
}
*--writer = 0;
return name;
}
static struct dns_answers *get_answers(char **reader, char *packet, int num)
-{
+{ /* Read all (num) answers into res and return it. */
struct dns_answers *res;
struct resource_header *head;
- if (!num) {
+ if (!num) { /* no answers left to read */
return 0;
}
+ /* Read one answer, starting with header: */
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;
+ /* Analyse answer data based on answer type: */
if (res->type == dns_type_cname) {
+ /* For CNAME data is just domain name: */
res->size--;
res->data = read_name(reader, packet);
} else if (res->type == dns_type_mx) {
+ /* For MX data is record preference, followed by its name: */
char *name;
unsigned short pref = ntohs(*((unsigned short *) *reader));
*reader += 2;
name = read_name(reader, packet);
- res->data = malloc(3 + strlen(name));
+ res->data = malloc(2 + strlen(name) + 1);
*((unsigned short *) res->data) = pref;
memcpy(res->data + 2, name, strlen(name) + 1);
free(name);
} else if (res->type == dns_type_srv) {
+ /* For SRV data is record priority, weight and port
+ * followed by its name. */
char *name;
unsigned short priority;
unsigned short weight;
@@ -275,7 +302,9 @@ static struct dns_answers *get_answers(char **reader, char *packet, int num)
memcpy(res->data + 6, name, strlen(name) + 1);
free(name);
} else {
+ /* For other types just copy the data: */
if (res->type == dns_type_txt) {
+ /* Skipping leading length byte for TXT: */
res->size--;
++*reader;
}
@@ -283,6 +312,7 @@ static struct dns_answers *get_answers(char **reader, char *packet, int num)
memcpy(res->data, *reader, res->size);
*reader += res->size;
}
+ /* Start recursion for the rest of answers and add them as next: */
res->next = get_answers(reader, packet, num - 1);
return res;
}
@@ -295,9 +325,9 @@ static struct dns_answers *parse(char *packet)
if (!packet) {
return 0;
}
- head->questions = ntohs(head->questions);
- head->answers = ntohs(head->answers);
- reader += sizeof(*head);
+ head->questions = ntohs(head->questions);
+ head->answers = ntohs(head->answers);
+ reader += sizeof(*head);
skip_questions(&reader, head->questions);
res = get_answers(&reader, packet, head->answers);
free(packet);
@@ -305,6 +335,9 @@ static struct dns_answers *parse(char *packet)
}
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * MX utility
+ */
char *dns_mx_server(void *data)
{
return ((char *) data) + 2;
@@ -316,6 +349,9 @@ int dns_mx_preference(void *data)
}
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * SRV utility
+ */
char *dns_srv_server(void *data)
{
return ((char *) data) + 6;
@@ -337,8 +373,11 @@ int dns_srv_weight(void *data)
}
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Constructor and destructor
+ */
struct dns_answers *dns_get(const char *server, const char *host, int query)
-{
+{ /* Acquire response in get and parse it. */
return parse(get(server, host, query));
}
diff --git a/dns.h b/dns.h
index 61478ae..ae69394 100644
--- a/dns.h
+++ b/dns.h
@@ -8,18 +8,23 @@ enum { /* Some of DNS types: */
dns_type_mx = 15,
dns_type_txt = 16,
dns_type_srv = 33
+ /* If you need other one -- add it here. */
};
+/* Answers as linked list. */
struct dns_answers {
struct dns_answers *next;
- char *host;
- int type;
- unsigned int size;
+ char *host; /* for which answer is provided */
+ int type; /* type of current answer */
+ unsigned int size; /* size of data */
void *data;
};
-/* For CNAME & TXT data is just 'char *',
- * For A data is 'unsigned char *' of size 4. */
+
+/* For CNAME & TXT data is just human-readable null-terminated 'char *',
+ * for A data is 'unsigned char *' of size 4,
+ * other answers are more complex,
+ * so functions to access their data are provided: */
char *dns_mx_server(void *data);
int dns_mx_preference(void *data);
@@ -29,7 +34,8 @@ int dns_srv_port(void *data);
int dns_srv_priority(void *data);
int dns_srv_weight(void *data);
-/* In case of error result is 0. */
+
+/* In case of error result is 0, as if server don't know host. */
struct dns_answers *dns_get(const char *server, const char *host, int query);
void dns_free(struct dns_answers *answers);
diff --git a/dns_example.c b/dns_example.c
index 843a1d7..62c9a7f 100644
--- a/dns_example.c
+++ b/dns_example.c
@@ -7,22 +7,29 @@ int main()
struct dns_answers *cur;
const char *server = "1.1.1.1"; /* place your favourite one */
struct dns_answers *ans = dns_get(server, "veresov.pro", dns_type_a);
+ /* Host is always human-readable domain name of request. */
for (cur = ans; cur; cur = cur->next) {
if (cur->type == dns_type_a) {
+ /* IP is stored in data as 4 bytes (unsigned chars). */
unsigned char *ip = cur->data;
printf("IP address 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) {
+ /* Data is human-readable domain name of response. */
printf("Cannonical name of %s is %s.\n", cur->host, cur->data);
} else if (cur->type == dns_type_txt) {
+ /* Data is null terminated string of TXT response. */
printf("Text from %s: %s\n", cur->host, cur->data);
} else if (cur->type == dns_type_srv) {
+ /* SRV answer data is pretty complex,
+ so functions to access it are provided. */
printf("Service %s is located at %s on port %d "
"with priority %d and weight %d.\n",
cur->host,
dns_srv_server(cur->data), dns_srv_port(cur->data),
dns_srv_priority(cur->data), dns_srv_weight(cur->data));
} else if (cur->type == dns_type_mx) {
+ /* Same as with SRV, functions provided. */
printf("Mail exchange server for %s with preference %d is %s.\n",
cur->host,
dns_mx_preference(cur->data), dns_mx_server(cur->data));