Files
smartdns/test-dns.c
2018-05-10 00:26:32 +08:00

756 lines
16 KiB
C
Executable File

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <string.h>
#include <malloc.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>
#define BUF_SIZE 1500
/*
* This software is licensed under the CC0.
*
* This is a _basic_ DNS Server for educational use.
* It does not prevent invalid packets from crashing
* the server.
*
* To test start the program and issue a DNS request:
* dig @127.0.0.1 -p 9000 foo.bar.com
*/
/*
* Masks and constants.
*/
static const uint32_t QR_MASK = 0x8000;
static const uint32_t OPCODE_MASK = 0x7800;
static const uint32_t AA_MASK = 0x0400;
static const uint32_t TC_MASK = 0x0200;
static const uint32_t RD_MASK = 0x0100;
static const uint32_t RA_MASK = 0x8000;
static const uint32_t RCODE_MASK = 0x000F;
/* Response Type */
enum {
Ok_ResponseType = 0,
FormatError_ResponseType = 1,
ServerFailure_ResponseType = 2,
NameError_ResponseType = 3,
NotImplemented_ResponseType = 4,
Refused_ResponseType = 5
};
/* Resource Record Types */
enum {
A_Resource_RecordType = 1,
NS_Resource_RecordType = 2,
CNAME_Resource_RecordType = 5,
SOA_Resource_RecordType = 6,
PTR_Resource_RecordType = 12,
MX_Resource_RecordType = 15,
TXT_Resource_RecordType = 16,
AAAA_Resource_RecordType = 28,
SRV_Resource_RecordType = 33
};
/* Operation Code */
enum {
QUERY_OperationCode = 0, /* standard query */
IQUERY_OperationCode = 1, /* inverse query */
STATUS_OperationCode = 2, /* server status request */
NOTIFY_OperationCode = 4, /* request zone transfer */
UPDATE_OperationCode = 5 /* change resource records */
};
/* Response Code */
enum {
NoError_ResponseCode = 0,
FormatError_ResponseCode = 1,
ServerFailure_ResponseCode = 2,
NameError_ResponseCode = 3
};
/* Query Type */
enum {
IXFR_QueryType = 251,
AXFR_QueryType = 252,
MAILB_QueryType = 253,
MAILA_QueryType = 254,
STAR_QueryType = 255
};
/*
* Types.
*/
/* Question Section */
struct Question {
char *qName;
uint16_t qType;
uint16_t qClass;
struct Question* next; // for linked list
};
/* Data part of a Resource Record */
union ResourceData {
struct {
char *txt_data;
} txt_record;
struct {
uint8_t addr[4];
} a_record;
struct {
char* MName;
char* RName;
uint32_t serial;
uint32_t refresh;
uint32_t retry;
uint32_t expire;
uint32_t minimum;
} soa_record;
struct {
char *name;
} name_server_record;
struct {
char name;
} cname_record;
struct {
char *name;
} ptr_record;
struct {
uint16_t preference;
char *exchange;
} mx_record;
struct {
uint8_t addr[16];
} aaaa_record;
struct {
uint16_t priority;
uint16_t weight;
uint16_t port;
char *target;
} srv_record;
};
/* Resource Record Section */
struct ResourceRecord {
char *name;
uint16_t type;
uint16_t class;
uint16_t ttl;
uint16_t rd_length;
union ResourceData rd_data;
struct ResourceRecord* next; // for linked list
};
struct Message {
uint16_t id; /* Identifier */
/* Flags */
uint16_t qr; /* Query/Response Flag */
uint16_t opcode; /* Operation Code */
uint16_t aa; /* Authoritative Answer Flag */
uint16_t tc; /* Truncation Flag */
uint16_t rd; /* Recursion Desired */
uint16_t ra; /* Recursion Available */
uint16_t rcode; /* Response Code */
uint16_t qdCount; /* Question Count */
uint16_t anCount; /* Answer Record Count */
uint16_t nsCount; /* Authority Record Count */
uint16_t arCount; /* Additional Record Count */
/* At least one question; questions are copied to the response 1:1 */
struct Question* questions;
/*
* Resource records to be send back.
* Every resource record can be in any of the following places.
* But every place has a different semantic.
*/
struct ResourceRecord* answers;
struct ResourceRecord* authorities;
struct ResourceRecord* additionals;
};
int get_A_Record(uint8_t addr[4], const char domain_name[])
{
if (strcmp("foo.bar.com", domain_name) == 0)
{
addr[0] = 192;
addr[1] = 168;
addr[2] = 1;
addr[3] = 1;
return 0;
}
else
{
return -1;
}
}
int get_AAAA_Record(uint8_t addr[16], const char domain_name[])
{
if (strcmp("foo.bar.com", domain_name) == 0)
{
addr[0] = 0xfe;
addr[1] = 0x80;
addr[2] = 0x00;
addr[3] = 0x00;
addr[4] = 0x00;
addr[5] = 0x00;
addr[6] = 0x00;
addr[7] = 0x00;
addr[8] = 0x00;
addr[9] = 0x00;
addr[10] = 0x00;
addr[11] = 0x00;
addr[12] = 0x00;
addr[13] = 0x00;
addr[14] = 0x00;
addr[15] = 0x01;
return 0;
}
else
{
return -1;
}
}
/*
* Debugging functions.
*/
void print_hex(uint8_t* buf, size_t len)
{
int i;
printf("%zu bytes:\n", len);
for(i = 0; i < len; ++i)
printf("%02x ", buf[i]);
printf("\n");
}
void print_resource_record(struct ResourceRecord* rr)
{
int i;
while (rr)
{
printf(" ResourceRecord { name '%s', type %u, class %u, ttl %u, rd_length %u, ",
rr->name,
rr->type,
rr->class,
rr->ttl,
rr->rd_length
);
union ResourceData *rd = &rr->rd_data;
switch (rr->type)
{
case A_Resource_RecordType:
printf("Address Resource Record { address ");
for(i = 0; i < 4; ++i)
printf("%s%u", (i ? "." : ""), rd->a_record.addr[i]);
printf(" }");
break;
case NS_Resource_RecordType:
printf("Name Server Resource Record { name %s }",
rd->name_server_record.name
);
break;
case CNAME_Resource_RecordType:
printf("Canonical Name Resource Record { name %u }",
rd->cname_record.name
);
break;
case SOA_Resource_RecordType:
printf("SOA { MName '%s', RName '%s', serial %u, refresh %u, retry %u, expire %u, minimum %u }",
rd->soa_record.MName,
rd->soa_record.RName,
rd->soa_record.serial,
rd->soa_record.refresh,
rd->soa_record.retry,
rd->soa_record.expire,
rd->soa_record.minimum
);
break;
case PTR_Resource_RecordType:
printf("Pointer Resource Record { name '%s' }",
rd->ptr_record.name
);
break;
case MX_Resource_RecordType:
printf("Mail Exchange Record { preference %u, exchange '%s' }",
rd->mx_record.preference,
rd->mx_record.exchange
);
break;
case TXT_Resource_RecordType:
printf("Text Resource Record { txt_data '%s' }",
rd->txt_record.txt_data
);
break;
case AAAA_Resource_RecordType:
printf("AAAA Resource Record { address ");
for(i = 0; i < 16; ++i)
printf("%s%02x", (i ? ":" : ""), rd->aaaa_record.addr[i]);
printf(" }");
break;
default:
printf("Unknown Resource Record { ??? }");
}
printf("}\n");
rr = rr->next;
}
}
void print_query(struct Message* msg)
{
printf("QUERY { ID: %02x", msg->id);
printf(". FIELDS: [ QR: %u, OpCode: %u ]", msg->qr, msg->opcode);
printf(", QDcount: %u", msg->qdCount);
printf(", ANcount: %u", msg->anCount);
printf(", NScount: %u", msg->nsCount);
printf(", ARcount: %u,\n", msg->arCount);
struct Question* q = msg->questions;
while (q)
{
printf(" Question { qName '%s', qType %u, qClass %u }\n",
q->qName,
q->qType,
q->qClass
);
q = q->next;
}
print_resource_record(msg->answers);
print_resource_record(msg->authorities);
print_resource_record(msg->additionals);
printf("}\n");
}
/*
* Basic memory operations.
*/
size_t get16bits(const uint8_t** buffer)
{
uint16_t value;
memcpy(&value, *buffer, 2);
*buffer += 2;
return ntohs(value);
}
void put8bits(uint8_t** buffer, uint8_t value)
{
memcpy(*buffer, &value, 1);
*buffer += 1;
}
void put16bits(uint8_t** buffer, uint16_t value)
{
value = htons(value);
memcpy(*buffer, &value, 2);
*buffer += 2;
}
void put32bits(uint8_t** buffer, uint32_t value)
{
value = htons(value);
memcpy(*buffer, &value, 4);
*buffer += 4;
}
/*
* Deconding/Encoding functions.
*/
// 3foo3bar3com0 => foo.bar.com
char* decode_domain_name(const uint8_t** buffer)
{
char name[256];
const uint8_t* buf = *buffer;
int j = 0;
int i = 0;
while (buf[i] != 0)
{
//if (i >= buflen || i > sizeof(name))
// return NULL;
if (i != 0)
{
name[j] = '.';
j += 1;
}
int len = buf[i];
i += 1;
memcpy(name+j, buf+i, len);
i += len;
j += len;
}
name[j] = '\0';
*buffer += i + 1; //also jump over the last 0
return strdup(name);
}
// foo.bar.com => 3foo3bar3com0
void encode_domain_name(uint8_t** buffer, const char* domain)
{
uint8_t* buf = *buffer;
const char* beg = domain;
const char* pos;
int len = 0;
int i = 0;
while ((pos = strchr(beg, '.')))
{
len = pos - beg;
buf[i] = len;
i += 1;
memcpy(buf+i, beg, len);
i += len;
beg = pos + 1;
}
len = strlen(domain) - (beg - domain);
buf[i] = len;
i += 1;
memcpy(buf + i, beg, len);
i += len;
buf[i] = 0;
i += 1;
*buffer += i;
}
void decode_header(struct Message* msg, const uint8_t** buffer)
{
msg->id = get16bits(buffer);
uint32_t fields = get16bits(buffer);
msg->qr = (fields & QR_MASK) >> 15;
msg->opcode = (fields & OPCODE_MASK) >> 11;
msg->aa = (fields & AA_MASK) >> 10;
msg->tc = (fields & TC_MASK) >> 9;
msg->rd = (fields & RD_MASK) >> 8;
msg->ra = (fields & RA_MASK) >> 7;
msg->rcode = (fields & RCODE_MASK) >> 0;
msg->qdCount = get16bits(buffer);
msg->anCount = get16bits(buffer);
msg->nsCount = get16bits(buffer);
msg->arCount = get16bits(buffer);
}
void encode_header(struct Message* msg, uint8_t** buffer)
{
put16bits(buffer, msg->id);
int fields = 0;
fields |= (msg->qr << 15) & QR_MASK;
fields |= (msg->rcode << 0) & RCODE_MASK;
// TODO: insert the rest of the fields
put16bits(buffer, fields);
put16bits(buffer, msg->qdCount);
put16bits(buffer, msg->anCount);
put16bits(buffer, msg->nsCount);
put16bits(buffer, msg->arCount);
}
int decode_msg(struct Message* msg, const uint8_t* buffer, int size)
{
int i;
decode_header(msg, &buffer);
if (msg->anCount != 0 || msg->nsCount != 0)
{
printf("Only questions expected!\n");
return -1;
}
// parse questions
uint32_t qcount = msg->qdCount;
struct Question* qs = msg->questions;
for (i = 0; i < qcount; ++i)
{
struct Question* q = malloc(sizeof(struct Question));
q->qName = decode_domain_name(&buffer);
q->qType = get16bits(&buffer);
q->qClass = get16bits(&buffer);
// prepend question to questions list
q->next = qs;
msg->questions = q;
}
// We do not expect any resource records to parse here.
return 0;
}
// For every question in the message add a appropiate resource record
// in either section 'answers', 'authorities' or 'additionals'.
void resolver_process(struct Message* msg)
{
struct ResourceRecord* beg;
struct ResourceRecord* rr;
struct Question* q;
int rc;
// leave most values intact for response
msg->qr = 1; // this is a response
msg->aa = 1; // this server is authoritative
msg->ra = 0; // no recursion available
msg->rcode = Ok_ResponseType;
// should already be 0
msg->anCount = 0;
msg->nsCount = 0;
msg->arCount = 0;
// for every question append resource records
q = msg->questions;
while (q)
{
rr = malloc(sizeof(struct ResourceRecord));
memset(rr, 0, sizeof(struct ResourceRecord));
rr->name = strdup(q->qName);
rr->type = q->qType;
rr->class = q->qClass;
rr->ttl = 60*60; // in seconds; 0 means no caching
printf("Query for '%s'\n", q->qName);
// We only can only answer two question types so far
// and the answer (resource records) will be all put
// into the answers list.
// This behavior is probably non-standard!
switch (q->qType)
{
case A_Resource_RecordType:
rr->rd_length = 4;
rc = get_A_Record(rr->rd_data.a_record.addr, q->qName);
if (rc < 0)
{
free(rr->name);
free(rr);
goto next;
}
break;
case AAAA_Resource_RecordType:
rr->rd_length = 16;
rc = get_AAAA_Record(rr->rd_data.aaaa_record.addr, q->qName);
if (rc < 0)
{
free(rr->name);
free(rr);
goto next;
}
break;
/*
case NS_Resource_RecordType:
case CNAME_Resource_RecordType:
case SOA_Resource_RecordType:
case PTR_Resource_RecordType:
case MX_Resource_RecordType:
case TXT_Resource_RecordType:
*/
default:
free(rr);
msg->rcode = NotImplemented_ResponseType;
printf("Cannot answer question of type %d.\n", q->qType);
goto next;
}
msg->anCount++;
// prepend resource record to answers list
beg = msg->answers;
msg->answers = rr;
rr->next = beg;
// jump here to omit question
next:
// process next question
q = q->next;
}
}
/* @return 0 upon failure, 1 upon success */
int encode_resource_records(struct ResourceRecord* rr, uint8_t** buffer)
{
int i;
while (rr)
{
// Answer questions by attaching resource sections.
encode_domain_name(buffer, rr->name);
put16bits(buffer, rr->type);
put16bits(buffer, rr->class);
put32bits(buffer, rr->ttl);
put16bits(buffer, rr->rd_length);
switch (rr->type)
{
case A_Resource_RecordType:
for(i = 0; i < 4; ++i)
put8bits(buffer, rr->rd_data.a_record.addr[i]);
break;
case AAAA_Resource_RecordType:
for(i = 0; i < 16; ++i)
put8bits(buffer, rr->rd_data.aaaa_record.addr[i]);
break;
default:
fprintf(stderr, "Unknown type %u. => Ignore resource record.\n", rr->type);
return 1;
}
rr = rr->next;
}
return 0;
}
/* @return 0 upon failure, 1 upon success */
int encode_msg(struct Message* msg, uint8_t** buffer)
{
struct Question* q;
int rc;
encode_header(msg, buffer);
q = msg->questions;
while (q)
{
encode_domain_name(buffer, q->qName);
put16bits(buffer, q->qType);
put16bits(buffer, q->qClass);
q = q->next;
}
rc = 0;
rc |= encode_resource_records(msg->answers, buffer);
rc |= encode_resource_records(msg->authorities, buffer);
rc |= encode_resource_records(msg->additionals, buffer);
return rc;
}
void free_resource_records(struct ResourceRecord* rr)
{
struct ResourceRecord* next;
while (rr) {
free(rr->name);
next = rr->next;
free(rr);
rr = next;
}
}
void free_questions(struct Question* qq)
{
struct Question* next;
while (qq) {
free(qq->qName);
next = qq->next;
free(qq);
qq = next;
}
}
int main()
{
// buffer for input/output binary packet
uint8_t buffer[BUF_SIZE];
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(struct sockaddr_in);
struct sockaddr_in addr;
int nbytes, rc;
int sock;
int port = 53;
struct Message msg;
memset(&msg, 0, sizeof(struct Message));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
sock = socket(AF_INET, SOCK_DGRAM, 0);
rc = bind(sock, (struct sockaddr*) &addr, addr_len);
if (rc != 0)
{
printf("Could not bind: %s\n", strerror(errno));
return 1;
}
printf("Listening on port %u.\n", port);
while (1)
{
free_questions(msg.questions);
free_resource_records(msg.answers);
free_resource_records(msg.authorities);
free_resource_records(msg.additionals);
memset(&msg, 0, sizeof(struct Message));
nbytes = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *) &client_addr, &addr_len);
if (decode_msg(&msg, buffer, nbytes) != 0) {
continue;
}
/* Print query */
print_query(&msg);
resolver_process(&msg);
/* Print response */
print_query(&msg);
uint8_t *p = buffer;
if (encode_msg(&msg, &p) != 0) {
continue;
}
int buflen = p - buffer;
sendto(sock, buffer, buflen, 0, (struct sockaddr*) &client_addr, addr_len);
}
}