| /* 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 <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> |
| |
| 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; |
| } |