diff --git a/libc/netdb/getaddrinfo.c b/libc/netdb/getaddrinfo.c index 80abfb9c..5d6523c7 100644 --- a/libc/netdb/getaddrinfo.c +++ b/libc/netdb/getaddrinfo.c @@ -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 #include +#include #include +#include +#include #include #include +#include #include #include #include +#include +#include +#include + +#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; }