@ -33,18 +33,23 @@
# include "ares.h"
# include "ares_private.h"
# include "ares_dns.h"
struct search_query {
/* Arguments passed to ares_search */
/* Arguments passed to ares_search() */
ares_channel_t * channel ;
char * name ; /* copied into an allocated buffer */
int dnsclass ;
int type ;
ares_callback callback ;
void * arg ;
char * * domains ; /* duplicate for ares_reinit() safety */
/* DNS record passed to ares_search(), encoded in string format */
unsigned char * buf ;
size_t buflen ;
/* Duplicate of channel domains for ares_reinit() safety */
char * * domains ;
size_t ndomains ;
/* State tracking progress through the search query */
int status_as_is ; /* error status from trying as-is */
size_t next_domain ; /* next search domain to try */
ares_bool_t trying_as_is ; /* current query is for name as-is */
@ -52,21 +57,47 @@ struct search_query {
ares_bool_t ever_got_nodata ; /* did we ever get ARES_ENODATA along the way? */
} ;
/* Callback argument structure passed to ares__dnsrec_convert_cb(). */
struct dnsrec_convert_arg {
ares_callback_dnsrec callback ;
void * arg ;
} ;
static void search_callback ( void * arg , int status , int timeouts ,
unsigned char * abuf , int alen ) ;
static ares_status_t ares__write_and_send_query ( ares_channel_t * channel ,
ares_dns_record_t * dnsrec ,
char * altname ,
ares_callback callback ,
void * arg ) ;
static void end_squery ( struct search_query * squery , ares_status_t status ,
unsigned char * abuf , size_t alen ) ;
static void ares__dnsrec_convert_cb ( void * arg , int status , int timeouts ,
unsigned char * abuf , int alen ) ;
static void ares_search_int ( ares_channel_t * channel , const char * name ,
int dnsclass , int type , ares_callback callback ,
void * arg )
static void ares_search_int ( ares_channel_t * channel , ares_dns_record_t * dnsrec ,
ares_callback callback , void * arg )
{
struct search_query * squery ;
char * s ;
const char * name ;
char * s = NULL ;
const char * p ;
ares_status_t status ;
size_t ndots ;
/* Extract the name for the search. Note that searches are only supported for
* DNS records containing a single query .
*/
if ( ares_dns_record_query_cnt ( dnsrec ) ! = 1 ) {
callback ( arg , ARES_EBADQUERY , 0 , NULL , 0 ) ;
return ;
}
status = ares_dns_record_query_get ( dnsrec , 0 , & name , NULL , NULL ) ;
if ( status ! = ARES_SUCCESS ) {
callback ( arg , ( int ) status , 0 , NULL , 0 ) ;
return ;
}
/* Per RFC 7686, reject queries for ".onion" domain names with NXDOMAIN. */
if ( ares__is_onion_domain ( name ) ) {
callback ( arg , ARES_ENOTFOUND , 0 , NULL , 0 ) ;
@ -74,16 +105,19 @@ static void ares_search_int(ares_channel_t *channel, const char *name,
}
/* If name only yields one domain to search, then we don't have
* to keep extra state , so just do an ares_query ( ) .
* to keep extra state , so just do an ares_send ( ) .
*/
status = ares__single_domain ( channel , name , & s ) ;
if ( status ! = ARES_SUCCESS ) {
callback ( arg , ( int ) status , 0 , NULL , 0 ) ;
return ;
}
if ( s ) {
ares_query ( channel , s , dnsclass , type , callback , arg ) ;
} else if ( s ! = NULL ) {
/* We only have a single domain to search, so do it here. */
status = ares__write_and_send_query ( channel , dnsrec , s , callback , arg ) ;
ares_free ( s ) ;
if ( status ! = ARES_SUCCESS ) {
callback ( arg , ( int ) status , 0 , NULL , 0 ) ;
}
return ;
}
@ -96,10 +130,14 @@ static void ares_search_int(ares_channel_t *channel, const char *name,
return ;
}
squery - > channel = channel ;
squery - > name = ares_strdup ( name ) ;
if ( ! squery - > name ) {
/* We pass the DNS record through the search_query structure by encoding it
* into a buffer and then later decoding it back .
*/
status = ares_dns_write ( dnsrec , & squery - > buf , & squery - > buflen ) ;
if ( status ! = ARES_SUCCESS ) {
ares_free ( squery ) ;
callback ( arg , ARES_ENOMEM , 0 , NULL , 0 ) ;
callback ( arg , ( int ) status , 0 , NULL , 0 ) ;
return ;
}
@ -108,7 +146,7 @@ static void ares_search_int(ares_channel_t *channel, const char *name,
squery - > domains =
ares__strsplit_duplicate ( channel - > domains , channel - > ndomains ) ;
if ( squery - > domains = = NULL ) {
ares_free ( squery - > name ) ;
ares_free ( squery - > buf ) ;
ares_free ( squery ) ;
callback ( arg , ARES_ENOMEM , 0 , NULL , 0 ) ;
return ;
@ -116,8 +154,6 @@ static void ares_search_int(ares_channel_t *channel, const char *name,
squery - > ndomains = channel - > ndomains ;
}
squery - > dnsclass = dnsclass ;
squery - > type = type ;
squery - > status_as_is = - 1 ;
squery - > callback = callback ;
squery - > arg = arg ;
@ -140,32 +176,83 @@ static void ares_search_int(ares_channel_t *channel, const char *name,
/* Try the name as-is first. */
squery - > next_domain = 0 ;
squery - > trying_as_is = ARES_TRUE ;
ares_query ( channel , name , dnsclass , type , search_callback , squery ) ;
ares_send ( channel , squery - > buf , ( int ) squery - > buflen , search_callback ,
squery ) ;
} else {
/* Try the name as-is last; start with the first search domain. */
squery - > next_domain = 1 ;
squery - > trying_as_is = ARES_FALSE ;
status = ares__cat_domain ( name , squery - > domains [ 0 ] , & s ) ;
if ( status = = ARES_SUCCESS ) {
ares_query ( channel , s , dnsclass , type , search_callback , squery ) ;
squery - > next_domain = 1 ;
squery - > trying_as_is = ARES_FALSE ;
status = ares__write_and_send_query ( channel , dnsrec , s , search_callback ,
squery ) ;
ares_free ( s ) ;
} else {
/* failed, free the malloc()ed memory */
ares_free ( squery - > name ) ;
ares_free ( squery ) ;
callback ( arg , ( int ) status , 0 , NULL , 0 ) ;
}
/* Handle any errors. */
if ( status ! = ARES_SUCCESS ) {
end_squery ( squery , status , NULL , 0 ) ;
}
}
}
/* Search for a DNS name with given class and type. Wrapper around
* ares_search_int ( ) where the DNS record to search is first constructed .
*/
void ares_search ( ares_channel_t * channel , const char * name , int dnsclass ,
int type , ares_callback callback , void * arg )
{
if ( channel = = NULL ) {
ares_status_t status ;
ares_dns_record_t * dnsrec = NULL ;
size_t max_udp_size ;
ares_dns_flags_t rd_flag ;
if ( ( channel = = NULL ) | | ( name = = NULL ) ) {
return ;
}
rd_flag = ! ( channel - > flags & ARES_FLAG_NORECURSE ) ? ARES_FLAG_RD : 0 ;
max_udp_size = ( channel - > flags & ARES_FLAG_EDNS ) ? channel - > ednspsz : 0 ;
status = ares_dns_record_create_query ( & dnsrec , name ,
( ares_dns_class_t ) dnsclass ,
( ares_dns_rec_type_t ) type ,
0 , rd_flag , max_udp_size ) ;
if ( status ! = ARES_SUCCESS ) {
callback ( arg , ( int ) status , 0 , NULL , 0 ) ;
return ;
}
ares__channel_lock ( channel ) ;
ares_search_int ( channel , name , dnsclass , type , callback , arg ) ;
ares_search_int ( channel , dnsrec , callback , arg ) ;
ares__channel_unlock ( channel ) ;
ares_dns_record_destroy ( dnsrec ) ;
}
/* Search for a DNS record. Wrapper around ares_search_int(). */
void ares_search_dnsrec ( ares_channel_t * channel , ares_dns_record_t * dnsrec ,
ares_callback_dnsrec callback , void * arg )
{
struct dnsrec_convert_arg * carg ;
if ( ( channel = = NULL ) | | ( dnsrec = = NULL ) ) {
return ;
}
/* For now, ares_search_int() uses the ares_callback prototype. We need to
* wrap the callback passed to this function in ares__dnsrec_convert_cb , to
* convert from ares_callback_dnsrec to ares_callback . Allocate the convert
* arg structure here .
*/
carg = ares_malloc_zero ( sizeof ( * carg ) ) ;
if ( carg = = NULL ) {
callback ( arg , ARES_ENOMEM , 0 , NULL ) ;
return ;
}
carg - > callback = callback ;
carg - > arg = arg ;
ares__channel_lock ( channel ) ;
ares_search_int ( channel , dnsrec , ares__dnsrec_convert_cb , carg ) ;
ares__channel_unlock ( channel ) ;
}
@ -174,51 +261,94 @@ static void search_callback(void *arg, int status, int timeouts,
{
struct search_query * squery = ( struct search_query * ) arg ;
ares_channel_t * channel = squery - > channel ;
char * s ;
ares_dns_record_t * dnsrep = NULL ;
ares_dns_rcode_t rcode ;
size_t ancount ;
ares_dns_record_t * dnsrec = NULL ;
const char * name ;
char * s = NULL ;
ares_status_t mystatus ;
squery - > timeouts + = ( size_t ) timeouts ;
/* Stop searching unless we got a non-fatal error. */
if ( status ! = ARES_ENODATA & & status ! = ARES_ESERVFAIL & &
status ! = ARES_ENOTFOUND ) {
if ( status ! = ARES_SUCCESS ) {
end_squery ( squery , ( ares_status_t ) status , abuf , ( size_t ) alen ) ;
return ;
}
/* Convert the rcode and ancount from the response into an ares_status_t
* value . Stop searching unless we got a non - fatal error .
*/
mystatus = ares_dns_parse ( abuf , ( size_t ) alen , 0 , & dnsrep ) ;
if ( mystatus ! = ARES_SUCCESS ) {
end_squery ( squery , mystatus , abuf , ( size_t ) alen ) ;
return ;
}
rcode = ares_dns_record_get_rcode ( dnsrep ) ;
ancount = ares_dns_record_rr_cnt ( dnsrep , ARES_SECTION_ANSWER ) ;
ares_dns_record_destroy ( dnsrep ) ;
mystatus = ares_dns_query_reply_tostatus ( rcode , ancount ) ;
if ( ( mystatus ! = ARES_ENODATA ) & & ( mystatus ! = ARES_ESERVFAIL ) & &
( mystatus ! = ARES_ENOTFOUND ) ) {
end_squery ( squery , mystatus , abuf , ( size_t ) alen ) ;
} else {
/* Save the status if we were trying as-is. */
if ( squery - > trying_as_is ) {
squery - > status_as_is = status ;
squery - > status_as_is = ( int ) my status;
}
/*
* If we ever get ARES_ENODATA along the way , record that ; if the search
/* If we ever get ARES_ENODATA along the way, record that; if the search
* should run to the very end and we got at least one ARES_ENODATA ,
* then callers like ares_gethostbyname ( ) may want to try a T_A search
* even if the last domain we queried for T_AAAA resource records
* returned ARES_ENOTFOUND .
*/
if ( status = = ARES_ENODATA ) {
if ( my status = = ARES_ENODATA ) {
squery - > ever_got_nodata = ARES_TRUE ;
}
if ( squery - > next_domain < squery - > ndomains ) {
ares_status_t mystatus ;
/* Try the next domain. */
mystatus = ares__cat_domain ( squery - > name ,
squery - > domains [ squery - > next_domain ] , & s ) ;
/* Try the next domain.
*
* First parse the encoded DNS record in the search_query structure , so
* that we can append the next domain to it .
*/
mystatus = ares_dns_parse ( squery - > buf , squery - > buflen , 0 , & dnsrec ) ;
if ( mystatus ! = ARES_SUCCESS ) {
end_squery ( squery , mystatus , NULL , 0 ) ;
} else {
/* Concatenate the name with the search domain and query using that. */
if ( ares_dns_record_query_cnt ( dnsrec ) ! = 1 ) {
mystatus = ARES_EBADQUERY ;
} else {
mystatus = ares_dns_record_query_get ( dnsrec , 0 , & name , NULL , NULL ) ;
if ( mystatus = = ARES_SUCCESS ) {
mystatus = ares__cat_domain ( name ,
squery - > domains [ squery - > next_domain ] ,
& s ) ;
if ( mystatus = = ARES_SUCCESS ) {
squery - > trying_as_is = ARES_FALSE ;
squery - > next_domain + + ;
ares_query ( channel , s , squery - > dnsclass , squery - > type , search_callback ,
squery ) ;
mystatus = ares__write_and_send_query ( channel , dnsrec , s ,
search_callback , arg ) ;
ares_free ( s ) ;
}
}
}
/* Clean up the DNS record object and handle any errors. */
ares_dns_record_destroy ( dnsrec ) ;
if ( mystatus ! = ARES_SUCCESS ) {
end_squery ( squery , mystatus , NULL , 0 ) ;
}
}
} else if ( squery - > status_as_is = = - 1 ) {
/* Try the name as-is at the end. */
squery - > trying_as_is = ARES_TRUE ;
ares_query ( channel , squery - > name , squery - > dnsclass , squery - > type ,
search_callback , squery ) ;
ares_send ( channel , squery - > buf , ( int ) squery - > buflen , search_callback ,
squery ) ;
} else {
/* We have no more domains to search, return an appropriate response. */
if ( squery - > status_as_is = = ARES_ENOTFOUND & & squery - > ever_got_nodata ) {
end_squery ( squery , ARES_ENODATA , NULL , 0 ) ;
} else {
@ -228,13 +358,43 @@ static void search_callback(void *arg, int status, int timeouts,
}
}
/* Write and send a DNS record on a channel. The DNS record must represent a
* query for a single name . An alternative name can be specified to temporarily
* overwrite the name on the DNS record before doing so . Note that this only
* affects the name in the question section ; RRs are not affected .
* This is used as a helper function in ares_search ( ) .
*/
static ares_status_t ares__write_and_send_query ( ares_channel_t * channel ,
ares_dns_record_t * dnsrec ,
char * altname ,
ares_callback callback ,
void * arg )
{
ares_status_t status ;
unsigned char * buf ;
size_t buflen ;
status = ares_dns_write_query_altname ( dnsrec , altname , & buf , & buflen ) ;
if ( status ! = ARES_SUCCESS ) {
return status ;
}
ares_send ( channel , buf , ( int ) buflen , callback , arg ) ;
ares_free ( buf ) ;
return ARES_SUCCESS ;
}
/* End a search query by invoking the user callback and freeing the
* search_query structure .
*/
static void end_squery ( struct search_query * squery , ares_status_t status ,
unsigned char * abuf , size_t alen )
{
squery - > callback ( squery - > arg , ( int ) status , ( int ) squery - > timeouts , abuf ,
( int ) alen ) ;
ares__strsplit_free ( squery - > domains , squery - > ndomains ) ;
ares_free ( squery - > name ) ;
ares_free ( squery - > buf ) ;
ares_free ( squery ) ;
}
@ -391,3 +551,29 @@ ares_status_t ares__single_domain(const ares_channel_t *channel,
* s = NULL ;
return ARES_SUCCESS ;
}
/* Callback function used to convert from the ares_callback prototype to the
* ares_callback_dnsrec prototype , by parsing the result and passing that to
* the inner callback .
*/
static void ares__dnsrec_convert_cb ( void * arg , int status , int timeouts ,
unsigned char * abuf , int alen )
{
struct dnsrec_convert_arg * carg = ( struct dnsrec_convert_arg * ) arg ;
ares_dns_record_t * dnsrec = NULL ;
ares_status_t mystatus ;
if ( status ! = ARES_SUCCESS ) {
carg - > callback ( carg - > arg , ( ares_status_t ) status , ( size_t ) timeouts , NULL ) ;
} else {
/* Parse the result. */
mystatus = ares_dns_parse ( abuf , ( size_t ) alen , 0 , & dnsrec ) ;
if ( mystatus ! = ARES_SUCCESS ) {
carg - > callback ( carg - > arg , mystatus , ( size_t ) timeouts , NULL ) ;
} else {
carg - > callback ( carg - > arg , ARES_SUCCESS , ( size_t ) timeouts , dnsrec ) ;
ares_dns_record_destroy ( dnsrec ) ;
}
}
ares_free ( carg ) ;
}