Relicense Sortix to the ISC license.
I hereby relicense all my work on Sortix under the ISC license as below.
All Sortix contributions by other people are already under this license,
are not substantial enough to be copyrightable, or have been removed.
All imported code from other projects is compatible with this license.
All GPL licensed code from other projects had previously been removed.
Copyright 2011-2016 Jonas 'Sortie' Termansen and contributors.
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
2016-03-02 22:38:16 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2013, 2015, 2016 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
|
|
|
|
* copyright notice and this permission notice appear in all copies.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
*
|
|
|
|
* netdb/getaddrinfo.c
|
|
|
|
* Network address and service translation.
|
|
|
|
*/
|
2013-03-19 21:40:37 +00:00
|
|
|
|
2016-07-26 20:47:51 +00:00
|
|
|
#include <sys/dnsconfig.h>
|
2016-01-18 13:42:02 +00:00
|
|
|
#include <sys/socket.h>
|
2013-03-19 21:40:37 +00:00
|
|
|
|
2016-07-26 20:47:51 +00:00
|
|
|
#include <arpa/inet.h>
|
2016-01-18 13:42:02 +00:00
|
|
|
#include <ctype.h>
|
2016-07-26 20:47:51 +00:00
|
|
|
#include <errno.h>
|
|
|
|
#include <endian.h>
|
2016-01-18 13:42:02 +00:00
|
|
|
#include <netdb.h>
|
|
|
|
#include <netinet/in.h>
|
2016-07-26 20:47:51 +00:00
|
|
|
#include <poll.h>
|
2016-02-28 11:11:02 +00:00
|
|
|
#include <stdbool.h>
|
2013-03-19 21:40:37 +00:00
|
|
|
#include <stdlib.h>
|
2016-01-18 13:42:02 +00:00
|
|
|
#include <string.h>
|
2016-07-26 20:47:51 +00:00
|
|
|
#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;
|
|
|
|
}
|
2016-01-18 13:42:02 +00:00
|
|
|
|
|
|
|
static bool linkaddrinfo(struct addrinfo** restrict* res_ptr,
|
|
|
|
const struct addrinfo* restrict templ)
|
|
|
|
{
|
2016-07-26 20:47:51 +00:00
|
|
|
struct addrinfo* link =
|
|
|
|
(struct addrinfo*) calloc(1, sizeof(struct addrinfo));
|
2016-01-18 13:42:02 +00:00
|
|
|
if ( !link )
|
|
|
|
return false;
|
|
|
|
link->ai_flags = templ->ai_flags;
|
|
|
|
link->ai_family = templ->ai_family;
|
|
|
|
link->ai_socktype = templ->ai_socktype;
|
|
|
|
link->ai_protocol = templ->ai_protocol;
|
|
|
|
link->ai_addrlen = templ->ai_addrlen;
|
|
|
|
link->ai_addr = (struct sockaddr*) malloc(templ->ai_addrlen);
|
|
|
|
if ( !link->ai_addr )
|
|
|
|
return free(link), false;
|
|
|
|
memcpy(link->ai_addr, templ->ai_addr, templ->ai_addrlen);
|
2016-07-26 11:16:21 +00:00
|
|
|
link->ai_canonname = NULL;
|
|
|
|
if ( templ->ai_canonname )
|
|
|
|
{
|
|
|
|
link->ai_canonname = strdup(templ->ai_canonname);
|
|
|
|
if ( !link->ai_canonname )
|
|
|
|
return free(link->ai_addr), free(link), false;
|
|
|
|
}
|
2016-01-18 13:42:02 +00:00
|
|
|
**res_ptr = link;
|
|
|
|
*res_ptr = &link->ai_next;
|
|
|
|
return true;
|
|
|
|
}
|
2013-03-19 21:40:37 +00:00
|
|
|
|
2016-02-28 11:11:02 +00:00
|
|
|
int getaddrinfo(const char* restrict node,
|
|
|
|
const char* restrict servname,
|
|
|
|
const struct addrinfo* restrict hints,
|
|
|
|
struct addrinfo** restrict res)
|
2013-03-19 21:40:37 +00:00
|
|
|
{
|
2016-07-26 20:47:51 +00:00
|
|
|
int flags = 0;
|
|
|
|
int family = AF_UNSPEC;
|
|
|
|
int socktype = 0;
|
|
|
|
int protocol = 0;
|
|
|
|
|
|
|
|
if ( hints )
|
2016-01-18 13:42:02 +00:00
|
|
|
{
|
2016-07-26 20:47:51 +00:00
|
|
|
flags = hints->ai_flags;
|
|
|
|
family = hints->ai_family;
|
|
|
|
socktype = hints->ai_socktype;
|
|
|
|
protocol = hints->ai_protocol;
|
2016-01-18 13:42:02 +00:00
|
|
|
}
|
|
|
|
|
2016-07-26 20:47:51 +00:00
|
|
|
// 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;
|
|
|
|
|
2016-01-18 13:42:02 +00:00
|
|
|
if ( socktype == 0 )
|
|
|
|
socktype = SOCK_STREAM;
|
|
|
|
|
|
|
|
in_port_t port = 0;
|
|
|
|
if ( servname )
|
|
|
|
{
|
2016-07-26 20:47:51 +00:00
|
|
|
int errval = flags & AI_NUMERICSERV ? EAI_NONAME : EAI_SERVICE;
|
2016-01-18 13:42:02 +00:00
|
|
|
if ( isspace((unsigned char) servname[0]) )
|
2016-07-26 20:47:51 +00:00
|
|
|
return errval;
|
2016-01-18 13:42:02 +00:00
|
|
|
const char* end;
|
|
|
|
long portl = strtol(servname, (char**) &end, 10);
|
|
|
|
if ( end[0] )
|
2016-07-26 20:47:51 +00:00
|
|
|
return errval;
|
2016-01-18 13:42:02 +00:00
|
|
|
if ( (in_port_t) portl != portl )
|
2016-07-26 20:47:51 +00:00
|
|
|
return errval;
|
2016-01-18 13:42:02 +00:00
|
|
|
port = portl;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct addrinfo** res_orig = res;
|
|
|
|
*res = NULL;
|
|
|
|
|
2016-07-26 20:47:51 +00:00
|
|
|
struct sockaddr_in sin;
|
|
|
|
memset(&sin, 0, sizeof(sin));
|
|
|
|
struct sockaddr_in6 sin6;
|
|
|
|
memset(&sin6, 0, sizeof(sin6));
|
|
|
|
|
|
|
|
if ( !node || !strcmp(node, "localhost") )
|
2016-01-18 13:42:02 +00:00
|
|
|
{
|
2016-07-26 20:47:51 +00:00
|
|
|
if ( !node && !servname )
|
|
|
|
return EAI_NONAME;
|
2016-01-18 13:42:02 +00:00
|
|
|
bool any = false;
|
2016-07-26 20:47:51 +00:00
|
|
|
if ( family == AF_UNSPEC || family == AF_INET )
|
2016-01-18 13:42:02 +00:00
|
|
|
{
|
|
|
|
sin.sin_family = AF_INET;
|
2016-07-26 20:47:51 +00:00
|
|
|
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);
|
2016-01-18 13:42:02 +00:00
|
|
|
struct addrinfo templ;
|
|
|
|
memset(&templ, 0, sizeof(templ));
|
|
|
|
templ.ai_family = sin.sin_family;
|
|
|
|
templ.ai_socktype = socktype;
|
2016-07-26 20:47:51 +00:00
|
|
|
templ.ai_protocol = protocol;
|
2016-01-18 13:42:02 +00:00
|
|
|
templ.ai_addrlen = sizeof(sin);
|
|
|
|
templ.ai_addr = (struct sockaddr*) &sin;
|
|
|
|
if ( !linkaddrinfo(&res, &templ) )
|
|
|
|
return freeaddrinfo(*res_orig), EAI_MEMORY;
|
2016-07-26 20:47:51 +00:00
|
|
|
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 ( !linkaddrinfo(&res, &templ) )
|
|
|
|
return freeaddrinfo(*res_orig), EAI_MEMORY;
|
|
|
|
any = true;
|
2016-01-18 13:42:02 +00:00
|
|
|
}
|
|
|
|
if ( any )
|
|
|
|
return 0;
|
2016-07-26 20:47:51 +00:00
|
|
|
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;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
// TODO: Off by one? This used to be DNS_NAME_MAX, but is now +1.
|
|
|
|
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;
|
|
|
|
// TODO: Use recvfrom to get the server ip.
|
|
|
|
// TODO: Have a timeout if there's no reply and retransmit the request
|
|
|
|
// again.
|
|
|
|
resp_size = recv(fd, resp, sizeof(resp), 0);
|
|
|
|
if ( resp_size < 0 )
|
|
|
|
return close(fd), EAI_SYSTEM;
|
|
|
|
// TODO: Verify the response came from the correct ip.
|
|
|
|
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;
|
2016-01-18 13:42:02 +00:00
|
|
|
}
|
|
|
|
|
2016-07-26 20:47:51 +00:00
|
|
|
return 0;
|
2013-03-19 21:40:37 +00:00
|
|
|
}
|