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;
void ares_process_fds(ares_channel_t *\fIchannel\fP,
const ares_fd_events_t *\fIevents\fP,
size_t \fInevents\fP,
unsigned int \fIflags\fP)
ares_status_t ares_process_fds(ares_channel_t *\fIchannel\fP,
const ares_fd_events_t *\fIevents\fP,
size_t \fInevents\fP,
unsigned int \fIflags\fP)
void ares_process_fd(ares_channel_t *\fIchannel\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
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
handle processing of multiple file descriptors at once, thus skipping repeating
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
* no events, but may have timeouts to process.
* \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,
const ares_fd_events_t *events,
size_t nevents, unsigned int flags);
CARES_EXTERN ares_status_t ares_process_fds(ares_channel_t *channel,
const ares_fd_events_t *events,
size_t nevents, unsigned int flags);
CARES_EXTERN void ares_process_fd(ares_channel_t *channel,
ares_socket_t read_fd,
ares_socket_t write_fd);
CARES_EXTERN void ares_process_fd(ares_channel_t *channel,
ares_socket_t read_fd,
ares_socket_t write_fd);
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,

@ -3,13 +3,10 @@
CSOURCES = ares_addrinfo2hostent.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_cancel.c \
ares_close_sockets.c \
ares_conn.c \
ares_cookie.c \
ares_data.c \
ares_destroy.c \
@ -21,16 +18,20 @@ CSOURCES = ares_addrinfo2hostent.c \
ares_gethostbyaddr.c \
ares_gethostbyname.c \
ares_getnameinfo.c \
ares_hosts_file.c \
ares_init.c \
ares_library_init.c \
ares_metrics.c \
ares_options.c \
ares_platform.c \
ares_parse_into_addrinfo.c \
ares_process.c \
ares_qcache.c \
ares_query.c \
ares_search.c \
ares_send.c \
ares_socket.c \
ares_sortaddrinfo.c \
ares_strerror.c \
ares_sysconfig.c \
ares_sysconfig_files.c \
@ -47,6 +48,7 @@ CSOURCES = ares_addrinfo2hostent.c \
dsa/ares_htable_asvp.c \
dsa/ares_htable_strvp.c \
dsa/ares_htable_szvp.c \
dsa/ares_htable_vpstr.c \
dsa/ares_htable_vpvp.c \
dsa/ares_llist.c \
dsa/ares_slist.c \
@ -83,7 +85,7 @@ CSOURCES = ares_addrinfo2hostent.c \
str/ares_buf.c \
str/ares_str.c \
str/ares_strsplit.c \
util/ares_iface_ips.c \
util/ares_iface_ips.c \
util/ares_threads.c \
util/ares_timeval.c \
util/ares_math.c \
@ -98,6 +100,7 @@ HHEADERS = ares_android.h \
ares_platform.h \
ares_private.h \
ares_setup.h \
ares_socket.h \
dsa/ares_htable.h \
dsa/ares_slist.h \
event/ares_event.h \
@ -107,6 +110,7 @@ HHEADERS = ares_android.h \
include/ares_htable_asvp.h \
include/ares_htable_strvp.h \
include/ares_htable_szvp.h \
include/ares_htable_vpstr.h \
include/ares_htable_vpvp.h \
include/ares_llist.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_close_socket(channel, conn->fd);
ares_socket_close(channel, conn->fd);
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
#define __ARES_CONN_H
struct ares_server;
typedef struct ares_server ares_server_t;
#include "ares_socket.h"
struct ares_conn;
typedef struct ares_conn ares_conn_t;
struct ares_server;
typedef struct ares_server ares_server_t;
typedef enum {
/*! No flags */
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_destroy_servers_state(ares_channel_t *channel);
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_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_status_t ares_open_connection(ares_conn_t **conn_out,
ares_channel_t *channel,
ares_server_t *server, ares_bool_t is_tcp);
ares_conn_err_t ares_conn_write(ares_conn_t *conn, const void *data, size_t len,
size_t *written);
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,
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,
ares_conn_state_flags_t flags);
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,
ares_socklen_t *from_len,
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,
const struct sockaddr *addr,
ares_socklen_t addrlen);
void ares_destroy_server(ares_server_t *server);
void ares_destroy_server(ares_server_t *server);
#endif

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

