mirror of
https://gitlab.com/sortix/sortix.git
synced 2023-02-13 20:55:38 -05:00
Add domain name system support to getaddrinfo(3).
This commit is contained in:
parent
580b71ae89
commit
08ddcc89b8
1 changed files with 711 additions and 22 deletions
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2013, 2015, 2016 Jonas 'Sortie' Termansen.
|
||||
* Copyright (c) 2013, 2015, 2016, 2022 Jonas 'Sortie' Termansen.
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
|
@ -17,19 +17,355 @@
|
|||
* Network address and service translation.
|
||||
*/
|
||||
|
||||
#include <sys/dnsconfig.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <endian.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <poll.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <timespec.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define DNS_SIZE 512
|
||||
#define DNS_NAME_MAX 255
|
||||
#define DNS_LABEL_MAX 64
|
||||
|
||||
struct dns_header
|
||||
{
|
||||
uint16_t id;
|
||||
uint16_t flags;
|
||||
uint16_t qdcount;
|
||||
uint16_t ancount;
|
||||
uint16_t nscount;
|
||||
uint16_t arcount;
|
||||
};
|
||||
|
||||
struct dns_question
|
||||
{
|
||||
uint16_t qtype;
|
||||
uint16_t qclass;
|
||||
};
|
||||
|
||||
struct dns_record
|
||||
{
|
||||
uint16_t type;
|
||||
uint16_t class;
|
||||
uint16_t ttl_high;
|
||||
uint16_t ttl_low;
|
||||
uint16_t rdlength;
|
||||
};
|
||||
|
||||
#define DNS_HEADER_FLAGS_RCODE_MASK (0xF << 0)
|
||||
#define DNS_HEADER_FLAGS_RCODE_NO (0 << 0)
|
||||
#define DNS_HEADER_FLAGS_RCODE_FORMAT (1 << 0)
|
||||
#define DNS_HEADER_FLAGS_RCODE_SERVER (2 << 0)
|
||||
#define DNS_HEADER_FLAGS_RCODE_NAME (3 << 0)
|
||||
#define DNS_HEADER_FLAGS_RCODE_NOT_IMPLEMENTED (4 << 0)
|
||||
#define DNS_HEADER_FLAGS_RCODE_REFUSED (5 << 0)
|
||||
#define DNS_HEADER_FLAGS_RA (1 << 7)
|
||||
#define DNS_HEADER_FLAGS_RD (1 << 8)
|
||||
#define DNS_HEADER_FLAGS_TC (1 << 9)
|
||||
#define DNS_HEADER_FLAGS_AA (1 << 10)
|
||||
#define DNS_HEADER_FLAGS_OPCODE_MASK (0xF << 11)
|
||||
#define DNS_HEADER_FLAGS_OPCODE_QUERY (0 << 11)
|
||||
#define DNS_HEADER_FLAGS_OPCODE_IQUERY (1 << 11)
|
||||
#define DNS_HEADER_FLAGS_OPCODE_STATUS (2 << 11)
|
||||
#define DNS_HEADER_FLAGS_QR (1 << 15)
|
||||
|
||||
#define DNS_TYPE_A 1
|
||||
#define DNS_TYPE_NS 2
|
||||
#define DNS_TYPE_MD 3
|
||||
#define DNS_TYPE_MF 4
|
||||
#define DNS_TYPE_CNAME 5
|
||||
#define DNS_TYPE_SOA 6
|
||||
#define DNS_TYPE_MB 7
|
||||
#define DNS_TYPE_MG 8
|
||||
#define DNS_TYPE_MR 9
|
||||
#define DNS_TYPE_NULL 10
|
||||
#define DNS_TYPE_WKS 11
|
||||
#define DNS_TYPE_PTR 12
|
||||
#define DNS_TYPE_HINFO 13
|
||||
#define DNS_TYPE_MINFO 14
|
||||
#define DNS_TYPE_MX 15
|
||||
#define DNS_TYPE_TXT 16
|
||||
#define DNS_TYPE_AAAA 28
|
||||
|
||||
#define DNS_QTYPE_AXFR 252
|
||||
#define DNS_QTYPE_MAILB 253
|
||||
#define DNS_QTYPE_MAILA 254
|
||||
#define DNS_QTYPE_ANY 255
|
||||
|
||||
#define DNS_CLASS_IN 1
|
||||
#define DNS_CLASS_CS 2
|
||||
#define DNS_CLASS_CH 3
|
||||
#define DNS_CLASS_HS 4
|
||||
|
||||
#define DNS_QCLASS_ANY 255
|
||||
|
||||
static bool encode_dns_header(unsigned char* msg,
|
||||
size_t* offset_ptr,
|
||||
const struct dns_header* hdrin)
|
||||
{
|
||||
size_t offset = *offset_ptr;
|
||||
struct dns_header hdr;
|
||||
if ( DNS_SIZE - offset < sizeof(hdr) )
|
||||
return false;
|
||||
hdr.id = htobe16(hdrin->id);
|
||||
hdr.flags = htobe16(hdrin->flags);
|
||||
hdr.qdcount = htobe16(hdrin->qdcount);
|
||||
hdr.ancount = htobe16(hdrin->ancount);
|
||||
hdr.nscount = htobe16(hdrin->nscount);
|
||||
hdr.arcount = htobe16(hdrin->arcount);
|
||||
memcpy(msg + offset, &hdr, sizeof(hdr));
|
||||
*offset_ptr = offset + sizeof(hdr);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool encode_dns_byte(unsigned char* msg,
|
||||
size_t* offset_ptr,
|
||||
unsigned char byte)
|
||||
{
|
||||
size_t offset = *offset_ptr;
|
||||
if ( DNS_SIZE - offset < 1 )
|
||||
return false;
|
||||
msg[offset] = byte;
|
||||
*offset_ptr = offset + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Enforce things like proper use of dashes and restrict to allowed ASCII
|
||||
// sequences (but see internationalized domain names).
|
||||
// TODO: Simplify this further.
|
||||
static bool is_valid_name(const char* name)
|
||||
{
|
||||
size_t index = 0;
|
||||
size_t namelen = 0;
|
||||
if ( !name[0] )
|
||||
return false; /* unexpected end of input */
|
||||
while ( name[index] )
|
||||
{
|
||||
if ( !strcmp(name + index, ".") )
|
||||
break;
|
||||
if ( name[index] == '.' )
|
||||
return false; /* empty label */
|
||||
size_t length = strcspn(name + index, ".");
|
||||
if ( DNS_LABEL_MAX <= length )
|
||||
return false; /* label too long */
|
||||
if ( namelen++ == DNS_NAME_MAX )
|
||||
return false; /* name is too long */
|
||||
for ( size_t i = 0; i < length; i++ )
|
||||
{
|
||||
if ( namelen++ == DNS_NAME_MAX )
|
||||
return false; /* name is too long */
|
||||
}
|
||||
index += length;
|
||||
if ( name[index] == '.' )
|
||||
index++;
|
||||
}
|
||||
if ( namelen++ == DNS_NAME_MAX )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool encode_dns_name(unsigned char* msg,
|
||||
size_t* offset_ptr,
|
||||
const char* name)
|
||||
{
|
||||
size_t index = 0;
|
||||
size_t namelen = 0;
|
||||
if ( !name[0] )
|
||||
return false;
|
||||
while ( name[index] )
|
||||
{
|
||||
if ( !strcmp(name + index, ".") )
|
||||
break;
|
||||
if ( name[index] == '.' )
|
||||
return false;
|
||||
size_t length = strcspn(name + index, ".");
|
||||
if ( DNS_LABEL_MAX <= length )
|
||||
return false;
|
||||
if ( namelen++ == DNS_NAME_MAX )
|
||||
return false;
|
||||
if ( !encode_dns_byte(msg, offset_ptr, length & 0xFF) )
|
||||
return false;
|
||||
for ( size_t i = 0; i < length; i++ )
|
||||
{
|
||||
if ( namelen++ == DNS_NAME_MAX )
|
||||
return false;
|
||||
if ( !encode_dns_byte(msg, offset_ptr, name[index + i]) )
|
||||
return false;
|
||||
}
|
||||
index += length;
|
||||
if ( name[index] == '.' )
|
||||
index++;
|
||||
}
|
||||
if ( namelen++ == DNS_NAME_MAX )
|
||||
return false;
|
||||
if ( !encode_dns_byte(msg, offset_ptr, 0) )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool encode_dns_question(unsigned char* msg,
|
||||
size_t* offset_ptr,
|
||||
const char* name,
|
||||
const struct dns_question* qsin)
|
||||
{
|
||||
if ( !encode_dns_name(msg, offset_ptr, name) )
|
||||
return false;
|
||||
size_t offset = *offset_ptr;
|
||||
struct dns_question qs;
|
||||
if ( DNS_SIZE - offset < sizeof(qs) )
|
||||
return false;
|
||||
qs.qtype = htobe16(qsin->qtype);
|
||||
qs.qclass = htobe16(qsin->qclass);
|
||||
memcpy(msg + offset, &qs, sizeof(qs));
|
||||
*offset_ptr = offset + sizeof(qs);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool decode_dns_header(const unsigned char* msg,
|
||||
size_t* offset_ptr,
|
||||
size_t msg_size,
|
||||
struct dns_header* hdrout)
|
||||
{
|
||||
size_t offset = *offset_ptr;
|
||||
struct dns_header hdr;
|
||||
if ( msg_size - offset < sizeof(hdr) )
|
||||
return false;
|
||||
memcpy(&hdr, msg + offset, sizeof(hdr));
|
||||
hdrout->id = be16toh(hdr.id);
|
||||
hdrout->flags = be16toh(hdr.flags);
|
||||
hdrout->qdcount = be16toh(hdr.qdcount);
|
||||
hdrout->ancount = be16toh(hdr.ancount);
|
||||
hdrout->nscount = be16toh(hdr.nscount);
|
||||
hdrout->arcount = be16toh(hdr.arcount);
|
||||
*offset_ptr = offset + sizeof(hdr);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool decode_dns_byte(const unsigned char* msg,
|
||||
size_t* offset_ptr,
|
||||
size_t msg_size,
|
||||
unsigned char* byte)
|
||||
{
|
||||
size_t offset = *offset_ptr;
|
||||
if ( msg_size <= offset || msg_size - offset < 1 )
|
||||
return false;
|
||||
*byte = msg[offset];
|
||||
*offset_ptr = offset + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool decode_dns_name(const unsigned char* msg,
|
||||
size_t* offset_ptr,
|
||||
size_t msg_size,
|
||||
char* name)
|
||||
{
|
||||
bool real_offset_set = false;
|
||||
size_t real_offset = 0;
|
||||
size_t index = 0;
|
||||
size_t namelen = 0;
|
||||
uint8_t b;
|
||||
while ( true )
|
||||
{
|
||||
if ( namelen++ == DNS_NAME_MAX )
|
||||
return false;
|
||||
if ( !decode_dns_byte(msg, offset_ptr, msg_size, &b) )
|
||||
return false;
|
||||
if ( 0xC0 & b )
|
||||
{
|
||||
namelen--;
|
||||
size_t ptr = (b & 0x3F) << 8;
|
||||
if ( !decode_dns_byte(msg, offset_ptr, msg_size, &b) )
|
||||
return false;
|
||||
ptr |= b;
|
||||
if ( !real_offset_set )
|
||||
{
|
||||
real_offset = *offset_ptr;
|
||||
real_offset_set = true;
|
||||
}
|
||||
*offset_ptr = ptr;
|
||||
continue;
|
||||
}
|
||||
size_t length = b;
|
||||
if ( DNS_LABEL_MAX <= length )
|
||||
return false;
|
||||
if ( !length )
|
||||
break;
|
||||
if ( index )
|
||||
name[index++] = '.';
|
||||
for ( size_t i = 0; i < length; i++ )
|
||||
{
|
||||
if ( namelen++ == DNS_NAME_MAX )
|
||||
return false;
|
||||
if ( !decode_dns_byte(msg, offset_ptr, msg_size, &b) )
|
||||
return false;
|
||||
// TODO: Handle if b == '.'.
|
||||
name[index++] = b;
|
||||
}
|
||||
}
|
||||
name[index++] = '.';
|
||||
name[index] = '\0';
|
||||
if ( real_offset_set )
|
||||
*offset_ptr = real_offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool decode_dns_question(const unsigned char* msg,
|
||||
size_t* offset_ptr,
|
||||
size_t msg_size,
|
||||
char* name,
|
||||
struct dns_question* qsout)
|
||||
{
|
||||
if ( !decode_dns_name(msg, offset_ptr, msg_size, name) )
|
||||
return false;
|
||||
size_t offset = *offset_ptr;
|
||||
struct dns_question qs;
|
||||
if ( msg_size <= offset || msg_size - offset < sizeof(qs) )
|
||||
return false;
|
||||
memcpy(&qs, msg + offset, sizeof(qs));
|
||||
qsout->qtype = be16toh(qs.qtype);
|
||||
qsout->qclass = be16toh(qs.qclass);
|
||||
*offset_ptr = offset + sizeof(qs);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool decode_dns_record(const unsigned char* msg,
|
||||
size_t* offset_ptr,
|
||||
size_t msg_size,
|
||||
char* name,
|
||||
struct dns_record* rrout)
|
||||
{
|
||||
if ( !decode_dns_name(msg, offset_ptr, msg_size, name) )
|
||||
return false;
|
||||
size_t offset = *offset_ptr;
|
||||
struct dns_record rr;
|
||||
if ( msg_size <= offset || msg_size - offset < sizeof(rr) )
|
||||
return false;
|
||||
memcpy(&rr, msg + offset, sizeof(rr));
|
||||
rrout->type = be16toh(rr.type);
|
||||
rrout->class = be16toh(rr.class);
|
||||
rrout->ttl_high = be16toh(rr.ttl_high);
|
||||
rrout->ttl_low = be16toh(rr.ttl_low);
|
||||
rrout->rdlength = be16toh(rr.rdlength);
|
||||
*offset_ptr = offset + sizeof(rr);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool linkaddrinfo(struct addrinfo** restrict* res_ptr,
|
||||
const struct addrinfo* restrict templ)
|
||||
{
|
||||
struct addrinfo* link = (struct addrinfo*) calloc(1, sizeof(struct addrinfo));
|
||||
struct addrinfo* link =
|
||||
(struct addrinfo*) calloc(1, sizeof(struct addrinfo));
|
||||
if ( !link )
|
||||
return false;
|
||||
link->ai_flags = templ->ai_flags;
|
||||
|
@ -58,59 +394,412 @@ int getaddrinfo(const char* restrict node,
|
|||
const struct addrinfo* restrict hints,
|
||||
struct addrinfo** restrict res)
|
||||
{
|
||||
struct addrinfo hints_def;
|
||||
if ( !hints )
|
||||
int flags = 0;
|
||||
int family = AF_UNSPEC;
|
||||
int socktype = 0;
|
||||
int protocol = 0;
|
||||
|
||||
if ( hints )
|
||||
{
|
||||
memset(&hints_def, 0, sizeof(hints_def));
|
||||
hints_def.ai_family = AF_UNSPEC;
|
||||
hints = &hints_def;
|
||||
flags = hints->ai_flags;
|
||||
family = hints->ai_family;
|
||||
socktype = hints->ai_socktype;
|
||||
protocol = hints->ai_protocol;
|
||||
}
|
||||
|
||||
int socktype = hints->ai_socktype;
|
||||
// TODO: Implement missing flags.
|
||||
int supported = AI_PASSIVE | AI_NUMERICHOST | AI_NUMERICSERV | AI_CANONNAME;
|
||||
if ( flags & ~supported )
|
||||
return EAI_BADFLAGS;
|
||||
|
||||
// TODO: IPv6 support.
|
||||
if ( family != AF_UNSPEC && family != AF_INET )
|
||||
return EAI_FAMILY;
|
||||
family = AF_INET;
|
||||
|
||||
if ( socktype == 0 )
|
||||
socktype = SOCK_STREAM;
|
||||
|
||||
in_port_t port = 0;
|
||||
if ( servname )
|
||||
{
|
||||
int errval = flags & AI_NUMERICSERV ? EAI_NONAME : EAI_SERVICE;
|
||||
if ( isspace((unsigned char) servname[0]) )
|
||||
return EAI_SERVICE;
|
||||
return errval;
|
||||
const char* end;
|
||||
long portl = strtol(servname, (char**) &end, 10);
|
||||
if ( end[0] )
|
||||
return EAI_SERVICE;
|
||||
return errval;
|
||||
if ( (in_port_t) portl != portl )
|
||||
return EAI_SERVICE;
|
||||
return errval;
|
||||
port = portl;
|
||||
}
|
||||
|
||||
struct addrinfo** res_orig = res;
|
||||
*res = NULL;
|
||||
|
||||
if ( !node )
|
||||
struct sockaddr_in sin;
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
struct sockaddr_in6 sin6;
|
||||
memset(&sin6, 0, sizeof(sin6));
|
||||
|
||||
if ( !node || !strcasecmp(node, "localhost") )
|
||||
{
|
||||
if ( !node && !servname )
|
||||
return EAI_NONAME;
|
||||
bool any = false;
|
||||
if ( hints->ai_family == AF_UNSPEC || hints->ai_family == AF_INET )
|
||||
if ( family == AF_UNSPEC || family == AF_INET )
|
||||
{
|
||||
any = true;
|
||||
struct sockaddr_in sin;
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_port = htons(port);
|
||||
memcpy(&sin.sin_addr, "\x7F\x00\x00\x01", 4);
|
||||
sin.sin_port = htobe16(port);
|
||||
if ( flags & AI_PASSIVE )
|
||||
sin.sin_addr.s_addr = htobe32(INADDR_ANY);
|
||||
else
|
||||
sin.sin_addr.s_addr = htobe32(INADDR_LOOPBACK);
|
||||
struct addrinfo templ;
|
||||
memset(&templ, 0, sizeof(templ));
|
||||
templ.ai_family = sin.sin_family;
|
||||
templ.ai_socktype = socktype;
|
||||
templ.ai_protocol = hints->ai_protocol;
|
||||
templ.ai_protocol = protocol;
|
||||
templ.ai_addrlen = sizeof(sin);
|
||||
templ.ai_addr = (struct sockaddr*) &sin;
|
||||
if ( flags & AI_CANONNAME )
|
||||
templ.ai_canonname = "localhost";
|
||||
if ( !linkaddrinfo(&res, &templ) )
|
||||
return freeaddrinfo(*res_orig), EAI_MEMORY;
|
||||
any = true;
|
||||
}
|
||||
if ( family == AF_UNSPEC || family == AF_INET6 )
|
||||
{
|
||||
sin6.sin6_family = AF_INET6;
|
||||
sin6.sin6_port = htobe16(port);
|
||||
sin6.sin6_flowinfo = 0;
|
||||
if ( flags & AI_PASSIVE )
|
||||
sin6.sin6_addr = in6addr_any;
|
||||
else
|
||||
sin6.sin6_addr = in6addr_loopback;
|
||||
sin6.sin6_scope_id = 0;
|
||||
struct addrinfo templ;
|
||||
memset(&templ, 0, sizeof(templ));
|
||||
templ.ai_family = sin6.sin6_family;
|
||||
templ.ai_socktype = socktype;
|
||||
templ.ai_protocol = protocol;
|
||||
templ.ai_addrlen = sizeof(sin6);
|
||||
templ.ai_addr = (struct sockaddr*) &sin6;
|
||||
if ( flags & AI_CANONNAME )
|
||||
templ.ai_canonname = "localhost";
|
||||
if ( !linkaddrinfo(&res, &templ) )
|
||||
return freeaddrinfo(*res_orig), EAI_MEMORY;
|
||||
any = true;
|
||||
}
|
||||
if ( any )
|
||||
return 0;
|
||||
return EAI_NONAME;
|
||||
}
|
||||
|
||||
if ( (family == AF_UNSPEC || family == AF_INET) )
|
||||
{
|
||||
// POSIX divergence: Use inet_pton instead of inet_addr format.
|
||||
if ( inet_pton(AF_INET, node, &sin.sin_addr) == 1 )
|
||||
{
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_port = htobe16(port);
|
||||
struct addrinfo templ;
|
||||
memset(&templ, 0, sizeof(templ));
|
||||
templ.ai_family = sin.sin_family;
|
||||
templ.ai_socktype = socktype;
|
||||
templ.ai_protocol = protocol;
|
||||
templ.ai_addrlen = sizeof(sin);
|
||||
templ.ai_addr = (struct sockaddr*) &sin;
|
||||
if ( !linkaddrinfo(&res, &templ) )
|
||||
return freeaddrinfo(*res_orig), EAI_MEMORY;
|
||||
}
|
||||
if ( any )
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return EAI_NONAME;
|
||||
if ( (family == AF_UNSPEC || family == AF_INET6) )
|
||||
{
|
||||
if ( inet_pton(AF_INET6, node, &sin6.sin6_addr) == 1 )
|
||||
{
|
||||
sin6.sin6_family = AF_INET6;
|
||||
sin6.sin6_port = htobe16(port);
|
||||
sin6.sin6_flowinfo = 0;
|
||||
sin6.sin6_scope_id = 0;
|
||||
struct addrinfo templ;
|
||||
memset(&templ, 0, sizeof(templ));
|
||||
templ.ai_family = sin6.sin6_family;
|
||||
templ.ai_socktype = socktype;
|
||||
templ.ai_protocol = protocol;
|
||||
templ.ai_addrlen = sizeof(sin6);
|
||||
templ.ai_addr = (struct sockaddr*) &sin6;
|
||||
if ( !linkaddrinfo(&res, &templ) )
|
||||
return freeaddrinfo(*res_orig), EAI_MEMORY;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ( flags & AI_NUMERICHOST )
|
||||
return EAI_NONAME;
|
||||
|
||||
if ( !is_valid_name(node) )
|
||||
return EAI_NONAME;
|
||||
|
||||
size_t encoded_size = 0;
|
||||
unsigned char encoded[DNS_SIZE];
|
||||
if ( !encode_dns_name(encoded, &encoded_size, node) )
|
||||
return EAI_NONAME;
|
||||
size_t decoded_size = 0;
|
||||
char target[DNS_NAME_MAX + 1];
|
||||
if ( !decode_dns_name(encoded, &decoded_size, encoded_size, target) )
|
||||
return EAI_NONAME;
|
||||
|
||||
int cname_retries = 0;
|
||||
retry_cname:
|
||||
if ( 5 < cname_retries++ )
|
||||
return EAI_NONAME;
|
||||
|
||||
struct dnsconfig dnsconfig;
|
||||
if ( getdnsconfig(&dnsconfig) < 0 )
|
||||
return EAI_SYSTEM;
|
||||
|
||||
// TODO: Potentially do a blocking wait for DNS configuration to come up (or
|
||||
// for the automatic configuration to time out) if it isn't ready yet.
|
||||
if ( dnsconfig.servers_count == 0 )
|
||||
return EAI_NONAME;
|
||||
|
||||
// TODO: Send requests to all the servers rather than picking the first one
|
||||
// a socket can be made for.
|
||||
int fd = -1;
|
||||
size_t server_index = 0;
|
||||
struct dnsconfig_server* server = NULL;
|
||||
for ( ; server_index < dnsconfig.servers_count; server_index++ )
|
||||
{
|
||||
server = &dnsconfig.servers[server_index];
|
||||
if ( 0 <= (fd = socket(server->family, SOCK_DGRAM, 0)) )
|
||||
break;
|
||||
}
|
||||
if ( server_index == dnsconfig.servers_count )
|
||||
return EAI_SYSTEM;
|
||||
|
||||
struct sockaddr* addr;
|
||||
size_t addr_size;
|
||||
if ( server->family == AF_INET )
|
||||
{
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_port = htobe16(53);
|
||||
memcpy(&sin.sin_addr, &server->addr, sizeof(sin.sin_addr));
|
||||
addr = (struct sockaddr*) &sin;
|
||||
addr_size = sizeof(sin);
|
||||
}
|
||||
else if ( server->family == AF_INET6 )
|
||||
{
|
||||
memset(&sin6, 0, sizeof(sin6));
|
||||
sin6.sin6_family = AF_INET6;
|
||||
sin6.sin6_port = htobe16(53);
|
||||
memcpy(&sin6.sin6_addr, &server->addr, sizeof(sin6.sin6_addr));
|
||||
addr = (struct sockaddr*) &sin6;
|
||||
addr_size = sizeof(sin6);
|
||||
}
|
||||
else
|
||||
return EAI_FAMILY;
|
||||
|
||||
if ( connect(fd, addr, addr_size) < 0 )
|
||||
return close(fd), EAI_SYSTEM;
|
||||
|
||||
uint16_t id = arc4random() & 0xFFFF;
|
||||
|
||||
unsigned char req[DNS_SIZE];
|
||||
size_t req_size = 0;
|
||||
struct dns_header hdr;
|
||||
hdr.id = id;
|
||||
hdr.flags = DNS_HEADER_FLAGS_RD;
|
||||
hdr.qdcount = 1;
|
||||
hdr.ancount = 0;
|
||||
hdr.nscount = 0;
|
||||
hdr.arcount = 0;
|
||||
if ( !encode_dns_header(req, &req_size, &hdr) )
|
||||
return close(fd), EAI_OVERFLOW;
|
||||
struct dns_question qs;
|
||||
qs.qtype = 0;
|
||||
if ( family == AF_INET )
|
||||
qs.qtype = DNS_TYPE_A;
|
||||
else if ( family == AF_INET6 )
|
||||
qs.qtype = DNS_TYPE_AAAA;
|
||||
qs.qclass = DNS_CLASS_IN;
|
||||
if ( !encode_dns_question(req, &req_size, node, &qs) )
|
||||
return close(fd), EAI_OVERFLOW;
|
||||
|
||||
struct timespec last_sent = timespec_nul();
|
||||
struct timespec timeout = timespec_nul();
|
||||
|
||||
unsigned char resp[DNS_SIZE];
|
||||
ssize_t resp_size;
|
||||
size_t offset;
|
||||
unsigned int retransmissions = 0;
|
||||
while ( true )
|
||||
{
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
struct timespec since_sent = timespec_sub(now, last_sent);
|
||||
if ( timespec_le(timeout, since_sent) )
|
||||
{
|
||||
if ( 2 <= retransmissions )
|
||||
return close(fd), EAI_AGAIN;
|
||||
ssize_t amount = send(fd, req, req_size, 0);
|
||||
if ( amount < 0 )
|
||||
return close(fd), EAI_SYSTEM;
|
||||
timeout = timespec_make(2, 500000000);
|
||||
last_sent = now;
|
||||
retransmissions++;
|
||||
}
|
||||
struct timespec left =
|
||||
timespec_sub(timespec_add(last_sent, timeout), now);
|
||||
struct pollfd pfd = { 0 };
|
||||
pfd.fd = fd;
|
||||
pfd.events = POLLIN;
|
||||
int num_events = ppoll(&pfd, 1, &left, NULL);
|
||||
if ( num_events < 0 )
|
||||
return close(fd), EAI_SYSTEM;
|
||||
if ( num_events == 0 )
|
||||
continue;
|
||||
resp_size = recv(fd, resp, sizeof(resp), 0);
|
||||
if ( resp_size < 0 )
|
||||
return close(fd), EAI_SYSTEM;
|
||||
offset = 0;
|
||||
if ( !decode_dns_header(resp, &offset, resp_size, &hdr) )
|
||||
continue;
|
||||
if ( hdr.id != id )
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Return the correct errors below. It may be the best behavior to
|
||||
// simply drop any responses with errors.
|
||||
uint16_t rcode = hdr.flags & DNS_HEADER_FLAGS_RCODE_MASK;
|
||||
if ( rcode == DNS_HEADER_FLAGS_RCODE_FORMAT )
|
||||
return close(fd), EAI_FAIL;
|
||||
else if ( rcode == DNS_HEADER_FLAGS_RCODE_SERVER )
|
||||
return close(fd), EAI_FAIL;
|
||||
else if ( rcode == DNS_HEADER_FLAGS_RCODE_NAME )
|
||||
return close(fd), EAI_NONAME;
|
||||
else if ( rcode == DNS_HEADER_FLAGS_RCODE_NOT_IMPLEMENTED )
|
||||
return close(fd), EAI_FAIL;
|
||||
else if ( rcode == DNS_HEADER_FLAGS_RCODE_REFUSED )
|
||||
return close(fd), EAI_FAIL;
|
||||
else if ( rcode != DNS_HEADER_FLAGS_RCODE_NO )
|
||||
return close(fd), EAI_FAIL;
|
||||
|
||||
// TODO: Reconnect to server using TCP to get full response.
|
||||
if ( hdr.flags & DNS_HEADER_FLAGS_TC )
|
||||
return close(fd), EAI_FAIL;
|
||||
|
||||
// TODO: Check query bit.
|
||||
|
||||
for ( uint16_t i = 0; i < hdr.qdcount; i++ )
|
||||
{
|
||||
char name[DNS_NAME_MAX + 1];
|
||||
if ( !decode_dns_question(resp, &offset, resp_size, name, &qs) )
|
||||
return close(fd), EAI_OVERFLOW;
|
||||
}
|
||||
|
||||
bool any = false;
|
||||
bool found_cname = false;
|
||||
|
||||
for ( uint16_t i = 0; i < hdr.ancount; i++ )
|
||||
{
|
||||
char name[DNS_NAME_MAX + 1];
|
||||
struct dns_record rr;
|
||||
if ( !decode_dns_record(resp, &offset, resp_size, name, &rr) )
|
||||
return close(fd), freeaddrinfo(*res_orig), EAI_OVERFLOW;
|
||||
bool match = strcmp(name, target) == 0;
|
||||
// TODO: Support aliases.
|
||||
if ( rr.class == DNS_CLASS_IN && rr.type == DNS_TYPE_A )
|
||||
{
|
||||
unsigned char ip[4];
|
||||
for ( size_t i = 0; i < 4; i++ )
|
||||
if ( !decode_dns_byte(resp, &offset, resp_size, &ip[i]) )
|
||||
return close(fd), freeaddrinfo(*res_orig), EAI_OVERFLOW;
|
||||
if ( match && (family == AF_UNSPEC || family == AF_INET) )
|
||||
{
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_port = htobe16(port);
|
||||
memcpy(&sin.sin_addr, ip, sizeof(sin.sin_addr));
|
||||
struct addrinfo templ;
|
||||
memset(&templ, 0, sizeof(templ));
|
||||
templ.ai_family = sin.sin_family;
|
||||
templ.ai_socktype = socktype;
|
||||
templ.ai_protocol = protocol;
|
||||
templ.ai_addrlen = sizeof(sin);
|
||||
templ.ai_addr = (struct sockaddr*) &sin;
|
||||
if ( !linkaddrinfo(&res, &templ) )
|
||||
return close(fd), freeaddrinfo(*res_orig), EAI_MEMORY;
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
else if ( rr.class == DNS_CLASS_IN && rr.type == DNS_TYPE_AAAA )
|
||||
{
|
||||
unsigned char ip[16];
|
||||
for ( size_t i = 0; i < 16; i++ )
|
||||
if ( !decode_dns_byte(resp, &offset, resp_size, &ip[i]) )
|
||||
return close(fd), freeaddrinfo(*res_orig), EAI_OVERFLOW;
|
||||
if ( match && (family == AF_UNSPEC || family == AF_INET6) )
|
||||
{
|
||||
sin6.sin6_family = AF_INET6;
|
||||
sin6.sin6_port = htobe16(port);
|
||||
sin6.sin6_flowinfo = 0;
|
||||
memcpy(&sin6.sin6_addr, ip, sizeof(sin6.sin6_addr));
|
||||
sin6.sin6_scope_id = 0;
|
||||
struct addrinfo templ;
|
||||
memset(&templ, 0, sizeof(templ));
|
||||
templ.ai_family = sin6.sin6_family;
|
||||
templ.ai_socktype = socktype;
|
||||
templ.ai_protocol = protocol;
|
||||
templ.ai_addrlen = sizeof(sin6);
|
||||
templ.ai_addr = (struct sockaddr*) &sin6;
|
||||
if ( !linkaddrinfo(&res, &templ) )
|
||||
return close(fd), freeaddrinfo(*res_orig), EAI_MEMORY;
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
else if ( rr.type == DNS_TYPE_CNAME )
|
||||
{
|
||||
char cname[DNS_NAME_MAX + 1];
|
||||
if ( !decode_dns_name(resp, &offset, resp_size, cname) )
|
||||
return close(fd), freeaddrinfo(*res_orig), EAI_OVERFLOW;
|
||||
if ( match )
|
||||
{
|
||||
// TODO: Report CNAME to caller.
|
||||
memcpy(target, cname, sizeof(target));
|
||||
found_cname = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for ( size_t i = 0; i < rr.rdlength; i++ )
|
||||
{
|
||||
unsigned char b;
|
||||
if ( !decode_dns_byte(resp, &offset, resp_size, &b) )
|
||||
return close(fd), freeaddrinfo(*res_orig), EAI_OVERFLOW;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(fd);
|
||||
|
||||
if ( !any )
|
||||
{
|
||||
if ( found_cname )
|
||||
{
|
||||
freeaddrinfo(*res_orig);
|
||||
res = res_orig;
|
||||
*res = NULL;
|
||||
goto retry_cname;
|
||||
|
||||
}
|
||||
return EAI_NONAME;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue