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
parent
3b80588384
commit
e0ef6a8a7a
15 changed files with 1199 additions and 676 deletions
@ -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); |
||||
} |
||||
|
@ -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 |
@ -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); |
||||
} |
@ -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…
Reference in new issue