/* 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" #ifdef HAVE_SYS_UIO_H # include #endif #ifdef HAVE_NETINET_IN_H # include #endif #ifdef HAVE_NETINET_TCP_H # include #endif #ifdef HAVE_NETDB_H # include #endif #ifdef HAVE_ARPA_INET_H # include #endif #ifdef HAVE_STRINGS_H # include #endif #ifdef HAVE_SYS_IOCTL_H # include #endif #ifdef NETWARE # include #endif #include #include #include static ares_conn_err_t ares_socket_deref_error(int err) { switch (err) { #if defined(EWOULDBLOCK) case EWOULDBLOCK: return ARES_CONN_ERR_WOULDBLOCK; #endif #if defined(EAGAIN) && (!defined(EWOULDBLOCK) || EAGAIN != EWOULDBLOCK) case EAGAIN: return ARES_CONN_ERR_WOULDBLOCK; #endif case EINPROGRESS: return ARES_CONN_ERR_WOULDBLOCK; case ENETDOWN: return ARES_CONN_ERR_NETDOWN; case ENETUNREACH: return ARES_CONN_ERR_NETUNREACH; case ECONNABORTED: return ARES_CONN_ERR_CONNABORTED; case ECONNRESET: return ARES_CONN_ERR_CONNRESET; case ECONNREFUSED: return ARES_CONN_ERR_CONNREFUSED; case ETIMEDOUT: return ARES_CONN_ERR_CONNTIMEDOUT; case EHOSTDOWN: return ARES_CONN_ERR_HOSTDOWN; case EHOSTUNREACH: return ARES_CONN_ERR_HOSTUNREACH; case EINTR: return ARES_CONN_ERR_INTERRUPT; case EAFNOSUPPORT: return ARES_CONN_ERR_AFNOSUPPORT; case EADDRNOTAVAIL: return ARES_CONN_ERR_BADADDR; default: break; } return ARES_CONN_ERR_FAILURE; } ares_bool_t ares_sockaddr_addr_eq(const struct sockaddr *sa, const struct ares_addr *aa) { const void *addr1; const void *addr2; if (sa->sa_family == aa->family) { switch (aa->family) { case AF_INET: addr1 = &aa->addr.addr4; addr2 = &(CARES_INADDR_CAST(const struct sockaddr_in *, sa))->sin_addr; if (memcmp(addr1, addr2, sizeof(aa->addr.addr4)) == 0) { return ARES_TRUE; /* match */ } break; case AF_INET6: addr1 = &aa->addr.addr6; addr2 = &(CARES_INADDR_CAST(const struct sockaddr_in6 *, sa))->sin6_addr; if (memcmp(addr1, addr2, sizeof(aa->addr.addr6)) == 0) { return ARES_TRUE; /* match */ } break; default: break; /* LCOV_EXCL_LINE */ } } return ARES_FALSE; /* different */ } ares_conn_err_t ares_socket_write(ares_channel_t *channel, ares_socket_t fd, const void *data, size_t len, size_t *written, const struct sockaddr *sa, ares_socklen_t salen) { int flags = 0; ares_ssize_t rv; ares_conn_err_t err = ARES_CONN_ERR_SUCCESS; #ifdef HAVE_MSG_NOSIGNAL flags |= MSG_NOSIGNAL; #endif rv = channel->sock_funcs.asendto(fd, data, len, flags, sa, salen, channel->sock_func_cb_data); if (rv <= 0) { err = ares_socket_deref_error(SOCKERRNO); } else { *written = (size_t)rv; } return err; } ares_conn_err_t ares_socket_recv(ares_channel_t *channel, ares_socket_t s, ares_bool_t is_tcp, void *data, size_t data_len, size_t *read_bytes) { ares_ssize_t rv; *read_bytes = 0; rv = channel->sock_funcs.arecvfrom(s, data, data_len, 0, NULL, 0, channel->sock_func_cb_data); if (rv > 0) { *read_bytes = (size_t)rv; return ARES_CONN_ERR_SUCCESS; } if (rv == 0) { /* UDP allows 0-byte packets and is connectionless, so this is success */ if (!is_tcp) { return ARES_CONN_ERR_SUCCESS; } else { return ARES_CONN_ERR_CONNCLOSED; } } /* If we're here, rv<0 */ return ares_socket_deref_error(SOCKERRNO); } ares_conn_err_t ares_socket_recvfrom(ares_channel_t *channel, ares_socket_t s, ares_bool_t is_tcp, void *data, size_t data_len, int flags, struct sockaddr *from, ares_socklen_t *from_len, size_t *read_bytes) { ares_ssize_t rv; rv = channel->sock_funcs.arecvfrom(s, data, data_len, flags, from, from_len, channel->sock_func_cb_data); if (rv > 0) { *read_bytes = (size_t)rv; return ARES_CONN_ERR_SUCCESS; } if (rv == 0) { /* UDP allows 0-byte packets and is connectionless, so this is success */ if (!is_tcp) { return ARES_CONN_ERR_SUCCESS; } else { return ARES_CONN_ERR_CONNCLOSED; } } /* If we're here, rv<0 */ return ares_socket_deref_error(SOCKERRNO); } ares_conn_err_t ares_socket_enable_tfo(const ares_channel_t *channel, ares_socket_t fd) { ares_bool_t opt = ARES_TRUE; if (channel->sock_funcs.asetsockopt(fd, ARES_SOCKET_OPT_TCP_FASTOPEN, (void *)&opt, sizeof(opt), channel->sock_func_cb_data) != 0) { return ARES_CONN_ERR_NOTIMP; } 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; struct sockaddr_in sa4; struct sockaddr_in6 sa6; } local; ares_socklen_t bindlen = 0; int rv; unsigned int bind_flags = 0; /* Set the socket's send and receive buffer sizes. */ if (channel->socket_send_buffer_size > 0) { rv = channel->sock_funcs.asetsockopt( fd, ARES_SOCKET_OPT_SENDBUF_SIZE, (void *)&channel->socket_send_buffer_size, sizeof(channel->socket_send_buffer_size), channel->sock_func_cb_data); if (rv != 0 && SOCKERRNO != ENOSYS) { return ARES_ECONNREFUSED; /* LCOV_EXCL_LINE: UntestablePath */ } } if (channel->socket_receive_buffer_size > 0) { rv = channel->sock_funcs.asetsockopt( fd, ARES_SOCKET_OPT_RECVBUF_SIZE, (void *)&channel->socket_receive_buffer_size, sizeof(channel->socket_receive_buffer_size), channel->sock_func_cb_data); if (rv != 0 && SOCKERRNO != ENOSYS) { return ARES_ECONNREFUSED; /* LCOV_EXCL_LINE: UntestablePath */ } } /* Bind to network interface if configured */ if (ares_strlen(channel->local_dev_name)) { /* Prior versions silently ignored failure, so we need to maintain that * compatibility */ (void)channel->sock_funcs.asetsockopt( fd, ARES_SOCKET_OPT_BIND_DEVICE, channel->local_dev_name, (ares_socklen_t)ares_strlen(channel->local_dev_name), channel->sock_func_cb_data); } /* Bind to ip address if configured */ 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 (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 */ 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 && channel->sock_funcs.abind != NULL) { bind_flags |= ARES_SOCKET_BIND_CLIENT; if (is_tcp) { bind_flags |= ARES_SOCKET_BIND_TCP; } if (channel->sock_funcs.abind(fd, bind_flags, &local.sa, bindlen, channel->sock_func_cb_data) != 0) { return ARES_ECONNREFUSED; } } return ARES_SUCCESS; } ares_bool_t ares_sockaddr_to_ares_addr(struct ares_addr *ares_addr, unsigned short *port, const struct sockaddr *sockaddr) { if (sockaddr->sa_family == AF_INET) { /* NOTE: memcpy sockaddr_in due to alignment issues found by UBSAN due to * dnsinfo packing on MacOS */ struct sockaddr_in sockaddr_in; memcpy(&sockaddr_in, sockaddr, sizeof(sockaddr_in)); ares_addr->family = AF_INET; memcpy(&ares_addr->addr.addr4, &(sockaddr_in.sin_addr), sizeof(ares_addr->addr.addr4)); if (port) { *port = ntohs(sockaddr_in.sin_port); } return ARES_TRUE; } if (sockaddr->sa_family == AF_INET6) { /* NOTE: memcpy sockaddr_in6 due to alignment issues found by UBSAN due to * dnsinfo packing on MacOS */ struct sockaddr_in6 sockaddr_in6; memcpy(&sockaddr_in6, sockaddr, sizeof(sockaddr_in6)); ares_addr->family = AF_INET6; memcpy(&ares_addr->addr.addr6, &(sockaddr_in6.sin6_addr), sizeof(ares_addr->addr.addr6)); if (port) { *port = ntohs(sockaddr_in6.sin6_port); } return ARES_TRUE; } return ARES_FALSE; } 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; *sock = ARES_SOCKET_BAD; s = channel->sock_funcs.asocket(af, type, protocol, channel->sock_func_cb_data); if (s == ARES_SOCKET_BAD) { return ares_socket_deref_error(SOCKERRNO); } *sock = s; return ARES_CONN_ERR_SUCCESS; } 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_conn_err_t err = ARES_CONN_ERR_SUCCESS; unsigned int flags = 0; if (is_tfo) { flags |= ARES_SOCKET_CONN_TCP_FASTOPEN; } do { int rv; rv = channel->sock_funcs.aconnect(sockfd, addr, addrlen, flags, channel->sock_func_cb_data); if (rv < 0) { err = ares_socket_deref_error(SOCKERRNO); } else { err = ARES_CONN_ERR_SUCCESS; } } while (err == ARES_CONN_ERR_INTERRUPT); return err; } void ares_socket_close(ares_channel_t *channel, ares_socket_t s) { if (channel == NULL || s == ARES_SOCKET_BAD) { return; } channel->sock_funcs.aclose(s, channel->sock_func_cb_data); } void ares_set_socket_callback(ares_channel_t *channel, ares_sock_create_callback cb, void *data) { if (channel == NULL) { return; } 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) { if (channel == NULL || channel->optmask & ARES_OPT_EVENT_THREAD) { return; } channel->sock_config_cb = cb; channel->sock_config_cb_data = data; } void ares_set_pending_write_cb(ares_channel_t *channel, ares_pending_write_cb callback, void *user_data) { if (channel == NULL || channel->optmask & ARES_OPT_EVENT_THREAD) { return; } channel->notify_pending_write_cb = callback; channel->notify_pending_write_cb_data = user_data; }