blob: ff8bd5720d1da0bb100dfe63fc27df3567a6282a [file] [log] [blame]
/* 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_setup.h"
#ifdef HAVE_SYS_UIO_H
# include <sys/uio.h>
#endif
#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif
#ifdef HAVE_NETINET_TCP_H
# include <netinet/tcp.h>
#endif
#ifdef HAVE_NETDB_H
# include <netdb.h>
#endif
#ifdef HAVE_ARPA_INET_H
# include <arpa/inet.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
#endif
#ifdef NETWARE
# include <sys/filio.h>
#endif
#include <assert.h>
#include <fcntl.h>
#include <limits.h>
#include "ares.h"
#include "ares_private.h"
ares_ssize_t ares__socket_recvfrom(ares_channel_t *channel, ares_socket_t s,
void *data, size_t data_len, int flags,
struct sockaddr *from,
ares_socklen_t *from_len)
{
if (channel->sock_funcs && channel->sock_funcs->arecvfrom) {
return channel->sock_funcs->arecvfrom(s, data, data_len, flags, from,
from_len, channel->sock_func_cb_data);
}
#ifdef HAVE_RECVFROM
return (ares_ssize_t)recvfrom(s, data, (RECVFROM_TYPE_ARG3)data_len, flags,
from, from_len);
#else
return sread(s, data, data_len);
#endif
}
ares_ssize_t ares__socket_recv(ares_channel_t *channel, ares_socket_t s,
void *data, size_t data_len)
{
if (channel->sock_funcs && channel->sock_funcs->arecvfrom) {
return channel->sock_funcs->arecvfrom(s, data, data_len, 0, 0, 0,
channel->sock_func_cb_data);
}
/* sread() is a wrapper for read() or recv() depending on the system */
return sread(s, data, data_len);
}
/*
* setsocknonblock sets the given socket to either blocking or non-blocking
* mode based on the 'nonblock' boolean argument. This function is highly
* portable.
*/
static int setsocknonblock(ares_socket_t sockfd, /* operate on this */
int nonblock /* TRUE or FALSE */)
{
#if defined(USE_BLOCKING_SOCKETS)
return 0; /* returns success */
#elif defined(HAVE_FCNTL_O_NONBLOCK)
/* most recent unix versions */
int flags;
flags = fcntl(sockfd, F_GETFL, 0);
if (nonblock) {
return fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
} else {
return fcntl(sockfd, F_SETFL, flags & (~O_NONBLOCK)); /* LCOV_EXCL_LINE */
}
#elif defined(HAVE_IOCTL_FIONBIO)
/* older unix versions */
int flags = nonblock ? 1 : 0;
return ioctl(sockfd, FIONBIO, &flags);
#elif defined(HAVE_IOCTLSOCKET_FIONBIO)
# ifdef WATT32
char flags = nonblock ? 1 : 0;
# else
/* Windows */
unsigned long flags = nonblock ? 1UL : 0UL;
# endif
return ioctlsocket(sockfd, (long)FIONBIO, &flags);
#elif defined(HAVE_IOCTLSOCKET_CAMEL_FIONBIO)
/* Amiga */
long flags = nonblock ? 1L : 0L;
return IoctlSocket(sockfd, FIONBIO, flags);
#elif defined(HAVE_SETSOCKOPT_SO_NONBLOCK)
/* BeOS */
long b = nonblock ? 1L : 0L;
return setsockopt(sockfd, SOL_SOCKET, SO_NONBLOCK, &b, sizeof(b));
#else
# error "no non-blocking method was found/used/set"
#endif
}
#if defined(IPV6_V6ONLY) && defined(WIN32)
/* It makes support for IPv4-mapped IPv6 addresses.
* Linux kernel, NetBSD, FreeBSD and Darwin: default is off;
* Windows Vista and later: default is on;
* DragonFly BSD: acts like off, and dummy setting;
* OpenBSD and earlier Windows: unsupported.
* Linux: controlled by /proc/sys/net/ipv6/bindv6only.
*/
static void set_ipv6_v6only(ares_socket_t sockfd, int on)
{
(void)setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on, sizeof(on));
}
#else
# define set_ipv6_v6only(s, v)
#endif
static int configure_socket(ares_socket_t s, struct server_state *server)
{
union {
struct sockaddr sa;
struct sockaddr_in sa4;
struct sockaddr_in6 sa6;
} local;
ares_socklen_t bindlen = 0;
ares_channel_t *channel = server->channel;
/* do not set options for user-managed sockets */
if (channel->sock_funcs && channel->sock_funcs->asocket) {
return 0;
}
(void)setsocknonblock(s, 1);
#if defined(FD_CLOEXEC) && !defined(MSDOS)
/* Configure the socket fd as close-on-exec. */
if (fcntl(s, F_SETFD, FD_CLOEXEC) == -1) {
return -1; /* LCOV_EXCL_LINE */
}
#endif
/* Set the socket's send and receive buffer sizes. */
if ((channel->socket_send_buffer_size > 0) &&
setsockopt(s, SOL_SOCKET, SO_SNDBUF,
(void *)&channel->socket_send_buffer_size,
sizeof(channel->socket_send_buffer_size)) == -1) {
return -1;
}
if ((channel->socket_receive_buffer_size > 0) &&
setsockopt(s, SOL_SOCKET, SO_RCVBUF,
(void *)&channel->socket_receive_buffer_size,
sizeof(channel->socket_receive_buffer_size)) == -1) {
return -1;
}
#ifdef SO_BINDTODEVICE
if (channel->local_dev_name[0] &&
setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, channel->local_dev_name,
sizeof(channel->local_dev_name))) {
/* Only root can do this, and usually not fatal if it doesn't work, so */
/* just continue on. */
}
#endif
if (server->addr.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 &&
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 */
memset(&local.sa6, 0, sizeof(local.sa6));
local.sa6.sin6_family = AF_INET6;
memcpy(&local.sa6.sin6_addr, channel->local_ip6,
sizeof(channel->local_ip6));
bindlen = sizeof(local.sa6);
}
if (bindlen && bind(s, &local.sa, bindlen) < 0) {
return -1;
}
if (server->addr.family == AF_INET6) {
set_ipv6_v6only(s, 0);
}
return 0;
}
ares_status_t ares__open_connection(ares_channel_t *channel,
struct server_state *server,
ares_bool_t is_tcp)
{
ares_socket_t s;
int opt;
ares_socklen_t salen;
union {
struct sockaddr_in sa4;
struct sockaddr_in6 sa6;
} saddr;
struct sockaddr *sa;
struct server_connection *conn;
ares__llist_node_t *node;
int type = is_tcp ? SOCK_STREAM : SOCK_DGRAM;
switch (server->addr.family) {
case AF_INET:
sa = (void *)&saddr.sa4;
salen = sizeof(saddr.sa4);
memset(sa, 0, (size_t)salen);
saddr.sa4.sin_family = AF_INET;
saddr.sa4.sin_port = htons(is_tcp ? server->tcp_port : server->udp_port);
memcpy(&saddr.sa4.sin_addr, &server->addr.addr.addr4,
sizeof(saddr.sa4.sin_addr));
break;
case AF_INET6:
sa = (void *)&saddr.sa6;
salen = sizeof(saddr.sa6);
memset(sa, 0, (size_t)salen);
saddr.sa6.sin6_family = AF_INET6;
saddr.sa6.sin6_port = htons(is_tcp ? server->tcp_port : server->udp_port);
memcpy(&saddr.sa6.sin6_addr, &server->addr.addr.addr6,
sizeof(saddr.sa6.sin6_addr));
#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID
saddr.sa6.sin6_scope_id = server->ll_scope;
#endif
break;
default:
return ARES_EBADFAMILY; /* LCOV_EXCL_LINE */
}
/* Acquire a socket. */
s = ares__open_socket(channel, server->addr.family, type, 0);
if (s == ARES_SOCKET_BAD) {
return ARES_ECONNREFUSED;
}
/* Configure it. */
if (configure_socket(s, server) < 0) {
ares__close_socket(channel, s);
return ARES_ECONNREFUSED;
}
#ifdef TCP_NODELAY
if (is_tcp) {
/*
* Disable the Nagle algorithm (only relevant for TCP sockets, and thus not
* in configure_socket). In general, in DNS lookups we're pretty much
* interested in firing off a single request and then waiting for a reply,
* so batching isn't very interesting.
*/
opt = 1;
if ((!channel->sock_funcs || !channel->sock_funcs->asocket) &&
setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void *)&opt, sizeof(opt)) ==
-1) {
ares__close_socket(channel, s);
return ARES_ECONNREFUSED;
}
}
#endif
if (channel->sock_config_cb) {
int err = channel->sock_config_cb(s, type, channel->sock_config_cb_data);
if (err < 0) {
ares__close_socket(channel, s);
return ARES_ECONNREFUSED;
}
}
/* Connect to the server. */
if (ares__connect_socket(channel, s, sa, salen) == -1) {
int err = SOCKERRNO;
if (err != EINPROGRESS && err != EWOULDBLOCK) {
ares__close_socket(channel, s);
return ARES_ECONNREFUSED;
}
}
if (channel->sock_create_cb) {
int err = channel->sock_create_cb(s, type, channel->sock_create_cb_data);
if (err < 0) {
ares__close_socket(channel, s);
return ARES_ECONNREFUSED;
}
}
conn = ares_malloc(sizeof(*conn));
if (conn == NULL) {
ares__close_socket(channel, s);
return ARES_ENOMEM;
}
memset(conn, 0, sizeof(*conn));
conn->fd = s;
conn->server = server;
conn->queries_to_conn = ares__llist_create(NULL);
conn->is_tcp = is_tcp;
if (conn->queries_to_conn == NULL) {
ares__close_socket(channel, s);
ares_free(conn);
return ARES_ENOMEM;
}
/* 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) {
ares__close_socket(channel, s);
ares__llist_destroy(conn->queries_to_conn);
ares_free(conn);
return ARES_ENOMEM;
}
/* Register globally to quickly map event on file descriptor to connection
* node object */
if (!ares__htable_asvp_insert(channel->connnode_by_socket, s, node)) {
ares__close_socket(channel, s);
ares__llist_destroy(conn->queries_to_conn);
ares__llist_node_claim(node);
ares_free(conn);
return ARES_ENOMEM;
}
SOCK_STATE_CALLBACK(channel, s, 1, 0);
if (is_tcp) {
server->tcp_conn = conn;
}
return ARES_SUCCESS;
}
ares_socket_t ares__open_socket(ares_channel_t *channel, int af, int type,
int protocol)
{
if (channel->sock_funcs && channel->sock_funcs->asocket) {
return channel->sock_funcs->asocket(af, type, protocol,
channel->sock_func_cb_data);
}
return socket(af, type, protocol);
}
int ares__connect_socket(ares_channel_t *channel, ares_socket_t sockfd,
const struct sockaddr *addr, ares_socklen_t addrlen)
{
if (channel->sock_funcs && channel->sock_funcs->aconnect) {
return channel->sock_funcs->aconnect(sockfd, addr, addrlen,
channel->sock_func_cb_data);
}
return connect(sockfd, addr, addrlen);
}
void ares__close_socket(ares_channel_t *channel, ares_socket_t s)
{
if (s == ARES_SOCKET_BAD) {
return;
}
if (channel->sock_funcs && channel->sock_funcs->aclose) {
channel->sock_funcs->aclose(s, channel->sock_func_cb_data);
} else {
sclose(s);
}
}
#ifndef HAVE_WRITEV
/* Structure for scatter/gather I/O. */
struct iovec {
void *iov_base; /* Pointer to data. */
size_t iov_len; /* Length of data. */
};
#endif
ares_ssize_t ares__socket_write(ares_channel_t *channel, ares_socket_t s,
const void *data, size_t len)
{
if (channel->sock_funcs && channel->sock_funcs->asendv) {
struct iovec vec;
vec.iov_base = (void *)((size_t)data); /* Cast off const */
vec.iov_len = len;
return channel->sock_funcs->asendv(s, &vec, 1, channel->sock_func_cb_data);
}
return swrite(s, data, len);
}
void ares_set_socket_callback(ares_channel_t *channel,
ares_sock_create_callback cb, void *data)
{
channel->sock_create_cb = cb;
channel->sock_create_cb_data = data;
}
void ares_set_socket_configure_callback(ares_channel_t *channel,
ares_sock_config_callback cb,
void *data)
{
channel->sock_config_cb = cb;
channel->sock_config_cb_data = data;
}
void ares_set_socket_functions(ares_channel_t *channel,
const struct ares_socket_functions *funcs,
void *data)
{
channel->sock_funcs = funcs;
channel->sock_func_cb_data = data;
}