@ -45,13 +45,14 @@
#include <limits.h>
static void timeadd(ares_timeval_t *now, size_t millisecs);
static void process_write(const ares_channel_t *channel,
ares_socket_t write_fd);
static void process_read(const ares_channel_t *channel, ares_socket_t read_fd,
const ares_timeval_t *now);
static void process_timeouts(ares_channel_t *channel,
const ares_timeval_t *now);
static void timeadd(ares_timeval_t *now, size_t millisecs);
static ares_status_t process_write(ares_channel_t *channel,
ares_socket_t write_fd);
static ares_status_t process_read(ares_channel_t *channel,
ares_socket_t read_fd,
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,
const unsigned char *abuf, size_t alen,
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,
const ares_fd_events_t *events,
size_t nevents, unsigned int flags)
static ares_status_t ares_process_fds_nolock(ares_channel_t *channel,
const ares_fd_events_t *events,
size_t nevents, unsigned int flags)
{
ares_timeval_t now;
size_t i;
ares_timeval_t now;
size_t i;
ares_status_t status = ARES_SUCCESS;
if (channel == NULL || (events == NULL && nevents != 0)) {
return; /* LCOV_EXCL_LINE: DefensiveCoding */
return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
}
ares_tvnow(&now);
/* Process read events */
/* Process write events */
for (i = 0; i < nevents; i++) {
if (events[i].fd == ARES_SOCKET_BAD ||
!(events[i].events & ARES_FD_EVENT_READ)) {
!(events[i].events & ARES_FD_EVENT_WRITE)) {
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++) {
if (events[i].fd == ARES_SOCKET_BAD ||
!(events[i].events & ARES_FD_EVENT_WRITE)) {
!(events[i].events & ARES_FD_EVENT_READ)) {
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)) {
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,
size_t nevents, unsigned int flags)
ares_status_t ares_process_fds(ares_channel_t *channel,
const ares_fd_events_t *events, size_t nevents,
unsigned int flags)
{
ares_status_t status;
if (channel == NULL) {
return;
return ARES_EFORMERR;
}
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);
return status;
}
void ares_process_fd(ares_channel_t *channel, ares_socket_t read_fd,
@ -355,19 +378,16 @@ done:
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_status_t status;
ares_conn_t *conn = ares_conn_from_fd(channel, write_fd);
ares_status_t status;
node = ares_htable_asvp_get_direct(channel->connnode_by_socket, write_fd);
if (node == NULL) {
return;
if (conn == NULL) {
return ARES_SUCCESS;
}
conn = ares_llist_node_val(node);
/* Mark as connected if we got here and TFO Initial not set */
if (!(conn->flags & ARES_CONN_FLAG_TFO_INITIAL)) {
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) {
handle_conn_error(conn, ARES_TRUE, status);
}
return status;
}
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;
}
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;
/* 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;
const unsigned char *data = NULL;
size_t data_len = 0;
ares_status_t status;
/* Tag so we can roll back */
ares_buf_tag(conn->in_buf);
/* 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);
break;
}
/* 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);
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);
if (status != ARES_SUCCESS) {
handle_conn_error(conn, ARES_TRUE, status);
return;
return status;
}
/* Since we processed the answer, clear the tag so space can be reclaimed */
ares_buf_tag_clear(conn->in_buf);
}
return status;
}
static void process_read(const ares_channel_t *channel, ares_socket_t read_fd,
const ares_timeval_t *now)
static ares_status_t process_read(ares_channel_t *channel,
ares_socket_t read_fd,
const ares_timeval_t *now)
{
ares_llist_node_t *node;
ares_conn_t *conn;
ares_conn_t *conn = ares_conn_from_fd(channel, read_fd);
ares_status_t status;
node = ares_htable_asvp_get_direct(channel->connnode_by_socket, read_fd);
if (node == NULL) {
return;
if (conn == NULL) {
return ARES_SUCCESS;
}
conn = ares_llist_node_val(node);
/* 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.
* 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
* a full buffer of 65535 bytes was read. */
if (read_conn_packets(conn) != ARES_SUCCESS) {
return;
status = read_conn_packets(conn);
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. */
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_status_t status = ARES_SUCCESS;
/* 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
@ -588,8 +614,16 @@ static void process_timeouts(ares_channel_t *channel, const ares_timeval_t *now)
conn = query->conn;
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)

@ -25,7 +25,6 @@
* SPDX-License-Identifier: MIT
*/
#include "ares_private.h"
#ifdef HAVE_SYS_UIO_H
# include <sys/uio.h>
#endif
@ -57,25 +56,25 @@
#include <limits.h>
#if defined(__linux__) && defined(TCP_FASTOPEN_CONNECT)
# define TFO_SUPPORTED 1
# define TFO_SUPPORTED ARES_TRUE
# 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_SUPPORTED ARES_TRUE
# 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_SUPPORTED ARES_TRUE
# define TFO_SKIP_CONNECT 0
# define TFO_USE_SENDTO 0
# define TFO_USE_CONNECTX 1
# undef TFO_CLIENT_SOCKOPT
#else
# define TFO_SUPPORTED 0
# define TFO_SUPPORTED ARES_FALSE
#endif
@ -175,6 +174,16 @@ struct iovec {
};
#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)
{
switch (err) {
@ -217,8 +226,8 @@ static ares_conn_err_t ares_socket_deref_error(int err)
return ARES_CONN_ERR_FAILURE;
}
static ares_bool_t same_address(const struct sockaddr *sa,
const struct ares_addr *aa)
ares_bool_t ares_sockaddr_addr_eq(const struct sockaddr *sa,
const struct ares_addr *aa)
{
const void *addr1;
const void *addr2;
@ -247,20 +256,76 @@ static ares_bool_t same_address(const struct sockaddr *sa,
return ARES_FALSE; /* different */
}
void ares_conn_sock_state_cb_update(ares_conn_t *conn,
ares_conn_state_flags_t flags)
ares_conn_err_t ares_socket_write(ares_channel_t *channel, ares_socket_t fd,
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 &&
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);
if (!ares_socket_tfo_supported(channel)) {
return ARES_CONN_ERR_NOTIMP;
}
conn->state_flags &= ~((unsigned int)ARES_CONN_STATE_CBFLAGS);
conn->state_flags |= flags;
#if defined(TFO_USE_SENDTO) && TFO_USE_SENDTO
{
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,
@ -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);
}
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
* 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)
#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 {
struct sockaddr sa;
@ -718,20 +503,18 @@ static ares_status_t configure_socket(ares_conn_t *conn)
struct sockaddr_in6 sa6;
} local;
ares_socklen_t bindlen = 0;
ares_server_t *server = conn->server;
ares_channel_t *channel = server->channel;
ares_socklen_t bindlen = 0;
/* do not set options for user-managed sockets */
if (channel->sock_funcs && channel->sock_funcs->asocket) {
return ARES_SUCCESS;
}
(void)setsocknonblock(conn->fd, 1);
(void)setsocknonblock(fd, 1);
#if defined(FD_CLOEXEC) && !defined(MSDOS)
/* 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 */
}
#endif
@ -740,21 +523,20 @@ static ares_status_t configure_socket(ares_conn_t *conn)
#if defined(SO_NOSIGPIPE)
{
int opt = 1;
(void)setsockopt(conn->fd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&opt,
sizeof(opt));
(void)setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&opt, sizeof(opt));
}
#endif
/* Set the socket's send and receive buffer sizes. */
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,
sizeof(channel->socket_send_buffer_size)) != 0) {
return ARES_ECONNREFUSED; /* LCOV_EXCL_LINE: UntestablePath */
}
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,
sizeof(channel->socket_receive_buffer_size)) != 0) {
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)) {
/* Only root can do this, and usually not fatal if it doesn't work, so
* just continue on. */
(void)setsockopt(conn->fd, SOL_SOCKET, SO_BINDTODEVICE,
channel->local_dev_name, sizeof(channel->local_dev_name));
(void)setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, channel->local_dev_name,
sizeof(channel->local_dev_name));
}
#endif
if (server->addr.family == AF_INET && channel->local_ip4) {
if (family == AF_INET && channel->local_ip4) {
memset(&local.sa4, 0, sizeof(local.sa4));
local.sa4.sin_family = AF_INET;
local.sa4.sin_addr.s_addr = htonl(channel->local_ip4);
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,
sizeof(channel->local_ip6)) != 0) {
/* 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);
}
if (bindlen && bind(conn->fd, &local.sa, bindlen) < 0) {
if (bindlen && bind(fd, &local.sa, bindlen) < 0) {
return ARES_ECONNREFUSED;
}
if (server->addr.family == AF_INET6) {
set_ipv6_v6only(conn->fd, 0);
if (family == AF_INET6) {
set_ipv6_v6only(fd, 0);
}
if (conn->flags & ARES_CONN_FLAG_TCP) {
if (is_tcp) {
int opt = 1;
#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,
* so batching isn't very interesting.
*/
if (setsockopt(conn->fd, IPPROTO_TCP, TCP_NODELAY, (void *)&opt,
sizeof(opt)) != 0) {
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&opt, sizeof(opt)) !=
0) {
return ARES_ECONNREFUSED;
}
#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;
@ -860,209 +633,7 @@ ares_bool_t ares_sockaddr_to_ares_addr(struct ares_addr *ares_addr,
return ARES_FALSE;
}
static ares_status_t ares_conn_connect(ares_conn_t *conn, struct sockaddr *sa,
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,
ares_conn_err_t ares_socket_open(ares_socket_t *sock, ares_channel_t *channel,
int af, int type, int protocol)
{
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;
}
ares_status_t ares_connect_socket(ares_channel_t *channel, ares_socket_t sockfd,
const struct sockaddr *addr,
ares_socklen_t addrlen)
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)
{
int rv;
ares_conn_err_t err;
ares_conn_err_t err = ARES_CONN_ERR_SUCCESS;
#if defined(TFO_SKIP_CONNECT) && TFO_SKIP_CONNECT
if (is_tfo) {
return ARES_CONN_ERR_SUCCESS;
}
#endif
do {
int rv;
if (channel->sock_funcs && channel->sock_funcs->aconnect) {
rv = channel->sock_funcs->aconnect(sockfd, addr, addrlen,
channel->sock_func_cb_data);
} 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) {
err = ares_socket_deref_error(SOCKERRNO);
} else {
break;
}
if (err != ARES_CONN_ERR_WOULDBLOCK && err != ARES_CONN_ERR_INTERRUPT) {
return ARES_ECONNREFUSED;
err = ARES_CONN_ERR_SUCCESS;
}
} 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) {
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 =
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) {
return 0;
} else if (err != ARES_CONN_ERR_SUCCESS) {
return -1;
}
if (ares_connect_socket(channel, sock, addr, len) != ARES_SUCCESS) {
ares_close_socket(channel, sock);
err = ares_socket_connect(channel, sock, ARES_FALSE, addr, len);
if (err != ARES_CONN_ERR_SUCCESS && err != ARES_CONN_ERR_WOULDBLOCK) {
ares_socket_close(channel, sock);
return 0;
}
if (getsockname(sock, src_addr, &len) != 0) {
ares_close_socket(channel, sock);
ares_socket_close(channel, sock);
return -1;
}
ares_close_socket(channel, sock);
ares_socket_close(channel, sock);
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);
}
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)
{
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,
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
*
* \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