@ -56,6 +56,78 @@
# include <fcntl.h>
# include <limits.h>
# if defined(__linux__) && defined(MSG_FASTOPEN)
# define TFO_SUPPORTED 1
# define TFO_SKIP_CONNECT 0
# define TFO_USE_SENDTO 0
# define TFO_USE_CONNECTX 0
# define TFO_CLIENT_SOCKOPT TCP_FASTOPEN_CONNECT
# elif defined(__FreeBSD__) && defined(TCP_FASTOPEN)
# define TFO_SUPPORTED 1
# define TFO_SKIP_CONNECT 1
# define TFO_USE_SENDTO 1
# define TFO_USE_CONNECTX 0
# define TFO_CLIENT_SOCKOPT TCP_FASTOPEN
# elif defined(__APPLE__) && defined(HAVE_CONNECTX)
# define TFO_SUPPORTED 1
# define TFO_SKIP_CONNECT 0
# define TFO_USE_SENDTO 0
# define TFO_USE_CONNECTX 1
# undef TFO_CLIENT_SOCKOPT
# else
# define TFO_SUPPORTED 0
# endif
# ifndef HAVE_WRITEV
/* Structure for scatter/gather I/O. */
struct iovec {
void * iov_base ; /* Pointer to data. */
size_t iov_len ; /* Length of data. */
} ;
# endif
/* Return 1 if the specified error number describes a readiness error, or 0
* otherwise . This is mostly for HP - UX , which could return EAGAIN or
* EWOULDBLOCK . See this man page
*
* http : //devrsrc1.external.hp.com/STKS/cgi-bin/man2html?
* manpage = / usr / share / man / man2 . Z / send .2
*/
ares_bool_t ares__socket_try_again ( int errnum )
{
# if !defined EWOULDBLOCK && !defined EAGAIN
# error "Neither EWOULDBLOCK nor EAGAIN defined"
# endif
# ifdef EWOULDBLOCK
if ( errnum = = EWOULDBLOCK ) {
return ARES_TRUE ;
}
# endif
# if defined EAGAIN && EAGAIN != EWOULDBLOCK
if ( errnum = = EAGAIN ) {
return ARES_TRUE ;
}
# endif
return ARES_FALSE ;
}
ares_ssize_t ares__socket_recv ( ares_channel_t * channel , ares_socket_t s ,
void * data , size_t data_len )
{
if ( channel - > sock_funcs & & channel - > sock_funcs - > arecvfrom ) {
return channel - > sock_funcs - > arecvfrom ( s , data , data_len , 0 , 0 , 0 ,
channel - > sock_func_cb_data ) ;
}
return ( ares_ssize_t ) recv ( ( RECV_TYPE_ARG1 ) s , ( RECV_TYPE_ARG2 ) data ,
( RECV_TYPE_ARG3 ) data_len , ( RECV_TYPE_ARG4 ) ( 0 ) ) ;
}
ares_ssize_t ares__socket_recvfrom ( ares_channel_t * channel , ares_socket_t s ,
void * data , size_t data_len , int flags ,
struct sockaddr * from ,
@ -70,20 +142,144 @@ ares_ssize_t ares__socket_recvfrom(ares_channel_t *channel, ares_socket_t s,
return ( ares_ssize_t ) recvfrom ( s , data , ( RECVFROM_TYPE_ARG3 ) data_len , flags ,
from , from_len ) ;
# else
return sread ( s , data , data_len ) ;
return ares__socket_recv ( channel , s , data , data_len ) ;
# endif
}
ares_ssize_t ares__socket_recv ( ares_channel_t * channel , ares_socket_t s ,
void * data , size_t data_len )
/* Use like:
* struct sockaddr_storage sa_storage ;
* ares_socklen_t salen = sizeof ( sa_storage ) ;
* struct sockaddr * sa = ( struct sockaddr * ) & sa_storage ;
* ares__conn_set_sockaddr ( conn , sa , & salen ) ;
*/
static ares_status_t ares__conn_set_sockaddr ( const ares_conn_t * conn ,
struct sockaddr * sa ,
ares_socklen_t * salen )
{
if ( channel - > sock_funcs & & channel - > sock_funcs - > arecvfrom ) {
return channel - > sock_funcs - > arecvfrom ( s , data , data_len , 0 , 0 , 0 ,
channel - > sock_func_cb_data ) ;
ares_server_t * server = conn - > server ;
unsigned short port =
conn - > flags & ARES_CONN_FLAG_TCP ? server - > tcp_port : server - > udp_port ;
switch ( server - > addr . family ) {
case AF_INET :
{
struct sockaddr_in * sin = ( struct sockaddr_in * ) ( void * ) sa ;
if ( * salen < ( ares_socklen_t ) sizeof ( * sin ) ) {
return ARES_EFORMERR ;
}
* salen = sizeof ( * sin ) ;
memset ( sin , 0 , sizeof ( * sin ) ) ;
sin - > sin_family = AF_INET ;
sin - > sin_port = htons ( port ) ;
memcpy ( & sin - > sin_addr , & server - > addr . addr . addr4 , sizeof ( sin - > sin_addr ) ) ;
}
return ARES_SUCCESS ;
case AF_INET6 :
{
struct sockaddr_in6 * sin6 = ( struct sockaddr_in6 * ) ( void * ) sa ;
if ( * salen < ( ares_socklen_t ) sizeof ( * sin6 ) ) {
return ARES_EFORMERR ;
}
* salen = sizeof ( * sin6 ) ;
memset ( sin6 , 0 , sizeof ( * sin6 ) ) ;
sin6 - > sin6_family = AF_INET6 ;
sin6 - > sin6_port = htons ( port ) ;
memcpy ( & sin6 - > sin6_addr , & server - > addr . addr . addr6 ,
sizeof ( sin6 - > sin6_addr ) ) ;
# ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID
sin6 - > sin6_scope_id = server - > ll_scope ;
# endif
}
return ARES_SUCCESS ;
default :
break ;
}
/* sread() is a wrapper for read() or recv() depending on the system */
return sread ( s , data , data_len ) ;
return ARES_EBADFAMILY ;
}
static ares_status_t ares_conn_set_self_ip ( ares_conn_t * conn , ares_bool_t early )
{
struct sockaddr_storage sa_storage ;
int rv ;
ares_socklen_t len = sizeof ( sa_storage ) ;
/* We call this twice on TFO, if we already have the IP we can go ahead and
* skip processing */
if ( ! early & & conn - > self_ip . family ! = AF_UNSPEC ) {
return ARES_SUCCESS ;
}
memset ( & sa_storage , 0 , sizeof ( sa_storage ) ) ;
rv = getsockname ( conn - > fd , ( struct sockaddr * ) ( void * ) & sa_storage , & len ) ;
if ( rv ! = 0 ) {
/* During TCP FastOpen, we can't get the IP this early since connect()
* may not be called . That ' s ok , we ' ll try again later */
if ( early & & conn - > flags & ARES_CONN_FLAG_TCP & &
conn - > flags & ARES_CONN_FLAG_TFO ) {
memset ( & conn - > self_ip , 0 , sizeof ( conn - > self_ip ) ) ;
return ARES_SUCCESS ;
}
return ARES_ECONNREFUSED ;
}
if ( ! ares_sockaddr_to_ares_addr ( & conn - > self_ip , NULL ,
( struct sockaddr * ) ( void * ) & sa_storage ) ) {
return ARES_ECONNREFUSED ;
}
return ARES_SUCCESS ;
}
ares_ssize_t ares__conn_write ( ares_conn_t * conn , const void * data , size_t len )
{
ares_channel_t * channel = conn - > server - > channel ;
int flags = 0 ;
# ifdef HAVE_MSG_NOSIGNAL
flags | = MSG_NOSIGNAL ;
# endif
if ( channel - > sock_funcs & & channel - > sock_funcs - > asendv ) {
struct iovec vec ;
vec . iov_base = ( void * ) ( ( size_t ) data ) ; /* Cast off const */
vec . iov_len = len ;
return channel - > sock_funcs - > asendv ( conn - > fd , & vec , 1 ,
channel - > sock_func_cb_data ) ;
}
if ( conn - > flags & ARES_CONN_FLAG_TFO_INITIAL ) {
conn - > flags & = ~ ( ( unsigned int ) ARES_CONN_FLAG_TFO_INITIAL ) ;
# if defined(TFO_USE_SENDTO) && TFO_USE_SENDTO
{
struct sockaddr_storage sa_storage ;
ares_socklen_t salen = sizeof ( sa_storage ) ;
struct sockaddr * sa = ( struct sockaddr * ) & sa_storage ;
ares_status_t status ;
ares_ssize_t rv ;
status = ares__conn_set_sockaddr ( conn , sa , & salen ) ;
if ( status ! = ARES_SUCCESS ) {
return status ;
}
rv = ( ares_ssize_t ) sendto ( ( SEND_TYPE_ARG1 ) conn - > fd , ( SEND_TYPE_ARG2 ) data ,
( SEND_TYPE_ARG3 ) len , ( SEND_TYPE_ARG4 ) flags , sa ,
salen ) ;
/* If using TFO, we might not have been able to get an IP earlier, since
* we hadn ' t informed the OS of the destination . When using sendto ( )
* now we have so we should be able to fetch it */
ares_conn_set_self_ip ( conn , ARES_TRUE ) ;
return rv ;
}
# endif
}
return ( ares_ssize_t ) send ( ( SEND_TYPE_ARG1 ) conn - > fd , ( SEND_TYPE_ARG2 ) data ,
( SEND_TYPE_ARG3 ) len , ( SEND_TYPE_ARG4 ) flags ) ;
}
/*
@ -192,22 +388,6 @@ static ares_status_t configure_socket(ares_conn_t *conn)
}
# endif
# ifdef TCP_NODELAY
if ( conn - > flags & ARES_CONN_FLAG_TCP ) {
/*
* Disable the Nagle algorithm ( only relevant for TCP sockets , and thus not
* in configure_socket ) . In general , in DNS lookups we ' re pretty much
* interested in firing off a single request and then waiting for a reply ,
* so batching isn ' t very interesting .
*/
int opt = 1 ;
if ( setsockopt ( conn - > fd , IPPROTO_TCP , TCP_NODELAY , ( void * ) & opt ,
sizeof ( opt ) ) ! = 0 ) {
return ARES_ECONNREFUSED ;
}
}
# endif
/* Set the socket's send and receive buffer sizes. */
if ( channel - > socket_send_buffer_size > 0 & &
setsockopt ( conn - > fd , SOL_SOCKET , SO_SNDBUF ,
@ -225,10 +405,10 @@ static ares_status_t configure_socket(ares_conn_t *conn)
# ifdef SO_BINDTODEVICE
if ( ares_strlen ( channel - > local_dev_name ) ) {
/* Only root can do this, and usually not fatal if it doesn't work, so
* just continue on . */
setsockopt ( conn - > fd , SOL_SOCKET , SO_BINDTODEVICE , channel - > local_dev_name ,
sizeof ( channel - > local_dev_name ) ) ;
/* Only root can do this, and usually not fatal if it doesn't work, so
* just continue on . */
setsockopt ( conn - > fd , SOL_SOCKET , SO_BINDTODEVICE , channel - > local_dev_name ,
sizeof ( channel - > local_dev_name ) ) ;
}
# endif
@ -256,6 +436,33 @@ static ares_status_t configure_socket(ares_conn_t *conn)
set_ipv6_v6only ( conn - > fd , 0 ) ;
}
if ( conn - > flags & ARES_CONN_FLAG_TCP ) {
int opt = 1 ;
# ifdef TCP_NODELAY
/*
* Disable the Nagle algorithm ( only relevant for TCP sockets , and thus not
* in configure_socket ) . In general , in DNS lookups we ' re pretty much
* interested in firing off a single request and then waiting for a reply ,
* so batching isn ' t very interesting .
*/
if ( setsockopt ( conn - > fd , IPPROTO_TCP , TCP_NODELAY , ( void * ) & opt ,
sizeof ( opt ) ) ! = 0 ) {
return ARES_ECONNREFUSED ;
}
# endif
if ( conn - > flags & ARES_CONN_FLAG_TFO ) {
# if defined(TFO_CLIENT_SOCKOPT)
if ( setsockopt ( conn - > fd , IPPROTO_TCP , TFO_CLIENT_SOCKOPT , ( void * ) & opt ,
sizeof ( opt ) ) ! = 0 ) {
/* Disable TFO if flag can't be set. */
conn - > flags & = ~ ( ( unsigned int ) ARES_CONN_FLAG_TFO ) ;
}
# endif
}
}
return ARES_SUCCESS ;
}
@ -297,61 +504,81 @@ ares_bool_t ares_sockaddr_to_ares_addr(struct ares_addr *ares_addr,
return ARES_FALSE ;
}
static ares_status_t ares_conn_set_self_ip ( ares_conn_t * conn )
static ares_status_t ares__conn_connect ( ares_conn_t * conn , struct sockaddr * sa ,
ares_socklen_t salen )
{
/* Some old systems might not have sockaddr_storage, so we make a union
* that ' s guaranteed to be large enough */
union {
struct sockaddr sa ;
struct sockaddr_in sa4 ;
struct sockaddr_in6 sa6 ;
} from ;
/* Normal non TCPFastOpen style connect */
if ( ! ( conn - > flags & ARES_CONN_FLAG_TFO ) ) {
return ares__connect_socket ( conn - > server - > channel , conn - > fd , sa , salen ) ;
}
int rv ;
ares_socklen_t len = sizeof ( from ) ;
/* FreeBSD don't want any sort of connect() so skip */
# if defined(TFO_SKIP_CONNECT) && TFO_SKIP_CONNECT
return ARES_SUCCESS ;
# elif defined(TFO_USE_CONNECTX) && TFO_USE_CONNECTX
{
int rv ;
int err ;
memset ( & from , 0 , sizeof ( from ) ) ;
do {
sa_endpoints_t endpoints ;
memset ( & endpoints , 0 , sizeof ( endpoints ) ) ;
endpoints . sae_dstaddr = sa ;
endpoints . sae_dstaddrlen = salen ;
rv = getsockname ( conn - > fd , & from . sa , & len ) ;
if ( rv ! = 0 ) {
return ARES_ECONNREFUSED ;
}
rv = connectx ( conn - > fd , & endpoints , SAE_ASSOCID_ANY ,
CONNECT_DATA_IDEMPOTENT | CONNECT_RESUME_ON_READ_WRITE ,
NULL , 0 , NULL , NULL ) ;
if ( ! ares_sockaddr_to_ares_addr ( & conn - > self_ip , NULL , & from . sa ) ) {
return ARES_ECONNREFUSED ;
}
err = SOCKERRNO ;
if ( rv = = - 1 & & err ! = EINPROGRESS & & err ! = EWOULDBLOCK ) {
return ARES_ECONNREFUSED ;
}
} while ( rv = = - 1 & & err = = EINTR ) ;
}
return ARES_SUCCESS ;
# elif defined(TFO_SUPPORTED) && TFO_SUPPORTED
return ares__connect_socket ( conn - > server - > channel , conn - > fd , sa , salen ) ;
# else
/* Shouldn't be possible */
return ARES_ECONNREFUSED ;
# endif
}
ares_status_t ares__open_connection ( ares_conn_t * * conn_out ,
ares_channel_t * channel ,
ares_server_t * server , ares_query_t * query )
ares_server_t * server , ares_bool_t is_tcp )
{
ares_socklen_t salen ;
ares_status_t status ;
ares_bool_t is_tcp = query - > using_tcp ;
union {
struct sockaddr_in sa4 ;
struct sockaddr_in6 sa6 ;
} saddr ;
struct sockaddr * sa ;
ares_conn_t * conn ;
ares__llist_node_t * node = NULL ;
int sock_type = is_tcp ? SOCK_STREAM : SOCK_DGRAM ;
ares_status_t status ;
struct sockaddr_storage sa_storage ;
ares_socklen_t salen = sizeof ( sa_storage ) ;
struct sockaddr * sa = ( struct sockaddr * ) & sa_storage ;
ares_conn_t * conn ;
ares__llist_node_t * node = NULL ;
int stype = is_tcp ? SOCK_STREAM : SOCK_DGRAM ;
* conn_out = NULL ;
conn = ares_malloc ( sizeof ( * conn ) ) ;
if ( conn = = NULL ) {
return ARES_ENOMEM ; /* LCOV_EXCL_LINE: OutOfMemory */
return ARES_ENOMEM ; /* LCOV_EXCL_LINE: OutOfMemory */
}
memset ( conn , 0 , sizeof ( * conn ) ) ;
conn - > fd = ARES_SOCKET_BAD ;
conn - > server = server ;
conn - > queries_to_conn = ares__llist_create ( NULL ) ;
conn - > flags = is_tcp ? ARES_CONN_FLAG_TCP : ARES_CONN_FLAG_NONE ;
conn - > flags = is_tcp ? ARES_CONN_FLAG_TCP : ARES_CONN_FLAG_NONE ;
/* Enable TFO if the OS supports it and we were passed in data to send during
* the connect . It might be disabled later if an error is encountered . Make
* sure a user isn ' t overriding anything . */
if ( conn - > flags & ARES_CONN_FLAG_TCP & & channel - > sock_funcs = = NULL & &
TFO_SUPPORTED ) {
conn - > flags | = ARES_CONN_FLAG_TFO ;
}
if ( conn - > queries_to_conn = = NULL ) {
/* LCOV_EXCL_START: OutOfMemory */
status = ARES_ENOMEM ;
@ -359,36 +586,14 @@ ares_status_t ares__open_connection(ares_conn_t **conn_out,
/* LCOV_EXCL_STOP */
}
switch ( server - > addr . family ) {
case AF_INET :
sa = ( void * ) & saddr . sa4 ;
salen = sizeof ( saddr . sa4 ) ;
memset ( sa , 0 , ( size_t ) salen ) ;
saddr . sa4 . sin_family = AF_INET ;
saddr . sa4 . sin_port = htons ( is_tcp ? server - > tcp_port : server - > udp_port ) ;
memcpy ( & saddr . sa4 . sin_addr , & server - > addr . addr . addr4 ,
sizeof ( saddr . sa4 . sin_addr ) ) ;
break ;
case AF_INET6 :
sa = ( void * ) & saddr . sa6 ;
salen = sizeof ( saddr . sa6 ) ;
memset ( sa , 0 , ( size_t ) salen ) ;
saddr . sa6 . sin6_family = AF_INET6 ;
saddr . sa6 . sin6_port = htons ( is_tcp ? server - > tcp_port : server - > udp_port ) ;
memcpy ( & saddr . sa6 . sin6_addr , & server - > addr . addr . addr6 ,
sizeof ( saddr . sa6 . sin6_addr ) ) ;
# ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID
saddr . sa6 . sin6_scope_id = server - > ll_scope ;
# endif
break ;
default :
status = ARES_EBADFAMILY ; /* LCOV_EXCL_LINE */
goto done ;
/* Convert into the struct sockaddr structure needed by the OS */
status = ares__conn_set_sockaddr ( conn , sa , & salen ) ;
if ( status ! = ARES_SUCCESS ) {
goto done ;
}
/* Acquire a socket. */
conn - > fd = ares__open_socket ( channel , server - > addr . family , sock_ type , 0 ) ;
conn - > fd = ares__open_socket ( channel , server - > addr . family , stype , 0 ) ;
if ( conn - > fd = = ARES_SOCKET_BAD ) {
status = ARES_ECONNREFUSED ;
goto done ;
@ -401,29 +606,36 @@ ares_status_t ares__open_connection(ares_conn_t **conn_out,
}
if ( channel - > sock_config_cb ) {
int err = channel - > sock_config_cb ( conn - > fd , sock_type , channel - > sock_config_cb_data ) ;
int err =
channel - > sock_config_cb ( conn - > fd , stype , channel - > sock_config_cb_data ) ;
if ( err < 0 ) {
status = ARES_ECONNREFUSED ;
goto done ;
}
}
/* Connect to the server. */
status = ares__connect_socket ( channel , conn - > fd , sa , salen ) ;
/* Connect */
status = ares__conn_connect ( conn , sa , salen ) ;
if ( status ! = ARES_SUCCESS ) {
goto done ;
}
if ( channel - > sock_create_cb ) {
int err = channel - > sock_create_cb ( conn - > fd , sock_type , channel - > sock_create_cb_data ) ;
int err =
channel - > sock_create_cb ( conn - > fd , stype , channel - > sock_create_cb_data ) ;
if ( err < 0 ) {
status = ARES_ECONNREFUSED ;
goto done ;
}
}
/* Let the connection know we haven't written our first packet yet for TFO */
if ( conn - > flags & ARES_CONN_FLAG_TFO ) {
conn - > flags | = ARES_CONN_FLAG_TFO_INITIAL ;
}
/* Need to store our own ip for DNS cookie support */
status = ares_conn_set_self_ip ( conn ) ;
status = ares_conn_set_self_ip ( conn , ARES_FALSE ) ;
if ( status ! = ARES_SUCCESS ) {
goto done ; /* LCOV_EXCL_LINE: UntestablePath */
}
@ -452,7 +664,7 @@ ares_status_t ares__open_connection(ares_conn_t **conn_out,
/* LCOV_EXCL_STOP */
}
SOCK_STATE_CALLBACK ( channel , conn - > fd , 1 , 0 ) ;
SOCK_STATE_CALLBACK ( channel , conn - > fd , 1 , is_tcp ? 1 : 0 ) ;
if ( is_tcp ) {
server - > tcp_conn = conn ;
@ -481,10 +693,10 @@ ares_socket_t ares__open_socket(ares_channel_t *channel, int af, int type,
return socket ( af , type , protocol ) ;
}
ares_status_t ares__connect_socket ( ares_channel_t * channel ,
ares_socket_t sockfd ,
ares_status_t ares__connect_socket ( ares_channel_t * channel ,
ares_socket_t sockfd ,
const struct sockaddr * addr ,
ares_socklen_t addrlen )
ares_socklen_t addrlen )
{
int rv ;
int err ;
@ -521,26 +733,6 @@ void ares__close_socket(ares_channel_t *channel, ares_socket_t s)
}
}
# ifndef HAVE_WRITEV
/* Structure for scatter/gather I/O. */
struct iovec {
void * iov_base ; /* Pointer to data. */
size_t iov_len ; /* Length of data. */
} ;
# endif
ares_ssize_t ares__socket_write ( ares_channel_t * channel , ares_socket_t s ,
const void * data , size_t len )
{
if ( channel - > sock_funcs & & channel - > sock_funcs - > asendv ) {
struct iovec vec ;
vec . iov_base = ( void * ) ( ( size_t ) data ) ; /* Cast off const */
vec . iov_len = len ;
return channel - > sock_funcs - > asendv ( s , & vec , 1 , channel - > sock_func_cb_data ) ;
}
return swrite ( s , data , len ) ;
}
void ares_set_socket_callback ( ares_channel_t * channel ,
ares_sock_create_callback cb , void * data )
{