More modularization for sockets/connections (#876)

Split socket from connection functions for a more clear delineation in
functionality. This is needed to support future DNS over TLS.

Also makes the new ares_process_fds() return a status code so we can
detect out of memory conditions, this too is going to be needed for
handling TLS which is more likely to run into such a situation.

Finally, add in a new ares_htable_vpstr_t type and functions which will
be needed as part of TLS session lookups.

Authored-By: Brad House (@bradh352)
coverity_scan
Brad House 7 months ago committed by GitHub
parent 3b80588384
commit e0ef6a8a7a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 11
      docs/ares_process.3
  2. 14
      include/ares.h
  3. 16
      src/lib/Makefile.inc
  4. 2
      src/lib/ares_close_sockets.c
  5. 506
      src/lib/ares_conn.c
  6. 49
      src/lib/ares_conn.h
  7. 2
      src/lib/ares_private.h
  8. 138
      src/lib/ares_process.c
  9. 718
      src/lib/ares_socket.c
  10. 79
      src/lib/ares_socket.h
  11. 11
      src/lib/ares_sortaddrinfo.c
  12. 22
      src/lib/dsa/ares_htable_strvp.c
  13. 186
      src/lib/dsa/ares_htable_vpstr.c
  14. 10
      src/lib/include/ares_htable_strvp.h
  15. 111
      src/lib/include/ares_htable_vpstr.h

@ -29,10 +29,10 @@ typedef enum {
} ares_process_flag_t; } ares_process_flag_t;
void ares_process_fds(ares_channel_t *\fIchannel\fP, ares_status_t ares_process_fds(ares_channel_t *\fIchannel\fP,
const ares_fd_events_t *\fIevents\fP, const ares_fd_events_t *\fIevents\fP,
size_t \fInevents\fP, size_t \fInevents\fP,
unsigned int \fIflags\fP) unsigned int \fIflags\fP)
void ares_process_fd(ares_channel_t *\fIchannel\fP, void ares_process_fd(ares_channel_t *\fIchannel\fP,
ares_socket_t \fIread_fd\fP, ares_socket_t \fIread_fd\fP,
@ -72,6 +72,9 @@ value \fIARES_FD_EVENT_NONE\fP (0) if there are no events for a given file
descriptor if an integrator wishes to simply maintain an array with all descriptor if an integrator wishes to simply maintain an array with all
possible file descriptors and update readiness via the \fIevent\fP member. possible file descriptors and update readiness via the \fIevent\fP member.
This function will return \fIARES_ENOMEM\fP in out of memory conditions,
otherwise will return \fIARES_SUCCESS\fP.
This function is recommended over \fBares_process_fd(3)\fP since it can This function is recommended over \fBares_process_fd(3)\fP since it can
handle processing of multiple file descriptors at once, thus skipping repeating handle processing of multiple file descriptors at once, thus skipping repeating
additional logic such as timeout processing which would be required if calling additional logic such as timeout processing which would be required if calling

@ -699,14 +699,16 @@ typedef enum {
* \param[in] nevents Number of elements in the events array. May be 0 if * \param[in] nevents Number of elements in the events array. May be 0 if
* no events, but may have timeouts to process. * no events, but may have timeouts to process.
* \param[in] flags Flags to alter behavior of the process command. * \param[in] flags Flags to alter behavior of the process command.
* \return ARES_ENOMEM on out of memory, ARES_EFORMERR on misuse,
* otherwise ARES_SUCCESS
*/ */
CARES_EXTERN void ares_process_fds(ares_channel_t *channel, CARES_EXTERN ares_status_t ares_process_fds(ares_channel_t *channel,
const ares_fd_events_t *events, const ares_fd_events_t *events,
size_t nevents, unsigned int flags); size_t nevents, unsigned int flags);
CARES_EXTERN void ares_process_fd(ares_channel_t *channel, CARES_EXTERN void ares_process_fd(ares_channel_t *channel,
ares_socket_t read_fd, ares_socket_t read_fd,
ares_socket_t write_fd); ares_socket_t write_fd);
CARES_EXTERN CARES_DEPRECATED_FOR(ares_dns_record_create) int ares_create_query( CARES_EXTERN CARES_DEPRECATED_FOR(ares_dns_record_create) int ares_create_query(
const char *name, int dnsclass, int type, unsigned short id, int rd, const char *name, int dnsclass, int type, unsigned short id, int rd,

@ -3,13 +3,10 @@
CSOURCES = ares_addrinfo2hostent.c \ CSOURCES = ares_addrinfo2hostent.c \
ares_addrinfo_localhost.c \ ares_addrinfo_localhost.c \
ares_close_sockets.c \
ares_hosts_file.c \
ares_parse_into_addrinfo.c \
ares_socket.c \
ares_sortaddrinfo.c \
ares_android.c \ ares_android.c \
ares_cancel.c \ ares_cancel.c \
ares_close_sockets.c \
ares_conn.c \
ares_cookie.c \ ares_cookie.c \
ares_data.c \ ares_data.c \
ares_destroy.c \ ares_destroy.c \
@ -21,16 +18,20 @@ CSOURCES = ares_addrinfo2hostent.c \
ares_gethostbyaddr.c \ ares_gethostbyaddr.c \
ares_gethostbyname.c \ ares_gethostbyname.c \
ares_getnameinfo.c \ ares_getnameinfo.c \
ares_hosts_file.c \
ares_init.c \ ares_init.c \
ares_library_init.c \ ares_library_init.c \
ares_metrics.c \ ares_metrics.c \
ares_options.c \ ares_options.c \
ares_platform.c \ ares_platform.c \
ares_parse_into_addrinfo.c \
ares_process.c \ ares_process.c \
ares_qcache.c \ ares_qcache.c \
ares_query.c \ ares_query.c \
ares_search.c \ ares_search.c \
ares_send.c \ ares_send.c \
ares_socket.c \
ares_sortaddrinfo.c \
ares_strerror.c \ ares_strerror.c \
ares_sysconfig.c \ ares_sysconfig.c \
ares_sysconfig_files.c \ ares_sysconfig_files.c \
@ -47,6 +48,7 @@ CSOURCES = ares_addrinfo2hostent.c \
dsa/ares_htable_asvp.c \ dsa/ares_htable_asvp.c \
dsa/ares_htable_strvp.c \ dsa/ares_htable_strvp.c \
dsa/ares_htable_szvp.c \ dsa/ares_htable_szvp.c \
dsa/ares_htable_vpstr.c \
dsa/ares_htable_vpvp.c \ dsa/ares_htable_vpvp.c \
dsa/ares_llist.c \ dsa/ares_llist.c \
dsa/ares_slist.c \ dsa/ares_slist.c \
@ -83,7 +85,7 @@ CSOURCES = ares_addrinfo2hostent.c \
str/ares_buf.c \ str/ares_buf.c \
str/ares_str.c \ str/ares_str.c \
str/ares_strsplit.c \ str/ares_strsplit.c \
util/ares_iface_ips.c \ util/ares_iface_ips.c \
util/ares_threads.c \ util/ares_threads.c \
util/ares_timeval.c \ util/ares_timeval.c \
util/ares_math.c \ util/ares_math.c \
@ -98,6 +100,7 @@ HHEADERS = ares_android.h \
ares_platform.h \ ares_platform.h \
ares_private.h \ ares_private.h \
ares_setup.h \ ares_setup.h \
ares_socket.h \
dsa/ares_htable.h \ dsa/ares_htable.h \
dsa/ares_slist.h \ dsa/ares_slist.h \
event/ares_event.h \ event/ares_event.h \
@ -107,6 +110,7 @@ HHEADERS = ares_android.h \
include/ares_htable_asvp.h \ include/ares_htable_asvp.h \
include/ares_htable_strvp.h \ include/ares_htable_strvp.h \
include/ares_htable_szvp.h \ include/ares_htable_szvp.h \
include/ares_htable_vpstr.h \
include/ares_htable_vpvp.h \ include/ares_htable_vpvp.h \
include/ares_llist.h \ include/ares_llist.h \
include/ares_str.h \ include/ares_str.h \

@ -65,7 +65,7 @@ void ares_close_connection(ares_conn_t *conn, ares_status_t requeue_status)
ares_conn_sock_state_cb_update(conn, ARES_CONN_STATE_NONE); ares_conn_sock_state_cb_update(conn, ARES_CONN_STATE_NONE);
ares_close_socket(channel, conn->fd); ares_socket_close(channel, conn->fd);
ares_free(conn); ares_free(conn);
} }

@ -0,0 +1,506 @@
/* MIT License
*
* Copyright (c) Massachusetts Institute of Technology
* Copyright (c) The c-ares project and its contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* SPDX-License-Identifier: MIT
*/
#include "ares_private.h"
void ares_conn_sock_state_cb_update(ares_conn_t *conn,
ares_conn_state_flags_t flags)
{
ares_channel_t *channel = conn->server->channel;
if ((conn->state_flags & ARES_CONN_STATE_CBFLAGS) != flags &&
channel->sock_state_cb) {
channel->sock_state_cb(channel->sock_state_cb_data, conn->fd,
flags & ARES_CONN_STATE_READ ? 1 : 0,
flags & ARES_CONN_STATE_WRITE ? 1 : 0);
}
conn->state_flags &= ~((unsigned int)ARES_CONN_STATE_CBFLAGS);
conn->state_flags |= flags;
}
ares_conn_err_t ares_conn_read(ares_conn_t *conn, void *data, size_t len,
size_t *read_bytes)
{
ares_channel_t *channel = conn->server->channel;
ares_conn_err_t err;
if (!(conn->flags & ARES_CONN_FLAG_TCP)) {
struct sockaddr_storage sa_storage;
ares_socklen_t salen = sizeof(sa_storage);
memset(&sa_storage, 0, sizeof(sa_storage));
err =
ares_socket_recvfrom(channel, conn->fd, ARES_FALSE, data, len, 0,
(struct sockaddr *)&sa_storage, &salen, read_bytes);
#ifdef HAVE_RECVFROM
if (err == ARES_CONN_ERR_SUCCESS &&
!ares_sockaddr_addr_eq((struct sockaddr *)&sa_storage,
&conn->server->addr)) {
err = ARES_CONN_ERR_WOULDBLOCK;
}
#endif
} else {
err = ares_socket_recv(channel, conn->fd, ARES_TRUE, data, len, read_bytes);
}
/* Toggle connected state if needed */
if (err == ARES_CONN_ERR_SUCCESS) {
conn->state_flags |= ARES_CONN_STATE_CONNECTED;
}
return err;
}
/* 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)
{
const ares_server_t *server = conn->server;
unsigned short port =
conn->flags & ARES_CONN_FLAG_TCP ? server->tcp_port : server->udp_port;
struct sockaddr_in *sin;
struct sockaddr_in6 *sin6;
switch (server->addr.family) {
case AF_INET:
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:
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;
}
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_conn_err_t ares_conn_write(ares_conn_t *conn, const void *data, size_t len,
size_t *written)
{
ares_channel_t *channel = conn->server->channel;
ares_bool_t is_tfo = ARES_FALSE;
ares_conn_err_t err = ARES_CONN_ERR_SUCCESS;
*written = 0;
/* Don't try to write if not doing initial TFO and not connected */
if (conn->flags & ARES_CONN_FLAG_TCP &&
!(conn->state_flags & ARES_CONN_STATE_CONNECTED) &&
!(conn->flags & ARES_CONN_FLAG_TFO_INITIAL)) {
return ARES_CONN_ERR_WOULDBLOCK;
}
if (conn->flags & ARES_CONN_FLAG_TFO_INITIAL) {
struct sockaddr_storage sa_storage;
ares_socklen_t salen = sizeof(sa_storage);
struct sockaddr *sa = (struct sockaddr *)&sa_storage;
conn->flags &= ~((unsigned int)ARES_CONN_FLAG_TFO_INITIAL);
is_tfo = ARES_TRUE;
if (ares_conn_set_sockaddr(conn, sa, &salen) != ARES_SUCCESS) {
return ARES_CONN_ERR_FAILURE;
}
err =
ares_socket_write_tfo(channel, conn->fd, data, len, written, sa, salen);
if (err != ARES_CONN_ERR_SUCCESS) {
goto done;
}
/* 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_FALSE);
goto done;
}
err = ares_socket_write(channel, conn->fd, data, len, written);
if (err != ARES_CONN_ERR_SUCCESS) {
goto done;
}
done:
if (err == ARES_CONN_ERR_SUCCESS && len == *written) {
/* Wrote all data, make sure we're not listening for write events unless
* using TFO, in which case we'll need a write event to know when
* we're connected. */
ares_conn_sock_state_cb_update(
conn, ARES_CONN_STATE_READ |
(is_tfo ? ARES_CONN_STATE_WRITE : ARES_CONN_STATE_NONE));
} else if (err == ARES_CONN_ERR_WOULDBLOCK) {
/* Need to wait on more buffer space to write */
ares_conn_sock_state_cb_update(conn, ARES_CONN_STATE_READ |
ARES_CONN_STATE_WRITE);
}
return err;
}
ares_status_t ares_conn_flush(ares_conn_t *conn)
{
const unsigned char *data;
size_t data_len;
size_t count;
ares_conn_err_t err;
ares_status_t status;
ares_bool_t tfo = ARES_FALSE;
if (conn == NULL) {
return ARES_EFORMERR;
}
if (conn->flags & ARES_CONN_FLAG_TFO_INITIAL) {
tfo = ARES_TRUE;
}
do {
if (ares_buf_len(conn->out_buf) == 0) {
status = ARES_SUCCESS;
goto done;
}
if (conn->flags & ARES_CONN_FLAG_TCP) {
data = ares_buf_peek(conn->out_buf, &data_len);
} else {
unsigned short msg_len;
/* Read length, then provide buffer without length */
ares_buf_tag(conn->out_buf);
status = ares_buf_fetch_be16(conn->out_buf, &msg_len);
if (status != ARES_SUCCESS) {
return status;
}
ares_buf_tag_rollback(conn->out_buf);
data = ares_buf_peek(conn->out_buf, &data_len);
if (data_len < (size_t)(msg_len + 2)) {
status = ARES_EFORMERR;
goto done;
}
data += 2;
data_len = msg_len;
}
err = ares_conn_write(conn, data, data_len, &count);
if (err != ARES_CONN_ERR_SUCCESS) {
if (err != ARES_CONN_ERR_WOULDBLOCK) {
status = ARES_ECONNREFUSED;
goto done;
}
status = ARES_SUCCESS;
goto done;
}
/* UDP didn't send the length prefix so augment that here */
if (!(conn->flags & ARES_CONN_FLAG_TCP)) {
count += 2;
}
/* Strip data written from the buffer */
ares_buf_consume(conn->out_buf, count);
status = ARES_SUCCESS;
/* Loop only for UDP since we have to send per-packet. We already
* sent everything we could if using tcp */
} while (!(conn->flags & ARES_CONN_FLAG_TCP));
done:
if (status == ARES_SUCCESS) {
ares_conn_state_flags_t flags = ARES_CONN_STATE_READ;
/* When using TFO, the we need to enabling waiting on a write event to
* be notified of when a connection is actually established */
if (tfo) {
flags |= ARES_CONN_STATE_WRITE;
}
/* If using TCP and not all data was written (partial write), that means
* we need to also wait on a write event */
if (conn->flags & ARES_CONN_FLAG_TCP && ares_buf_len(conn->out_buf)) {
flags |= ARES_CONN_STATE_WRITE;
}
ares_conn_sock_state_cb_update(conn, flags);
}
return status;
}
static ares_status_t ares_conn_connect(ares_conn_t *conn, struct sockaddr *sa,
ares_socklen_t salen)
{
ares_conn_err_t err;
err = ares_socket_connect(
conn->server->channel, conn->fd,
(conn->flags & ARES_CONN_FLAG_TFO) ? ARES_TRUE : ARES_FALSE, sa, salen);
if (err != ARES_CONN_ERR_WOULDBLOCK && err != ARES_CONN_ERR_SUCCESS) {
return ARES_ECONNREFUSED;
}
return ARES_SUCCESS;
}
ares_status_t ares_open_connection(ares_conn_t **conn_out,
ares_channel_t *channel,
ares_server_t *server, ares_bool_t is_tcp)
{
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;
ares_conn_state_flags_t state_flags;
*conn_out = NULL;
conn = ares_malloc(sizeof(*conn));
if (conn == NULL) {
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->out_buf = ares_buf_create();
conn->in_buf = ares_buf_create();
if (conn->queries_to_conn == NULL || conn->out_buf == NULL ||
conn->in_buf == NULL) {
/* LCOV_EXCL_START: OutOfMemory */
status = ARES_ENOMEM;
goto done;
/* LCOV_EXCL_STOP */
}
/* 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 && ares_socket_tfo_supported(channel)) {
conn->flags |= ARES_CONN_FLAG_TFO;
}
/* 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. */
if (ares_socket_open(&conn->fd, channel, server->addr.family, stype, 0) !=
ARES_CONN_ERR_SUCCESS) {
status = ARES_ECONNREFUSED;
goto done;
}
/* Configure it. */
status = ares_socket_configure(
channel, server->addr.family,
(conn->flags & ARES_CONN_FLAG_TCP) ? ARES_TRUE : ARES_FALSE, conn->fd);
if (status != ARES_SUCCESS) {
goto done;
}
/* Enable TFO if possible */
if (conn->flags & ARES_CONN_FLAG_TFO) {
if (ares_socket_enable_tfo(channel, conn->fd) != ARES_CONN_ERR_SUCCESS) {
conn->flags &= ~((unsigned int)ARES_CONN_FLAG_TFO);
}
}
if (channel->sock_config_cb) {
int err =
channel->sock_config_cb(conn->fd, stype, channel->sock_config_cb_data);
if (err < 0) {
status = ARES_ECONNREFUSED;
goto done;
}
}
/* 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, 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, ARES_TRUE);
if (status != ARES_SUCCESS) {
goto done; /* LCOV_EXCL_LINE: UntestablePath */
}
/* TCP connections are thrown to the end as we don't spawn multiple TCP
* connections. UDP connections are put on front where the newest connection
* can be quickly pulled */
if (is_tcp) {
node = ares_llist_insert_last(server->connections, conn);
} else {
node = ares_llist_insert_first(server->connections, conn);
}
if (node == NULL) {
/* LCOV_EXCL_START: OutOfMemory */
status = ARES_ENOMEM;
goto done;
/* LCOV_EXCL_STOP */
}
/* Register globally to quickly map event on file descriptor to connection
* node object */
if (!ares_htable_asvp_insert(channel->connnode_by_socket, conn->fd, node)) {
/* LCOV_EXCL_START: OutOfMemory */
status = ARES_ENOMEM;
goto done;
/* LCOV_EXCL_STOP */
}
state_flags = ARES_CONN_STATE_READ;
/* Get notified on connect if using TCP */
if (conn->flags & ARES_CONN_FLAG_TCP) {
state_flags |= ARES_CONN_STATE_WRITE;
}
/* Dot no attempt to update sock state callbacks on TFO until *after* the
* initial write is performed. Due to the notification event, its possible
* an erroneous read can come in before the attempt to write the data which
* might be used to set the ip address */
if (!(conn->flags & ARES_CONN_FLAG_TFO_INITIAL)) {
ares_conn_sock_state_cb_update(conn, state_flags);
}
if (is_tcp) {
server->tcp_conn = conn;
}
done:
if (status != ARES_SUCCESS) {
ares_llist_node_claim(node);
ares_llist_destroy(conn->queries_to_conn);
ares_socket_close(channel, conn->fd);
ares_buf_destroy(conn->out_buf);
ares_buf_destroy(conn->in_buf);
ares_free(conn);
} else {
*conn_out = conn;
}
return status;
}
ares_conn_t *ares_conn_from_fd(ares_channel_t *channel, ares_socket_t fd)
{
ares_llist_node_t *node;
node = ares_htable_asvp_get_direct(channel->connnode_by_socket, fd);
if (node == NULL) {
return NULL;
}
return ares_llist_node_val(node);
}

@ -26,12 +26,14 @@
#ifndef __ARES_CONN_H #ifndef __ARES_CONN_H
#define __ARES_CONN_H #define __ARES_CONN_H
struct ares_server; #include "ares_socket.h"
typedef struct ares_server ares_server_t;
struct ares_conn; struct ares_conn;
typedef struct ares_conn ares_conn_t; typedef struct ares_conn ares_conn_t;
struct ares_server;
typedef struct ares_server ares_server_t;
typedef enum { typedef enum {
/*! No flags */ /*! No flags */
ARES_CONN_FLAG_NONE = 0, ARES_CONN_FLAG_NONE = 0,
@ -165,42 +167,16 @@ void ares_close_sockets(ares_server_t *server);
void ares_check_cleanup_conns(const ares_channel_t *channel); void ares_check_cleanup_conns(const ares_channel_t *channel);
void ares_destroy_servers_state(ares_channel_t *channel); void ares_destroy_servers_state(ares_channel_t *channel);
ares_status_t ares_open_connection(ares_conn_t **conn_out, ares_status_t ares_open_connection(ares_conn_t **conn_out,
ares_channel_t *channel, ares_channel_t *channel,
ares_server_t *server, ares_bool_t is_tcp); ares_server_t *server, ares_bool_t is_tcp);
ares_bool_t ares_sockaddr_to_ares_addr(struct ares_addr *ares_addr,
unsigned short *port,
const struct sockaddr *sockaddr);
/*! Socket errors */
typedef enum {
ARES_CONN_ERR_SUCCESS = 0, /*!< Success */
ARES_CONN_ERR_WOULDBLOCK = 1, /*!< Operation would block */
ARES_CONN_ERR_CONNCLOSED = 2, /*!< Connection closed (gracefully) */
ARES_CONN_ERR_CONNABORTED = 3, /*!< Connection Aborted */
ARES_CONN_ERR_CONNRESET = 4, /*!< Connection Reset */
ARES_CONN_ERR_CONNREFUSED = 5, /*!< Connection Refused */
ARES_CONN_ERR_CONNTIMEDOUT = 6, /*!< Connection Timed Out */
ARES_CONN_ERR_HOSTDOWN = 7, /*!< Host Down */
ARES_CONN_ERR_HOSTUNREACH = 8, /*!< Host Unreachable */
ARES_CONN_ERR_NETDOWN = 9, /*!< Network Down */
ARES_CONN_ERR_NETUNREACH = 10, /*!< Network Unreachable */
ARES_CONN_ERR_INTERRUPT = 11, /*!< Call interrupted by signal, repeat */
ARES_CONN_ERR_AFNOSUPPORT = 12, /*!< Address family not supported */
ARES_CONN_ERR_BADADDR = 13, /*!< Bad Address / Unavailable */
ARES_CONN_ERR_NOMEM = 14, /*!< Out of memory */
ARES_CONN_ERR_INVALID = 15, /*!< Invalid Usage */
ARES_CONN_ERR_FAILURE = 99 /*!< Generic failure */
} ares_conn_err_t;
ares_conn_err_t ares_open_socket(ares_socket_t *sock, ares_channel_t *channel,
int af, int type, int protocol);
ares_bool_t ares_socket_try_again(int errnum);
ares_conn_err_t ares_conn_write(ares_conn_t *conn, const void *data, size_t len, ares_conn_err_t ares_conn_write(ares_conn_t *conn, const void *data, size_t len,
size_t *written); size_t *written);
ares_status_t ares_conn_flush(ares_conn_t *conn); ares_status_t ares_conn_flush(ares_conn_t *conn);
ares_conn_err_t ares_conn_read(ares_conn_t *conn, void *data, size_t len, ares_conn_err_t ares_conn_read(ares_conn_t *conn, void *data, size_t len,
size_t *read_bytes); size_t *read_bytes);
ares_conn_t *ares_conn_from_fd(ares_channel_t *channel, ares_socket_t fd);
void ares_conn_sock_state_cb_update(ares_conn_t *conn, void ares_conn_sock_state_cb_update(ares_conn_t *conn,
ares_conn_state_flags_t flags); ares_conn_state_flags_t flags);
ares_conn_err_t ares_socket_recv(ares_channel_t *channel, ares_socket_t s, ares_conn_err_t ares_socket_recv(ares_channel_t *channel, ares_socket_t s,
@ -212,10 +188,7 @@ ares_conn_err_t ares_socket_recvfrom(ares_channel_t *channel, ares_socket_t s,
struct sockaddr *from, struct sockaddr *from,
ares_socklen_t *from_len, ares_socklen_t *from_len,
size_t *read_bytes); size_t *read_bytes);
void ares_close_socket(ares_channel_t *channel, ares_socket_t s);
ares_status_t ares_connect_socket(ares_channel_t *channel, ares_socket_t sockfd, void ares_destroy_server(ares_server_t *server);
const struct sockaddr *addr,
ares_socklen_t addrlen);
void ares_destroy_server(ares_server_t *server);
#endif #endif

@ -52,11 +52,13 @@
#include "ares_htable_szvp.h" #include "ares_htable_szvp.h"
#include "ares_htable_asvp.h" #include "ares_htable_asvp.h"
#include "ares_htable_vpvp.h" #include "ares_htable_vpvp.h"
#include "ares_htable_vpstr.h"
#include "record/ares_dns_multistring.h" #include "record/ares_dns_multistring.h"
#include "ares_buf.h" #include "ares_buf.h"
#include "record/ares_dns_private.h" #include "record/ares_dns_private.h"
#include "util/ares_iface_ips.h" #include "util/ares_iface_ips.h"
#include "util/ares_threads.h" #include "util/ares_threads.h"
#include "ares_socket.h"
#include "ares_conn.h" #include "ares_conn.h"
#include "ares_str.h" #include "ares_str.h"
#include "str/ares_strsplit.h" #include "str/ares_strsplit.h"

@ -45,13 +45,14 @@
#include <limits.h> #include <limits.h>
static void timeadd(ares_timeval_t *now, size_t millisecs); static void timeadd(ares_timeval_t *now, size_t millisecs);
static void process_write(const ares_channel_t *channel, static ares_status_t process_write(ares_channel_t *channel,
ares_socket_t write_fd); ares_socket_t write_fd);
static void process_read(const ares_channel_t *channel, ares_socket_t read_fd, static ares_status_t process_read(ares_channel_t *channel,
const ares_timeval_t *now); ares_socket_t read_fd,
static void process_timeouts(ares_channel_t *channel, const ares_timeval_t *now);
const ares_timeval_t *now); static ares_status_t process_timeouts(ares_channel_t *channel,
const ares_timeval_t *now);
static ares_status_t process_answer(ares_channel_t *channel, static ares_status_t process_answer(ares_channel_t *channel,
const unsigned char *abuf, size_t alen, const unsigned char *abuf, size_t alen,
ares_conn_t *conn, ares_conn_t *conn,
@ -187,53 +188,75 @@ static void timeadd(ares_timeval_t *now, size_t millisecs)
} }
} }
static void ares_process_fds_nolock(ares_channel_t *channel, static ares_status_t ares_process_fds_nolock(ares_channel_t *channel,
const ares_fd_events_t *events, const ares_fd_events_t *events,
size_t nevents, unsigned int flags) size_t nevents, unsigned int flags)
{ {
ares_timeval_t now; ares_timeval_t now;
size_t i; size_t i;
ares_status_t status = ARES_SUCCESS;
if (channel == NULL || (events == NULL && nevents != 0)) { if (channel == NULL || (events == NULL && nevents != 0)) {
return; /* LCOV_EXCL_LINE: DefensiveCoding */ return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
} }
ares_tvnow(&now); ares_tvnow(&now);
/* Process read events */ /* Process write events */
for (i = 0; i < nevents; i++) { for (i = 0; i < nevents; i++) {
if (events[i].fd == ARES_SOCKET_BAD || if (events[i].fd == ARES_SOCKET_BAD ||
!(events[i].events & ARES_FD_EVENT_READ)) { !(events[i].events & ARES_FD_EVENT_WRITE)) {
continue; continue;
} }
process_read(channel, events[i].fd, &now); status = process_write(channel, events[i].fd);
/* We only care about ENOMEM, anything else is handled via connection
* retries, etc */
if (status == ARES_ENOMEM) {
goto done;
}
} }
/* Process write events */ /* Process read events */
for (i = 0; i < nevents; i++) { for (i = 0; i < nevents; i++) {
if (events[i].fd == ARES_SOCKET_BAD || if (events[i].fd == ARES_SOCKET_BAD ||
!(events[i].events & ARES_FD_EVENT_WRITE)) { !(events[i].events & ARES_FD_EVENT_READ)) {
continue; continue;
} }
process_write(channel, events[i].fd); status = process_read(channel, events[i].fd, &now);
if (status == ARES_ENOMEM) {
goto done;
}
} }
if (!(flags & ARES_PROCESS_FLAG_SKIP_NON_FD)) { if (!(flags & ARES_PROCESS_FLAG_SKIP_NON_FD)) {
ares_check_cleanup_conns(channel); ares_check_cleanup_conns(channel);
process_timeouts(channel, &now); status = process_timeouts(channel, &now);
if (status == ARES_ENOMEM) {
goto done;
}
} }
done:
if (status == ARES_ENOMEM) {
return ARES_ENOMEM;
}
return ARES_SUCCESS;
} }
void ares_process_fds(ares_channel_t *channel, const ares_fd_events_t *events, ares_status_t ares_process_fds(ares_channel_t *channel,
size_t nevents, unsigned int flags) const ares_fd_events_t *events, size_t nevents,
unsigned int flags)
{ {
ares_status_t status;
if (channel == NULL) { if (channel == NULL) {
return; return ARES_EFORMERR;
} }
ares_channel_lock(channel); ares_channel_lock(channel);
ares_process_fds_nolock(channel, events, nevents, flags); status = ares_process_fds_nolock(channel, events, nevents, flags);
ares_channel_unlock(channel); ares_channel_unlock(channel);
return status;
} }
void ares_process_fd(ares_channel_t *channel, ares_socket_t read_fd, void ares_process_fd(ares_channel_t *channel, ares_socket_t read_fd,
@ -355,19 +378,16 @@ done:
ares_channel_unlock(channel); ares_channel_unlock(channel);
} }
static void process_write(const ares_channel_t *channel, ares_socket_t write_fd) static ares_status_t process_write(ares_channel_t *channel,
ares_socket_t write_fd)
{ {
ares_llist_node_t *node; ares_conn_t *conn = ares_conn_from_fd(channel, write_fd);
ares_conn_t *conn; ares_status_t status;
ares_status_t status;
node = ares_htable_asvp_get_direct(channel->connnode_by_socket, write_fd); if (conn == NULL) {
if (node == NULL) { return ARES_SUCCESS;
return;
} }
conn = ares_llist_node_val(node);
/* Mark as connected if we got here and TFO Initial not set */ /* Mark as connected if we got here and TFO Initial not set */
if (!(conn->flags & ARES_CONN_FLAG_TFO_INITIAL)) { if (!(conn->flags & ARES_CONN_FLAG_TFO_INITIAL)) {
conn->state_flags |= ARES_CONN_STATE_CONNECTED; conn->state_flags |= ARES_CONN_STATE_CONNECTED;
@ -377,6 +397,7 @@ static void process_write(const ares_channel_t *channel, ares_socket_t write_fd)
if (status != ARES_SUCCESS) { if (status != ARES_SUCCESS) {
handle_conn_error(conn, ARES_TRUE, status); handle_conn_error(conn, ARES_TRUE, status);
} }
return status;
} }
void ares_process_pending_write(ares_channel_t *channel) void ares_process_pending_write(ares_channel_t *channel)
@ -492,8 +513,9 @@ static ares_status_t read_conn_packets(ares_conn_t *conn)
return ARES_SUCCESS; return ARES_SUCCESS;
} }
static void read_answers(ares_conn_t *conn, const ares_timeval_t *now) static ares_status_t read_answers(ares_conn_t *conn, const ares_timeval_t *now)
{ {
ares_status_t status;
ares_channel_t *channel = conn->server->channel; ares_channel_t *channel = conn->server->channel;
/* Process all queued answers */ /* Process all queued answers */
@ -501,19 +523,20 @@ static void read_answers(ares_conn_t *conn, const ares_timeval_t *now)
unsigned short dns_len = 0; unsigned short dns_len = 0;
const unsigned char *data = NULL; const unsigned char *data = NULL;
size_t data_len = 0; size_t data_len = 0;
ares_status_t status;
/* Tag so we can roll back */ /* Tag so we can roll back */
ares_buf_tag(conn->in_buf); ares_buf_tag(conn->in_buf);
/* Read length indicator */ /* Read length indicator */
if (ares_buf_fetch_be16(conn->in_buf, &dns_len) != ARES_SUCCESS) { status = ares_buf_fetch_be16(conn->in_buf, &dns_len);
if (status != ARES_SUCCESS) {
ares_buf_tag_rollback(conn->in_buf); ares_buf_tag_rollback(conn->in_buf);
break; break;
} }
/* Not enough data for a full response yet */ /* Not enough data for a full response yet */
if (ares_buf_consume(conn->in_buf, dns_len) != ARES_SUCCESS) { status = ares_buf_consume(conn->in_buf, dns_len);
if (status != ARES_SUCCESS) {
ares_buf_tag_rollback(conn->in_buf); ares_buf_tag_rollback(conn->in_buf);
break; break;
} }
@ -533,43 +556,46 @@ static void read_answers(ares_conn_t *conn, const ares_timeval_t *now)
status = process_answer(channel, data, data_len, conn, now); status = process_answer(channel, data, data_len, conn, now);
if (status != ARES_SUCCESS) { if (status != ARES_SUCCESS) {
handle_conn_error(conn, ARES_TRUE, status); handle_conn_error(conn, ARES_TRUE, status);
return; return status;
} }
/* Since we processed the answer, clear the tag so space can be reclaimed */ /* Since we processed the answer, clear the tag so space can be reclaimed */
ares_buf_tag_clear(conn->in_buf); ares_buf_tag_clear(conn->in_buf);
} }
return status;
} }
static void process_read(const ares_channel_t *channel, ares_socket_t read_fd, static ares_status_t process_read(ares_channel_t *channel,
const ares_timeval_t *now) ares_socket_t read_fd,
const ares_timeval_t *now)
{ {
ares_llist_node_t *node; ares_conn_t *conn = ares_conn_from_fd(channel, read_fd);
ares_conn_t *conn; ares_status_t status;
node = ares_htable_asvp_get_direct(channel->connnode_by_socket, read_fd); if (conn == NULL) {
if (node == NULL) { return ARES_SUCCESS;
return;
} }
conn = ares_llist_node_val(node);
/* TODO: There might be a potential issue here where there was a read that /* TODO: There might be a potential issue here where there was a read that
* read some data, then looped and read again and got a disconnect. * read some data, then looped and read again and got a disconnect.
* Right now, that would cause a resend instead of processing the data * Right now, that would cause a resend instead of processing the data
* we have. This is fairly unlikely to occur due to only looping if * we have. This is fairly unlikely to occur due to only looping if
* a full buffer of 65535 bytes was read. */ * a full buffer of 65535 bytes was read. */
if (read_conn_packets(conn) != ARES_SUCCESS) { status = read_conn_packets(conn);
return;
if (status != ARES_SUCCESS) {
return status;
} }
read_answers(conn, now); return read_answers(conn, now);
} }
/* If any queries have timed out, note the timeout and move them on. */ /* If any queries have timed out, note the timeout and move them on. */
static void process_timeouts(ares_channel_t *channel, const ares_timeval_t *now) static ares_status_t process_timeouts(ares_channel_t *channel,
const ares_timeval_t *now)
{ {
ares_slist_node_t *node; ares_slist_node_t *node;
ares_status_t status = ARES_SUCCESS;
/* Just keep popping off the first as this list will re-sort as things come /* Just keep popping off the first as this list will re-sort as things come
* and go. We don't want to try to rely on 'next' as some operation might * and go. We don't want to try to rely on 'next' as some operation might
@ -588,8 +614,16 @@ static void process_timeouts(ares_channel_t *channel, const ares_timeval_t *now)
conn = query->conn; conn = query->conn;
server_increment_failures(conn->server, query->using_tcp); server_increment_failures(conn->server, query->using_tcp);
ares_requeue_query(query, now, ARES_ETIMEOUT, ARES_TRUE, NULL); status = ares_requeue_query(query, now, ARES_ETIMEOUT, ARES_TRUE, NULL);
if (status == ARES_ENOMEM) {
goto done;
}
} }
done:
if (status == ARES_ENOMEM) {
return ARES_ENOMEM;
}
return ARES_SUCCESS;
} }
static ares_status_t rewrite_without_edns(ares_query_t *query) static ares_status_t rewrite_without_edns(ares_query_t *query)

@ -25,7 +25,6 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
#include "ares_private.h" #include "ares_private.h"
#ifdef HAVE_SYS_UIO_H #ifdef HAVE_SYS_UIO_H
# include <sys/uio.h> # include <sys/uio.h>
#endif #endif
@ -57,25 +56,25 @@
#include <limits.h> #include <limits.h>
#if defined(__linux__) && defined(TCP_FASTOPEN_CONNECT) #if defined(__linux__) && defined(TCP_FASTOPEN_CONNECT)
# define TFO_SUPPORTED 1 # define TFO_SUPPORTED ARES_TRUE
# define TFO_SKIP_CONNECT 0 # define TFO_SKIP_CONNECT 0
# define TFO_USE_SENDTO 0 # define TFO_USE_SENDTO 0
# define TFO_USE_CONNECTX 0 # define TFO_USE_CONNECTX 0
# define TFO_CLIENT_SOCKOPT TCP_FASTOPEN_CONNECT # define TFO_CLIENT_SOCKOPT TCP_FASTOPEN_CONNECT
#elif defined(__FreeBSD__) && defined(TCP_FASTOPEN) #elif defined(__FreeBSD__) && defined(TCP_FASTOPEN)
# define TFO_SUPPORTED 1 # define TFO_SUPPORTED ARES_TRUE
# define TFO_SKIP_CONNECT 1 # define TFO_SKIP_CONNECT 1
# define TFO_USE_SENDTO 1 # define TFO_USE_SENDTO 1
# define TFO_USE_CONNECTX 0 # define TFO_USE_CONNECTX 0
# define TFO_CLIENT_SOCKOPT TCP_FASTOPEN # define TFO_CLIENT_SOCKOPT TCP_FASTOPEN
#elif defined(__APPLE__) && defined(HAVE_CONNECTX) #elif defined(__APPLE__) && defined(HAVE_CONNECTX)
# define TFO_SUPPORTED 1 # define TFO_SUPPORTED ARES_TRUE
# define TFO_SKIP_CONNECT 0 # define TFO_SKIP_CONNECT 0
# define TFO_USE_SENDTO 0 # define TFO_USE_SENDTO 0
# define TFO_USE_CONNECTX 1 # define TFO_USE_CONNECTX 1
# undef TFO_CLIENT_SOCKOPT # undef TFO_CLIENT_SOCKOPT
#else #else
# define TFO_SUPPORTED 0 # define TFO_SUPPORTED ARES_FALSE
#endif #endif
@ -175,6 +174,16 @@ struct iovec {
}; };
#endif #endif
ares_bool_t ares_socket_tfo_supported(const ares_channel_t *channel)
{
if (!TFO_SUPPORTED ||
(channel->sock_funcs != NULL && channel->sock_funcs->asendv != NULL)) {
return ARES_FALSE;
}
return ARES_TRUE;
}
static ares_conn_err_t ares_socket_deref_error(int err) static ares_conn_err_t ares_socket_deref_error(int err)
{ {
switch (err) { switch (err) {
@ -217,8 +226,8 @@ static ares_conn_err_t ares_socket_deref_error(int err)
return ARES_CONN_ERR_FAILURE; return ARES_CONN_ERR_FAILURE;
} }
static ares_bool_t same_address(const struct sockaddr *sa, ares_bool_t ares_sockaddr_addr_eq(const struct sockaddr *sa,
const struct ares_addr *aa) const struct ares_addr *aa)
{ {
const void *addr1; const void *addr1;
const void *addr2; const void *addr2;
@ -247,20 +256,76 @@ static ares_bool_t same_address(const struct sockaddr *sa,
return ARES_FALSE; /* different */ return ARES_FALSE; /* different */
} }
void ares_conn_sock_state_cb_update(ares_conn_t *conn, ares_conn_err_t ares_socket_write(ares_channel_t *channel, ares_socket_t fd,
ares_conn_state_flags_t flags) const void *data, size_t len, size_t *written)
{
int flags = 0;
ares_ssize_t rv;
ares_conn_err_t err = ARES_CONN_ERR_SUCCESS;
#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;
rv = channel->sock_funcs->asendv(fd, &vec, 1, channel->sock_func_cb_data);
if (rv <= 0) {
err = ares_socket_deref_error(SOCKERRNO);
} else {
*written = (size_t)rv;
}
return err;
}
rv = (ares_ssize_t)send((SEND_TYPE_ARG1)fd, (SEND_TYPE_ARG2)data,
(SEND_TYPE_ARG3)len, (SEND_TYPE_ARG4)flags);
if (rv <= 0) {
err = ares_socket_deref_error(SOCKERRNO);
} else {
*written = (size_t)rv;
}
return err;
}
ares_conn_err_t ares_socket_write_tfo(ares_channel_t *channel, ares_socket_t fd,
const void *data, size_t len,
size_t *written, struct sockaddr *sa,
ares_socklen_t salen)
{ {
ares_channel_t *channel = conn->server->channel; ares_conn_err_t err;
if ((conn->state_flags & ARES_CONN_STATE_CBFLAGS) != flags && if (!ares_socket_tfo_supported(channel)) {
channel->sock_state_cb) { return ARES_CONN_ERR_NOTIMP;
channel->sock_state_cb(channel->sock_state_cb_data, conn->fd,
flags & ARES_CONN_STATE_READ ? 1 : 0,
flags & ARES_CONN_STATE_WRITE ? 1 : 0);
} }
conn->state_flags &= ~((unsigned int)ARES_CONN_STATE_CBFLAGS); #if defined(TFO_USE_SENDTO) && TFO_USE_SENDTO
conn->state_flags |= flags; {
ares_ssize_t rv;
int flags = 0;
# ifdef HAVE_MSG_NOSIGNAL
flags |= MSG_NOSIGNAL;
# endif
err = ARES_CONN_ERR_SUCCESS;
rv = (ares_ssize_t)sendto((SEND_TYPE_ARG1)fd, (SEND_TYPE_ARG2)data,
(SEND_TYPE_ARG3)len, (SEND_TYPE_ARG4)flags, sa,
salen);
if (rv <= 0) {
err = ares_socket_deref_error(SOCKERRNO);
} else {
*written = (size_t)rv;
}
}
#else
(void)sa;
(void)salen;
err = ares_socket_write(channel, fd, data, len, written);
#endif
return err;
} }
ares_conn_err_t ares_socket_recv(ares_channel_t *channel, ares_socket_t s, ares_conn_err_t ares_socket_recv(ares_channel_t *channel, ares_socket_t s,
@ -336,308 +401,6 @@ ares_conn_err_t ares_socket_recvfrom(ares_channel_t *channel, ares_socket_t s,
return ares_socket_deref_error(SOCKERRNO); return ares_socket_deref_error(SOCKERRNO);
} }
ares_conn_err_t ares_conn_read(ares_conn_t *conn, void *data, size_t len,
size_t *read_bytes)
{
ares_channel_t *channel = conn->server->channel;
ares_conn_err_t err;
if (!(conn->flags & ARES_CONN_FLAG_TCP)) {
struct sockaddr_storage sa_storage;
ares_socklen_t salen = sizeof(sa_storage);
memset(&sa_storage, 0, sizeof(sa_storage));
err =
ares_socket_recvfrom(channel, conn->fd, ARES_FALSE, data, len, 0,
(struct sockaddr *)&sa_storage, &salen, read_bytes);
#ifdef HAVE_RECVFROM
if (err == ARES_CONN_ERR_SUCCESS &&
!same_address((struct sockaddr *)&sa_storage, &conn->server->addr)) {
err = ARES_CONN_ERR_WOULDBLOCK;
}
#endif
} else {
err = ares_socket_recv(channel, conn->fd, ARES_TRUE, data, len, read_bytes);
}
/* Toggle connected state if needed */
if (err == ARES_CONN_ERR_SUCCESS) {
conn->state_flags |= ARES_CONN_STATE_CONNECTED;
}
return err;
}
/* 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)
{
const ares_server_t *server = conn->server;
unsigned short port =
conn->flags & ARES_CONN_FLAG_TCP ? server->tcp_port : server->udp_port;
struct sockaddr_in *sin;
struct sockaddr_in6 *sin6;
switch (server->addr.family) {
case AF_INET:
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:
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;
}
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_conn_err_t ares_conn_write(ares_conn_t *conn, const void *data, size_t len,
size_t *written)
{
ares_channel_t *channel = conn->server->channel;
int flags = 0;
ares_ssize_t rv;
ares_bool_t is_tfo = ARES_FALSE;
ares_conn_err_t err = ARES_CONN_ERR_SUCCESS;
*written = 0;
/* Don't try to write if not doing initial TFO and not connected */
if (conn->flags & ARES_CONN_FLAG_TCP &&
!(conn->state_flags & ARES_CONN_STATE_CONNECTED) &&
!(conn->flags & ARES_CONN_FLAG_TFO_INITIAL)) {
return ARES_CONN_ERR_WOULDBLOCK;
}
#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;
rv = channel->sock_funcs->asendv(conn->fd, &vec, 1,
channel->sock_func_cb_data);
if (rv <= 0) {
err = ares_socket_deref_error(SOCKERRNO);
} else {
*written = (size_t)rv;
}
goto done;
}
if (conn->flags & ARES_CONN_FLAG_TFO_INITIAL) {
conn->flags &= ~((unsigned int)ARES_CONN_FLAG_TFO_INITIAL);
is_tfo = ARES_TRUE;
#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;
if (ares_conn_set_sockaddr(conn, sa, &salen) != ARES_SUCCESS) {
return ARES_CONN_ERR_FAILURE;
}
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 (rv <= 0) {
err = ares_socket_deref_error(SOCKERRNO);
} else {
*written = (size_t)rv;
}
/* 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_FALSE);
goto done;
}
#endif
}
rv = (ares_ssize_t)send((SEND_TYPE_ARG1)conn->fd, (SEND_TYPE_ARG2)data,
(SEND_TYPE_ARG3)len, (SEND_TYPE_ARG4)flags);
if (rv <= 0) {
err = ares_socket_deref_error(SOCKERRNO);
} else {
*written = (size_t)rv;
}
goto done;
done:
if (err == ARES_CONN_ERR_SUCCESS && len == *written) {
/* Wrote all data, make sure we're not listening for write events unless
* using TFO, in which case we'll need a write event to know when
* we're connected. */
ares_conn_sock_state_cb_update(
conn, ARES_CONN_STATE_READ |
(is_tfo ? ARES_CONN_STATE_WRITE : ARES_CONN_STATE_NONE));
} else if (err == ARES_CONN_ERR_WOULDBLOCK) {
/* Need to wait on more buffer space to write */
ares_conn_sock_state_cb_update(conn, ARES_CONN_STATE_READ |
ARES_CONN_STATE_WRITE);
}
return err;
}
ares_status_t ares_conn_flush(ares_conn_t *conn)
{
const unsigned char *data;
size_t data_len;
size_t count;
ares_conn_err_t err;
ares_status_t status;
ares_bool_t tfo = ARES_FALSE;
if (conn == NULL) {
return ARES_EFORMERR;
}
if (conn->flags & ARES_CONN_FLAG_TFO_INITIAL) {
tfo = ARES_TRUE;
}
do {
if (ares_buf_len(conn->out_buf) == 0) {
status = ARES_SUCCESS;
goto done;
}
if (conn->flags & ARES_CONN_FLAG_TCP) {
data = ares_buf_peek(conn->out_buf, &data_len);
} else {
unsigned short msg_len;
/* Read length, then provide buffer without length */
ares_buf_tag(conn->out_buf);
status = ares_buf_fetch_be16(conn->out_buf, &msg_len);
if (status != ARES_SUCCESS) {
return status;
}
ares_buf_tag_rollback(conn->out_buf);
data = ares_buf_peek(conn->out_buf, &data_len);
if (data_len < (size_t)(msg_len + 2)) {
status = ARES_EFORMERR;
goto done;
}
data += 2;
data_len = msg_len;
}
err = ares_conn_write(conn, data, data_len, &count);
if (err != ARES_CONN_ERR_SUCCESS) {
if (err != ARES_CONN_ERR_WOULDBLOCK) {
status = ARES_ECONNREFUSED;
goto done;
}
status = ARES_SUCCESS;
goto done;
}
/* UDP didn't send the length prefix so augment that here */
if (!(conn->flags & ARES_CONN_FLAG_TCP)) {
count += 2;
}
/* Strip data written from the buffer */
ares_buf_consume(conn->out_buf, count);
status = ARES_SUCCESS;
/* Loop only for UDP since we have to send per-packet. We already
* sent everything we could if using tcp */
} while (!(conn->flags & ARES_CONN_FLAG_TCP));
done:
if (status == ARES_SUCCESS) {
ares_conn_state_flags_t flags = ARES_CONN_STATE_READ;
/* When using TFO, the we need to enabling waiting on a write event to
* be notified of when a connection is actually established */
if (tfo) {
flags |= ARES_CONN_STATE_WRITE;
}
/* If using TCP and not all data was written (partial write), that means
* we need to also wait on a write event */
if (conn->flags & ARES_CONN_FLAG_TCP && ares_buf_len(conn->out_buf)) {
flags |= ARES_CONN_STATE_WRITE;
}
ares_conn_sock_state_cb_update(conn, flags);
}
return status;
}
/* /*
* setsocknonblock sets the given socket to either blocking or non-blocking * setsocknonblock sets the given socket to either blocking or non-blocking
* mode based on the 'nonblock' boolean argument. This function is highly * mode based on the 'nonblock' boolean argument. This function is highly
@ -710,7 +473,29 @@ static void set_ipv6_v6only(ares_socket_t sockfd, int on)
# define set_ipv6_v6only(s, v) # define set_ipv6_v6only(s, v)
#endif #endif
static ares_status_t configure_socket(ares_conn_t *conn) ares_conn_err_t ares_socket_enable_tfo(const ares_channel_t *channel,
ares_socket_t fd)
{
if (!ares_socket_tfo_supported(channel)) {
return ARES_CONN_ERR_NOTIMP;
}
#if defined(TFO_CLIENT_SOCKOPT)
{
int opt = 1;
if (setsockopt(fd, IPPROTO_TCP, TFO_CLIENT_SOCKOPT, (void *)&opt,
sizeof(opt)) != 0) {
return ARES_CONN_ERR_NOTIMP;
}
}
#else
(void)fd;
#endif
return ARES_CONN_ERR_SUCCESS;
}
ares_status_t ares_socket_configure(ares_channel_t *channel, int family,
ares_bool_t is_tcp, ares_socket_t fd)
{ {
union { union {
struct sockaddr sa; struct sockaddr sa;
@ -718,20 +503,18 @@ static ares_status_t configure_socket(ares_conn_t *conn)
struct sockaddr_in6 sa6; struct sockaddr_in6 sa6;
} local; } local;
ares_socklen_t bindlen = 0; ares_socklen_t bindlen = 0;
ares_server_t *server = conn->server;
ares_channel_t *channel = server->channel;
/* do not set options for user-managed sockets */ /* do not set options for user-managed sockets */
if (channel->sock_funcs && channel->sock_funcs->asocket) { if (channel->sock_funcs && channel->sock_funcs->asocket) {
return ARES_SUCCESS; return ARES_SUCCESS;
} }
(void)setsocknonblock(conn->fd, 1); (void)setsocknonblock(fd, 1);
#if defined(FD_CLOEXEC) && !defined(MSDOS) #if defined(FD_CLOEXEC) && !defined(MSDOS)
/* Configure the socket fd as close-on-exec. */ /* Configure the socket fd as close-on-exec. */
if (fcntl(conn->fd, F_SETFD, FD_CLOEXEC) != 0) { if (fcntl(fd, F_SETFD, FD_CLOEXEC) != 0) {
return ARES_ECONNREFUSED; /* LCOV_EXCL_LINE */ return ARES_ECONNREFUSED; /* LCOV_EXCL_LINE */
} }
#endif #endif
@ -740,21 +523,20 @@ static ares_status_t configure_socket(ares_conn_t *conn)
#if defined(SO_NOSIGPIPE) #if defined(SO_NOSIGPIPE)
{ {
int opt = 1; int opt = 1;
(void)setsockopt(conn->fd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&opt, (void)setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&opt, sizeof(opt));
sizeof(opt));
} }
#endif #endif
/* Set the socket's send and receive buffer sizes. */ /* Set the socket's send and receive buffer sizes. */
if (channel->socket_send_buffer_size > 0 && if (channel->socket_send_buffer_size > 0 &&
setsockopt(conn->fd, SOL_SOCKET, SO_SNDBUF, setsockopt(fd, SOL_SOCKET, SO_SNDBUF,
(void *)&channel->socket_send_buffer_size, (void *)&channel->socket_send_buffer_size,
sizeof(channel->socket_send_buffer_size)) != 0) { sizeof(channel->socket_send_buffer_size)) != 0) {
return ARES_ECONNREFUSED; /* LCOV_EXCL_LINE: UntestablePath */ return ARES_ECONNREFUSED; /* LCOV_EXCL_LINE: UntestablePath */
} }
if (channel->socket_receive_buffer_size > 0 && if (channel->socket_receive_buffer_size > 0 &&
setsockopt(conn->fd, SOL_SOCKET, SO_RCVBUF, setsockopt(fd, SOL_SOCKET, SO_RCVBUF,
(void *)&channel->socket_receive_buffer_size, (void *)&channel->socket_receive_buffer_size,
sizeof(channel->socket_receive_buffer_size)) != 0) { sizeof(channel->socket_receive_buffer_size)) != 0) {
return ARES_ECONNREFUSED; /* LCOV_EXCL_LINE: UntestablePath */ return ARES_ECONNREFUSED; /* LCOV_EXCL_LINE: UntestablePath */
@ -764,17 +546,17 @@ static ares_status_t configure_socket(ares_conn_t *conn)
if (ares_strlen(channel->local_dev_name)) { if (ares_strlen(channel->local_dev_name)) {
/* Only root can do this, and usually not fatal if it doesn't work, so /* Only root can do this, and usually not fatal if it doesn't work, so
* just continue on. */ * just continue on. */
(void)setsockopt(conn->fd, SOL_SOCKET, SO_BINDTODEVICE, (void)setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, channel->local_dev_name,
channel->local_dev_name, sizeof(channel->local_dev_name)); sizeof(channel->local_dev_name));
} }
#endif #endif
if (server->addr.family == AF_INET && channel->local_ip4) { if (family == AF_INET && channel->local_ip4) {
memset(&local.sa4, 0, sizeof(local.sa4)); memset(&local.sa4, 0, sizeof(local.sa4));
local.sa4.sin_family = AF_INET; local.sa4.sin_family = AF_INET;
local.sa4.sin_addr.s_addr = htonl(channel->local_ip4); local.sa4.sin_addr.s_addr = htonl(channel->local_ip4);
bindlen = sizeof(local.sa4); bindlen = sizeof(local.sa4);
} else if (server->addr.family == AF_INET6 && server->ll_scope == 0 && } else if (family == AF_INET6 &&
memcmp(channel->local_ip6, ares_in6addr_any._S6_un._S6_u8, memcmp(channel->local_ip6, ares_in6addr_any._S6_un._S6_u8,
sizeof(channel->local_ip6)) != 0) { sizeof(channel->local_ip6)) != 0) {
/* Only if not link-local and an ip other than "::" is specified */ /* Only if not link-local and an ip other than "::" is specified */
@ -785,15 +567,15 @@ static ares_status_t configure_socket(ares_conn_t *conn)
bindlen = sizeof(local.sa6); bindlen = sizeof(local.sa6);
} }
if (bindlen && bind(conn->fd, &local.sa, bindlen) < 0) { if (bindlen && bind(fd, &local.sa, bindlen) < 0) {
return ARES_ECONNREFUSED; return ARES_ECONNREFUSED;
} }
if (server->addr.family == AF_INET6) { if (family == AF_INET6) {
set_ipv6_v6only(conn->fd, 0); set_ipv6_v6only(fd, 0);
} }
if (conn->flags & ARES_CONN_FLAG_TCP) { if (is_tcp) {
int opt = 1; int opt = 1;
#ifdef TCP_NODELAY #ifdef TCP_NODELAY
@ -803,20 +585,11 @@ static ares_status_t configure_socket(ares_conn_t *conn)
* interested in firing off a single request and then waiting for a reply, * interested in firing off a single request and then waiting for a reply,
* so batching isn't very interesting. * so batching isn't very interesting.
*/ */
if (setsockopt(conn->fd, IPPROTO_TCP, TCP_NODELAY, (void *)&opt, if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&opt, sizeof(opt)) !=
sizeof(opt)) != 0) { 0) {
return ARES_ECONNREFUSED; return ARES_ECONNREFUSED;
} }
#endif #endif
#if defined(TFO_CLIENT_SOCKOPT)
if (conn->flags & ARES_CONN_FLAG_TFO &&
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; return ARES_SUCCESS;
@ -860,209 +633,7 @@ ares_bool_t ares_sockaddr_to_ares_addr(struct ares_addr *ares_addr,
return ARES_FALSE; return ARES_FALSE;
} }
static ares_status_t ares_conn_connect(ares_conn_t *conn, struct sockaddr *sa, ares_conn_err_t ares_socket_open(ares_socket_t *sock, ares_channel_t *channel,
ares_socklen_t salen)
{
/* Normal non TCPFastOpen style connect */
if (!(conn->flags & ARES_CONN_FLAG_TFO)) {
return ares_connect_socket(conn->server->channel, conn->fd, sa, salen);
}
/* 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;
ares_conn_err_t err;
do {
sa_endpoints_t endpoints;
memset(&endpoints, 0, sizeof(endpoints));
endpoints.sae_dstaddr = sa;
endpoints.sae_dstaddrlen = salen;
rv = connectx(conn->fd, &endpoints, SAE_ASSOCID_ANY,
CONNECT_DATA_IDEMPOTENT | CONNECT_RESUME_ON_READ_WRITE,
NULL, 0, NULL, NULL);
if (rv < 0) {
err = ares_socket_deref_error(SOCKERRNO);
} else {
break;
}
if (err != ARES_CONN_ERR_WOULDBLOCK && err != ARES_CONN_ERR_INTERRUPT) {
return ARES_ECONNREFUSED;
}
} while (err == ARES_CONN_ERR_INTERRUPT);
}
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_bool_t is_tcp)
{
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;
ares_conn_state_flags_t state_flags;
*conn_out = NULL;
conn = ares_malloc(sizeof(*conn));
if (conn == NULL) {
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->out_buf = ares_buf_create();
conn->in_buf = ares_buf_create();
if (conn->queries_to_conn == NULL || conn->out_buf == NULL ||
conn->in_buf == NULL) {
/* LCOV_EXCL_START: OutOfMemory */
status = ARES_ENOMEM;
goto done;
/* LCOV_EXCL_STOP */
}
/* 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;
}
/* 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. */
if (ares_open_socket(&conn->fd, channel, server->addr.family, stype, 0) !=
ARES_CONN_ERR_SUCCESS) {
status = ARES_ECONNREFUSED;
goto done;
}
/* Configure it. */
status = configure_socket(conn);
if (status != ARES_SUCCESS) {
goto done;
}
if (channel->sock_config_cb) {
int err =
channel->sock_config_cb(conn->fd, stype, channel->sock_config_cb_data);
if (err < 0) {
status = ARES_ECONNREFUSED;
goto done;
}
}
/* 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, 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, ARES_TRUE);
if (status != ARES_SUCCESS) {
goto done; /* LCOV_EXCL_LINE: UntestablePath */
}
/* TCP connections are thrown to the end as we don't spawn multiple TCP
* connections. UDP connections are put on front where the newest connection
* can be quickly pulled */
if (is_tcp) {
node = ares_llist_insert_last(server->connections, conn);
} else {
node = ares_llist_insert_first(server->connections, conn);
}
if (node == NULL) {
/* LCOV_EXCL_START: OutOfMemory */
status = ARES_ENOMEM;
goto done;
/* LCOV_EXCL_STOP */
}
/* Register globally to quickly map event on file descriptor to connection
* node object */
if (!ares_htable_asvp_insert(channel->connnode_by_socket, conn->fd, node)) {
/* LCOV_EXCL_START: OutOfMemory */
status = ARES_ENOMEM;
goto done;
/* LCOV_EXCL_STOP */
}
state_flags = ARES_CONN_STATE_READ;
/* Get notified on connect if using TCP */
if (conn->flags & ARES_CONN_FLAG_TCP) {
state_flags |= ARES_CONN_STATE_WRITE;
}
/* Dot no attempt to update sock state callbacks on TFO until *after* the
* initial write is performed. Due to the notification event, its possible
* an erroneous read can come in before the attempt to write the data which
* might be used to set the ip address */
if (!(conn->flags & ARES_CONN_FLAG_TFO_INITIAL)) {
ares_conn_sock_state_cb_update(conn, state_flags);
}
if (is_tcp) {
server->tcp_conn = conn;
}
done:
if (status != ARES_SUCCESS) {
ares_llist_node_claim(node);
ares_llist_destroy(conn->queries_to_conn);
ares_close_socket(channel, conn->fd);
ares_buf_destroy(conn->out_buf);
ares_buf_destroy(conn->in_buf);
ares_free(conn);
} else {
*conn_out = conn;
}
return status;
}
ares_conn_err_t ares_open_socket(ares_socket_t *sock, ares_channel_t *channel,
int af, int type, int protocol) int af, int type, int protocol)
{ {
ares_socket_t s; ares_socket_t s;
@ -1085,36 +656,55 @@ ares_conn_err_t ares_open_socket(ares_socket_t *sock, ares_channel_t *channel,
return ARES_CONN_ERR_SUCCESS; return ARES_CONN_ERR_SUCCESS;
} }
ares_status_t ares_connect_socket(ares_channel_t *channel, ares_socket_t sockfd, ares_conn_err_t ares_socket_connect(ares_channel_t *channel,
const struct sockaddr *addr, ares_socket_t sockfd, ares_bool_t is_tfo,
ares_socklen_t addrlen) const struct sockaddr *addr,
ares_socklen_t addrlen)
{ {
int rv; ares_conn_err_t err = ARES_CONN_ERR_SUCCESS;
ares_conn_err_t err;
#if defined(TFO_SKIP_CONNECT) && TFO_SKIP_CONNECT
if (is_tfo) {
return ARES_CONN_ERR_SUCCESS;
}
#endif
do { do {
int rv;
if (channel->sock_funcs && channel->sock_funcs->aconnect) { if (channel->sock_funcs && channel->sock_funcs->aconnect) {
rv = channel->sock_funcs->aconnect(sockfd, addr, addrlen, rv = channel->sock_funcs->aconnect(sockfd, addr, addrlen,
channel->sock_func_cb_data); channel->sock_func_cb_data);
} else { } else {
rv = connect(sockfd, addr, addrlen); if (is_tfo) {
#if defined(TFO_USE_CONNECTX) && TFO_USE_CONNECTX
sa_endpoints_t endpoints;
memset(&endpoints, 0, sizeof(endpoints));
endpoints.sae_dstaddr = addr;
endpoints.sae_dstaddrlen = addrlen;
rv = connectx(sockfd, &endpoints, SAE_ASSOCID_ANY,
CONNECT_DATA_IDEMPOTENT | CONNECT_RESUME_ON_READ_WRITE,
NULL, 0, NULL, NULL);
#else
rv = connect(sockfd, addr, addrlen);
#endif
} else {
rv = connect(sockfd, addr, addrlen);
}
} }
if (rv < 0) { if (rv < 0) {
err = ares_socket_deref_error(SOCKERRNO); err = ares_socket_deref_error(SOCKERRNO);
} else { } else {
break; err = ARES_CONN_ERR_SUCCESS;
}
if (err != ARES_CONN_ERR_WOULDBLOCK && err != ARES_CONN_ERR_INTERRUPT) {
return ARES_ECONNREFUSED;
} }
} while (err == ARES_CONN_ERR_INTERRUPT); } while (err == ARES_CONN_ERR_INTERRUPT);
return ARES_SUCCESS; return err;
} }
void ares_close_socket(ares_channel_t *channel, ares_socket_t s) void ares_socket_close(ares_channel_t *channel, ares_socket_t s)
{ {
if (s == ARES_SOCKET_BAD) { if (s == ARES_SOCKET_BAD) {
return; return;

@ -0,0 +1,79 @@
/* MIT License
*
* Copyright (c) 2024 Brad House
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* SPDX-License-Identifier: MIT
*/
#ifndef __ARES_SOCKET_H
#define __ARES_SOCKET_H
/*! Socket errors */
typedef enum {
ARES_CONN_ERR_SUCCESS = 0, /*!< Success */
ARES_CONN_ERR_WOULDBLOCK = 1, /*!< Operation would block */
ARES_CONN_ERR_CONNCLOSED = 2, /*!< Connection closed (gracefully) */
ARES_CONN_ERR_CONNABORTED = 3, /*!< Connection Aborted */
ARES_CONN_ERR_CONNRESET = 4, /*!< Connection Reset */
ARES_CONN_ERR_CONNREFUSED = 5, /*!< Connection Refused */
ARES_CONN_ERR_CONNTIMEDOUT = 6, /*!< Connection Timed Out */
ARES_CONN_ERR_HOSTDOWN = 7, /*!< Host Down */
ARES_CONN_ERR_HOSTUNREACH = 8, /*!< Host Unreachable */
ARES_CONN_ERR_NETDOWN = 9, /*!< Network Down */
ARES_CONN_ERR_NETUNREACH = 10, /*!< Network Unreachable */
ARES_CONN_ERR_INTERRUPT = 11, /*!< Call interrupted by signal, repeat */
ARES_CONN_ERR_AFNOSUPPORT = 12, /*!< Address family not supported */
ARES_CONN_ERR_BADADDR = 13, /*!< Bad Address / Unavailable */
ARES_CONN_ERR_NOMEM = 14, /*!< Out of memory */
ARES_CONN_ERR_INVALID = 15, /*!< Invalid Usage */
ARES_CONN_ERR_TOOLARGE = 16, /*!< Request size too large */
ARES_CONN_ERR_NOTIMP = 17, /*!< Not implemented */
ARES_CONN_ERR_FAILURE = 99 /*!< Generic failure */
} ares_conn_err_t;
ares_bool_t ares_socket_tfo_supported(const ares_channel_t *channel);
ares_bool_t ares_sockaddr_addr_eq(const struct sockaddr *sa,
const struct ares_addr *aa);
ares_status_t ares_socket_configure(ares_channel_t *channel, int family,
ares_bool_t is_tcp, ares_socket_t fd);
ares_conn_err_t ares_socket_enable_tfo(const ares_channel_t *channel,
ares_socket_t fd);
ares_conn_err_t ares_socket_open(ares_socket_t *sock, ares_channel_t *channel,
int af, int type, int protocol);
ares_bool_t ares_socket_try_again(int errnum);
void ares_socket_close(ares_channel_t *channel, ares_socket_t s);
ares_conn_err_t ares_socket_connect(ares_channel_t *channel,
ares_socket_t sockfd, ares_bool_t is_tfo,
const struct sockaddr *addr,
ares_socklen_t addrlen);
ares_bool_t ares_sockaddr_to_ares_addr(struct ares_addr *ares_addr,
unsigned short *port,
const struct sockaddr *sockaddr);
ares_conn_err_t ares_socket_write(ares_channel_t *channel, ares_socket_t fd,
const void *data, size_t len,
size_t *written);
ares_conn_err_t ares_socket_write_tfo(ares_channel_t *channel, ares_socket_t fd,
const void *data, size_t len,
size_t *written, struct sockaddr *sa,
ares_socklen_t salen);
#endif

@ -362,23 +362,24 @@ static int find_src_addr(ares_channel_t *channel, const struct sockaddr *addr,
} }
err = err =
ares_open_socket(&sock, channel, addr->sa_family, SOCK_DGRAM, IPPROTO_UDP); ares_socket_open(&sock, channel, addr->sa_family, SOCK_DGRAM, IPPROTO_UDP);
if (err == ARES_CONN_ERR_AFNOSUPPORT) { if (err == ARES_CONN_ERR_AFNOSUPPORT) {
return 0; return 0;
} else if (err != ARES_CONN_ERR_SUCCESS) { } else if (err != ARES_CONN_ERR_SUCCESS) {
return -1; return -1;
} }
if (ares_connect_socket(channel, sock, addr, len) != ARES_SUCCESS) { err = ares_socket_connect(channel, sock, ARES_FALSE, addr, len);
ares_close_socket(channel, sock); if (err != ARES_CONN_ERR_SUCCESS && err != ARES_CONN_ERR_WOULDBLOCK) {
ares_socket_close(channel, sock);
return 0; return 0;
} }
if (getsockname(sock, src_addr, &len) != 0) { if (getsockname(sock, src_addr, &len) != 0) {
ares_close_socket(channel, sock); ares_socket_close(channel, sock);
return -1; return -1;
} }
ares_close_socket(channel, sock); ares_socket_close(channel, sock);
return 1; return 1;
} }

@ -179,6 +179,28 @@ ares_bool_t ares_htable_strvp_remove(ares_htable_strvp_t *htable,
return ares_htable_remove(htable->hash, key); return ares_htable_remove(htable->hash, key);
} }
void *ares_htable_strvp_claim(ares_htable_strvp_t *htable, const char *key)
{
ares_htable_strvp_bucket_t *bucket = NULL;
void *val;
if (htable == NULL || key == NULL) {
return NULL;
}
bucket = ares_htable_get(htable->hash, key);
if (bucket == NULL) {
return NULL;
}
/* Unassociate value from bucket */
val = bucket->val;
bucket->val = NULL;
ares_htable_strvp_remove(htable, key);
return val;
}
size_t ares_htable_strvp_num_keys(const ares_htable_strvp_t *htable) size_t ares_htable_strvp_num_keys(const ares_htable_strvp_t *htable)
{ {
if (htable == NULL) { if (htable == NULL) {

@ -0,0 +1,186 @@
/* MIT License
*
* Copyright (c) 2024 Brad House
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* SPDX-License-Identifier: MIT
*/
#include "ares_private.h"
#include "ares_htable.h"
#include "ares_htable_vpstr.h"
struct ares_htable_vpstr {
ares_htable_t *hash;
};
typedef struct {
void *key;
char *val;
ares_htable_vpstr_t *parent;
} ares_htable_vpstr_bucket_t;
void ares_htable_vpstr_destroy(ares_htable_vpstr_t *htable)
{
if (htable == NULL) {
return; /* LCOV_EXCL_LINE: DefensiveCoding */
}
ares_htable_destroy(htable->hash);
ares_free(htable);
}
static unsigned int hash_func(const void *key, unsigned int seed)
{
return ares_htable_hash_FNV1a((const unsigned char *)&key, sizeof(key), seed);
}
static const void *bucket_key(const void *bucket)
{
const ares_htable_vpstr_bucket_t *arg = bucket;
return arg->key;
}
static void bucket_free(void *bucket)
{
ares_htable_vpstr_bucket_t *arg = bucket;
ares_free(arg->val);
ares_free(arg);
}
static ares_bool_t key_eq(const void *key1, const void *key2)
{
if (key1 == key2) {
return ARES_TRUE;
}
return ARES_FALSE;
}
ares_htable_vpstr_t *ares_htable_vpstr_create(void)
{
ares_htable_vpstr_t *htable = ares_malloc(sizeof(*htable));
if (htable == NULL) {
goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
}
htable->hash = ares_htable_create(hash_func, bucket_key, bucket_free, key_eq);
if (htable->hash == NULL) {
goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
}
return htable;
/* LCOV_EXCL_START: OutOfMemory */
fail:
if (htable) {
ares_htable_destroy(htable->hash);
ares_free(htable);
}
return NULL;
/* LCOV_EXCL_STOP */
}
ares_bool_t ares_htable_vpstr_insert(ares_htable_vpstr_t *htable, void *key,
const char *val)
{
ares_htable_vpstr_bucket_t *bucket = NULL;
if (htable == NULL) {
goto fail;
}
bucket = ares_malloc(sizeof(*bucket));
if (bucket == NULL) {
goto fail;
}
bucket->parent = htable;
bucket->key = key;
bucket->val = ares_strdup(val);
if (bucket->val == NULL) {
goto fail;
}
if (!ares_htable_insert(htable->hash, bucket)) {
goto fail;
}
return ARES_TRUE;
fail:
if (bucket) {
ares_free(bucket->val);
ares_free(bucket);
}
return ARES_FALSE;
}
ares_bool_t ares_htable_vpstr_get(const ares_htable_vpstr_t *htable,
const void *key, const char **val)
{
ares_htable_vpstr_bucket_t *bucket = NULL;
if (val) {
*val = NULL;
}
if (htable == NULL) {
return ARES_FALSE;
}
bucket = ares_htable_get(htable->hash, key);
if (bucket == NULL) {
return ARES_FALSE;
}
if (val) {
*val = bucket->val;
}
return ARES_TRUE;
}
const char *ares_htable_vpstr_get_direct(const ares_htable_vpstr_t *htable,
const void *key)
{
const char *val = NULL;
ares_htable_vpstr_get(htable, key, &val);
return val;
}
ares_bool_t ares_htable_vpstr_remove(ares_htable_vpstr_t *htable,
const void *key)
{
if (htable == NULL) {
return ARES_FALSE;
}
return ares_htable_remove(htable->hash, key);
}
size_t ares_htable_vpstr_num_keys(const ares_htable_vpstr_t *htable)
{
if (htable == NULL) {
return 0;
}
return ares_htable_num_keys(htable->hash);
}

@ -107,6 +107,16 @@ CARES_EXTERN void *
CARES_EXTERN ares_bool_t ares_htable_strvp_remove(ares_htable_strvp_t *htable, CARES_EXTERN ares_bool_t ares_htable_strvp_remove(ares_htable_strvp_t *htable,
const char *key); const char *key);
/*! Remove the value from the hashtable, and return the value instead of
* calling the val_free passed to ares_htable_strvp_create().
*
* \param[in] htable Initialized hash table
* \param[in] key key to use to search
* \return value in hashtable or NULL on error
*/
CARES_EXTERN void *ares_htable_strvp_claim(ares_htable_strvp_t *htable,
const char *key);
/*! Retrieve the number of keys stored in the hash table /*! Retrieve the number of keys stored in the hash table
* *
* \param[in] htable Initialized hash table * \param[in] htable Initialized hash table

@ -0,0 +1,111 @@
/* MIT License
*
* Copyright (c) 2024 Brad House
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* SPDX-License-Identifier: MIT
*/
#ifndef __ARES__HTABLE_VPSTR_H
#define __ARES__HTABLE_VPSTR_H
/*! \addtogroup ares_htable_vpstr HashTable with void pointer Key and string
* value
*
* This data structure wraps the base ares_htable data structure in order to
* split the key and value data types as void pointer and string, respectively.
*
* Average time complexity:
* - Insert: O(1)
* - Search: O(1)
* - Delete: O(1)
*
* @{
*/
struct ares_htable_vpstr;
/*! Opaque data type for void pointer key, string value hash table
* implementation */
typedef struct ares_htable_vpstr ares_htable_vpstr_t;
/*! Destroy hashtable
*
* \param[in] htable Initialized hashtable
*/
CARES_EXTERN void ares_htable_vpstr_destroy(ares_htable_vpstr_t *htable);
/*! Create void pointer key, string value hash table
*
*/
CARES_EXTERN ares_htable_vpstr_t *ares_htable_vpstr_create(void);
/*! Insert key/value into hash table
*
* \param[in] htable Initialized hash table
* \param[in] key key to associate with value
* \param[in] val value to store (duplicates).
* \return ARES_TRUE on success, ARES_FALSE on failure or out of memory
*/
CARES_EXTERN ares_bool_t ares_htable_vpstr_insert(ares_htable_vpstr_t *htable,
void *key, const char *val);
/*! Retrieve value from hashtable based on key
*
* \param[in] htable Initialized hash table
* \param[in] key key to use to search
* \param[out] val Optional. Pointer to store value.
* \return ARES_TRUE on success, ARES_FALSE on failure
*/
CARES_EXTERN ares_bool_t ares_htable_vpstr_get(
const ares_htable_vpstr_t *htable, const void *key, const char **val);
/*! Retrieve value from hashtable directly as return value. Caveat to this
* function over ares_htable_vpstr_get() is that if a NULL value is stored
* you cannot determine if the key is not found or the value is NULL.
*
* \param[in] htable Initialized hash table
* \param[in] key key to use to search
* \return value associated with key in hashtable or NULL
*/
CARES_EXTERN const char *
ares_htable_vpstr_get_direct(const ares_htable_vpstr_t *htable,
const void *key);
/*! Remove a value from the hashtable by key
*
* \param[in] htable Initialized hash table
* \param[in] key key to use to search
* \return ARES_TRUE if found, ARES_FALSE if not
*/
CARES_EXTERN ares_bool_t ares_htable_vpstr_remove(ares_htable_vpstr_t *htable,
const void *key);
/*! Retrieve the number of keys stored in the hash table
*
* \param[in] htable Initialized hash table
* \return count
*/
CARES_EXTERN size_t
ares_htable_vpstr_num_keys(const ares_htable_vpstr_t *htable);
/*! @} */
#endif /* __ARES__HTABLE_VPSTR_H */
Loading…
Cancel
Save