diff --git a/.cirrus.yml b/.cirrus.yml index 59174540..f0c81ff2 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -158,6 +158,9 @@ task: # pkg upgrade -y && \ pkg install -y bash cmake ninja googletest pkgconf llvm gmake ln -sf /usr/local/bin/bash /bin/bash + # Enable TCP FastOpen + sysctl net.inet.tcp.fastopen.server_enable=1 + sysctl net.inet.tcp.fastopen.client_enable=1 case "${BUILD_TYPE}" in autotools) pkg install -y autoconf automake libtool diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 946875e1..9016d690 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -23,6 +23,8 @@ jobs: run: brew install cmake googletest llvm autoconf automake libtool make ninja - name: Checkout c-ares uses: actions/checkout@v4 + - name: "Make sure TCP FastOpen is enabled" + run: sudo sysctl net.inet.tcp.fastopen=3 - name: "CMake: build and test c-ares" env: BUILD_TYPE: CMAKE @@ -72,3 +74,12 @@ jobs: run: | ./ci/build.sh ./ci/test.sh + - name: "CMake: No TCP FastOpen" + env: + BUILD_TYPE: CMAKE + CMAKE_TEST_FLAGS: "-DCARES_BUILD_TESTS=ON" + TEST_DEBUGGER: lldb + run: | + sudo sysctl net.inet.tcp.fastopen=0 + ./ci/build.sh + ./ci/test.sh diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index a527b093..898fd477 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -26,6 +26,9 @@ jobs: version: 1.0 - name: Checkout c-ares uses: actions/checkout@v4 + - name: "Make sure TCP FastOpen is enabled" + run: | + sudo sysctl -w net.ipv4.tcp_fastopen=3 - name: "CMake: build and test c-ares" env: BUILD_TYPE: CMAKE @@ -84,7 +87,17 @@ jobs: run: | ./ci/build.sh ./ci/test.sh - - name: "Valgrind: build and test c-ares" + - name: "CMake: Static Analyzer: build c-ares" + env: + BUILD_TYPE: "analyze" + CC: "clang" + SCAN_WRAP: "scan-build -v --status-bugs" + CMAKE_TEST_FLAGS: "-DCARES_BUILD_TESTS=OFF" + TEST_DEBUGGER: "lldb" + run: | + ./ci/build.sh + ./ci/test.sh + - name: "Valgrind: build and test c-ares (no TCP FastOpen)" env: BUILD_TYPE: "valgrind" TEST_WRAP: "valgrind --leak-check=full" @@ -92,15 +105,15 @@ jobs: CMAKE_TEST_FLAGS: "-DCARES_BUILD_TESTS=ON" TEST_DEBUGGER: none run: | + sudo sysctl -w net.ipv4.tcp_fastopen=0 ./ci/build.sh ./ci/test.sh - - name: "CMake: Static Analyzer: build c-ares" + - name: "CMake: No TCP FastOpen" env: - BUILD_TYPE: "analyze" - CC: "clang" - SCAN_WRAP: "scan-build -v --status-bugs" - CMAKE_TEST_FLAGS: "-DCARES_BUILD_TESTS=OFF" - TEST_DEBUGGER: "lldb" + BUILD_TYPE: CMAKE + CMAKE_TEST_FLAGS: "-DCARES_BUILD_TESTS=ON" + TEST_DEBUGGER: gdb run: | + sudo sysctl -w net.ipv4.tcp_fastopen=0 ./ci/build.sh ./ci/test.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 71853797..84186824 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -410,6 +410,7 @@ CHECK_STRUCT_HAS_MEMBER("struct sockaddr_in6" sin6_scope_id "${CMAKE_EXTRA_INCLU CHECK_SYMBOL_EXISTS (closesocket "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_CLOSESOCKET) CHECK_SYMBOL_EXISTS (CloseSocket "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_CLOSESOCKET_CAMEL) CHECK_SYMBOL_EXISTS (connect "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_CONNECT) +CHECK_SYMBOL_EXISTS (connectx "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_CONNECTX) CHECK_SYMBOL_EXISTS (fcntl "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_FCNTL) CHECK_SYMBOL_EXISTS (freeaddrinfo "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_FREEADDRINFO) CHECK_SYMBOL_EXISTS (getaddrinfo "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_GETADDRINFO) diff --git a/configure.ac b/configure.ac index 7abf80f6..859b410f 100644 --- a/configure.ac +++ b/configure.ac @@ -538,6 +538,7 @@ AC_CHECK_DECL(send, [AC_DEFINE([HAVE_SEND], 1, [Define t AC_CHECK_DECL(getnameinfo, [AC_DEFINE([HAVE_GETNAMEINFO], 1, [Define to 1 if you have `getnameinfo`] )], [], $cares_all_includes) AC_CHECK_DECL(gethostname, [AC_DEFINE([HAVE_GETHOSTNAME], 1, [Define to 1 if you have `gethostname`] )], [], $cares_all_includes) AC_CHECK_DECL(connect, [AC_DEFINE([HAVE_CONNECT], 1, [Define to 1 if you have `connect`] )], [], $cares_all_includes) +AC_CHECK_DECL(connectx, [AC_DEFINE([HAVE_CONNECTX], 1, [Define to 1 if you have `connectx`] )], [], $cares_all_includes) AC_CHECK_DECL(closesocket, [AC_DEFINE([HAVE_CLOSESOCKET], 1, [Define to 1 if you have `closesocket`] )], [], $cares_all_includes) AC_CHECK_DECL(CloseSocket, [AC_DEFINE([HAVE_CLOSESOCKET_CAMEL], 1, [Define to 1 if you have `CloseSocket`] )], [], $cares_all_includes) AC_CHECK_DECL(fcntl, [AC_DEFINE([HAVE_FCNTL], 1, [Define to 1 if you have `fcntl`] )], [], $cares_all_includes) diff --git a/src/lib/ares__socket.c b/src/lib/ares__socket.c index cc5b8e94..19d71789 100644 --- a/src/lib/ares__socket.c +++ b/src/lib/ares__socket.c @@ -56,6 +56,78 @@ #include #include +#if defined(__linux__) && defined(MSG_FASTOPEN) +# define TFO_SUPPORTED 1 +# 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_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_SKIP_CONNECT 0 +# define TFO_USE_SENDTO 0 +# define TFO_USE_CONNECTX 1 +# undef TFO_CLIENT_SOCKOPT +#else +# define TFO_SUPPORTED 0 +#endif + + +#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 + + +/* Return 1 if the specified error number describes a readiness error, or 0 + * otherwise. This is mostly for HP-UX, which could return EAGAIN or + * EWOULDBLOCK. See this man page + * + * http://devrsrc1.external.hp.com/STKS/cgi-bin/man2html? + * manpage=/usr/share/man/man2.Z/send.2 + */ +ares_bool_t ares__socket_try_again(int errnum) +{ +#if !defined EWOULDBLOCK && !defined EAGAIN +# error "Neither EWOULDBLOCK nor EAGAIN defined" +#endif + +#ifdef EWOULDBLOCK + if (errnum == EWOULDBLOCK) { + return ARES_TRUE; + } +#endif + +#if defined EAGAIN && EAGAIN != EWOULDBLOCK + if (errnum == EAGAIN) { + return ARES_TRUE; + } +#endif + + return ARES_FALSE; +} + +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); + } + + return (ares_ssize_t)recv((RECV_TYPE_ARG1)s, (RECV_TYPE_ARG2)data, + (RECV_TYPE_ARG3)data_len, (RECV_TYPE_ARG4)(0)); +} + 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, @@ -70,20 +142,144 @@ ares_ssize_t ares__socket_recvfrom(ares_channel_t *channel, ares_socket_t s, return (ares_ssize_t)recvfrom(s, data, (RECVFROM_TYPE_ARG3)data_len, flags, from, from_len); #else - return sread(s, data, data_len); + return ares__socket_recv(channel, 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) +/* 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) { - 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); + ares_server_t *server = conn->server; + unsigned short port = + conn->flags & ARES_CONN_FLAG_TCP ? server->tcp_port : server->udp_port; + + switch (server->addr.family) { + case AF_INET: + { + struct sockaddr_in *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: + { + struct sockaddr_in6 *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; } - /* sread() is a wrapper for read() or recv() depending on the system */ - return sread(s, data, data_len); + 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_ssize_t ares__conn_write(ares_conn_t *conn, const void *data, size_t len) +{ + ares_channel_t *channel = conn->server->channel; + int flags = 0; + +#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; + return channel->sock_funcs->asendv(conn->fd, &vec, 1, + channel->sock_func_cb_data); + } + + if (conn->flags & ARES_CONN_FLAG_TFO_INITIAL) { + conn->flags &= ~((unsigned int)ARES_CONN_FLAG_TFO_INITIAL); + +#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; + ares_status_t status; + ares_ssize_t rv; + + status = ares__conn_set_sockaddr(conn, sa, &salen); + if (status != ARES_SUCCESS) { + return status; + } + + 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 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_TRUE); + return rv; + } +#endif + } + + return (ares_ssize_t)send((SEND_TYPE_ARG1)conn->fd, (SEND_TYPE_ARG2)data, + (SEND_TYPE_ARG3)len, (SEND_TYPE_ARG4)flags); } /* @@ -192,22 +388,6 @@ static ares_status_t configure_socket(ares_conn_t *conn) } #endif -#ifdef TCP_NODELAY - if (conn->flags & ARES_CONN_FLAG_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. - */ - int opt = 1; - if (setsockopt(conn->fd, IPPROTO_TCP, TCP_NODELAY, (void *)&opt, - sizeof(opt)) != 0) { - return ARES_ECONNREFUSED; - } - } -#endif - /* Set the socket's send and receive buffer sizes. */ if (channel->socket_send_buffer_size > 0 && setsockopt(conn->fd, SOL_SOCKET, SO_SNDBUF, @@ -225,10 +405,10 @@ static ares_status_t configure_socket(ares_conn_t *conn) #ifdef SO_BINDTODEVICE 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. */ - setsockopt(conn->fd, 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. */ + setsockopt(conn->fd, SOL_SOCKET, SO_BINDTODEVICE, channel->local_dev_name, + sizeof(channel->local_dev_name)); } #endif @@ -256,6 +436,33 @@ static ares_status_t configure_socket(ares_conn_t *conn) set_ipv6_v6only(conn->fd, 0); } + if (conn->flags & ARES_CONN_FLAG_TCP) { + int opt = 1; + +#ifdef TCP_NODELAY + /* + * 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. + */ + if (setsockopt(conn->fd, IPPROTO_TCP, TCP_NODELAY, (void *)&opt, + sizeof(opt)) != 0) { + return ARES_ECONNREFUSED; + } +#endif + + if (conn->flags & ARES_CONN_FLAG_TFO) { +#if defined(TFO_CLIENT_SOCKOPT) + if (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; } @@ -297,61 +504,81 @@ ares_bool_t ares_sockaddr_to_ares_addr(struct ares_addr *ares_addr, return ARES_FALSE; } -static ares_status_t ares_conn_set_self_ip(ares_conn_t *conn) +static ares_status_t ares__conn_connect(ares_conn_t *conn, struct sockaddr *sa, + ares_socklen_t salen) { - /* Some old systems might not have sockaddr_storage, so we make a union - * that's guaranteed to be large enough */ - union { - struct sockaddr sa; - struct sockaddr_in sa4; - struct sockaddr_in6 sa6; - } from; + /* Normal non TCPFastOpen style connect */ + if (!(conn->flags & ARES_CONN_FLAG_TFO)) { + return ares__connect_socket(conn->server->channel, conn->fd, sa, salen); + } - int rv; - ares_socklen_t len = sizeof(from); + /* 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; + int err; - memset(&from, 0, sizeof(from)); + do { + sa_endpoints_t endpoints; + memset(&endpoints, 0, sizeof(endpoints)); + endpoints.sae_dstaddr = sa; + endpoints.sae_dstaddrlen = salen; - rv = getsockname(conn->fd, &from.sa, &len); - if (rv != 0) { - return ARES_ECONNREFUSED; - } + rv = connectx(conn->fd, &endpoints, SAE_ASSOCID_ANY, + CONNECT_DATA_IDEMPOTENT | CONNECT_RESUME_ON_READ_WRITE, + NULL, 0, NULL, NULL); - if (!ares_sockaddr_to_ares_addr(&conn->self_ip, NULL, &from.sa)) { - return ARES_ECONNREFUSED; - } + err = SOCKERRNO; + if (rv == -1 && err != EINPROGRESS && err != EWOULDBLOCK) { + return ARES_ECONNREFUSED; + } + } while (rv == -1 && err == EINTR); + } 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_query_t *query) + ares_server_t *server, ares_bool_t is_tcp) { - ares_socklen_t salen; - ares_status_t status; - ares_bool_t is_tcp = query->using_tcp; - - union { - struct sockaddr_in sa4; - struct sockaddr_in6 sa6; - } saddr; - struct sockaddr *sa; - ares_conn_t *conn; - ares__llist_node_t *node = NULL; - int sock_type = is_tcp ? SOCK_STREAM : SOCK_DGRAM; + 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; *conn_out = NULL; conn = ares_malloc(sizeof(*conn)); if (conn == NULL) { - return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */ + 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->flags = is_tcp ? ARES_CONN_FLAG_TCP : ARES_CONN_FLAG_NONE; + + /* 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; + } + if (conn->queries_to_conn == NULL) { /* LCOV_EXCL_START: OutOfMemory */ status = ARES_ENOMEM; @@ -359,36 +586,14 @@ ares_status_t ares__open_connection(ares_conn_t **conn_out, /* LCOV_EXCL_STOP */ } - 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: - status = ARES_EBADFAMILY; /* LCOV_EXCL_LINE */ - goto done; + /* 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. */ - conn->fd = ares__open_socket(channel, server->addr.family, sock_type, 0); + conn->fd = ares__open_socket(channel, server->addr.family, stype, 0); if (conn->fd == ARES_SOCKET_BAD) { status = ARES_ECONNREFUSED; goto done; @@ -401,29 +606,36 @@ ares_status_t ares__open_connection(ares_conn_t **conn_out, } if (channel->sock_config_cb) { - int err = channel->sock_config_cb(conn->fd, sock_type, channel->sock_config_cb_data); + int err = + channel->sock_config_cb(conn->fd, stype, channel->sock_config_cb_data); if (err < 0) { status = ARES_ECONNREFUSED; goto done; } } - /* Connect to the server. */ - status = ares__connect_socket(channel, conn->fd, sa, salen); + /* 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, sock_type, channel->sock_create_cb_data); + 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); + status = ares_conn_set_self_ip(conn, ARES_FALSE); if (status != ARES_SUCCESS) { goto done; /* LCOV_EXCL_LINE: UntestablePath */ } @@ -452,7 +664,7 @@ ares_status_t ares__open_connection(ares_conn_t **conn_out, /* LCOV_EXCL_STOP */ } - SOCK_STATE_CALLBACK(channel, conn->fd, 1, 0); + SOCK_STATE_CALLBACK(channel, conn->fd, 1, is_tcp ? 1 : 0); if (is_tcp) { server->tcp_conn = conn; @@ -481,10 +693,10 @@ ares_socket_t ares__open_socket(ares_channel_t *channel, int af, int type, return socket(af, type, protocol); } -ares_status_t ares__connect_socket(ares_channel_t *channel, - ares_socket_t sockfd, +ares_status_t ares__connect_socket(ares_channel_t *channel, + ares_socket_t sockfd, const struct sockaddr *addr, - ares_socklen_t addrlen) + ares_socklen_t addrlen) { int rv; int err; @@ -521,26 +733,6 @@ void ares__close_socket(ares_channel_t *channel, ares_socket_t 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) { diff --git a/src/lib/ares_config.h.cmake b/src/lib/ares_config.h.cmake index cafdadd9..b3f91699 100644 --- a/src/lib/ares_config.h.cmake +++ b/src/lib/ares_config.h.cmake @@ -67,6 +67,9 @@ /* Define to 1 if you have the connect function. */ #cmakedefine HAVE_CONNECT 1 +/* Define to 1 if you have the connectx function. */ +#cmakedefine HAVE_CONNECTX 1 + /* define if the compiler supports basic C++11 syntax */ #cmakedefine HAVE_CXX11 1 diff --git a/src/lib/ares_private.h b/src/lib/ares_private.h index dd60ac5a..d2167852 100644 --- a/src/lib/ares_private.h +++ b/src/lib/ares_private.h @@ -160,8 +160,15 @@ struct ares_conn; typedef struct ares_conn ares_conn_t; typedef enum { - ARES_CONN_FLAG_NONE = 0, /*!< No flags */ - ARES_CONN_FLAG_TCP = 1 << 0 /*!< TCP not UDP */ + /*! No flags */ + ARES_CONN_FLAG_NONE = 0, + /*! TCP connection, not UDP */ + ARES_CONN_FLAG_TCP = 1 << 0, + /*! TCP Fast Open is enabled and being used if supported by the OS */ + ARES_CONN_FLAG_TFO = 1 << 1, + /*! TCP Fast Open has not yet sent its first packet. Gets unset on first + * write to a connection */ + ARES_CONN_FLAG_TFO_INITIAL = 1 << 2 } ares_conn_flags_t; struct ares_conn { @@ -597,14 +604,14 @@ ares_status_t ares__addrinfo_localhost(const char *name, unsigned short port, struct ares_addrinfo *ai); ares_status_t ares__open_connection(ares_conn_t **conn_out, ares_channel_t *channel, - ares_server_t *server, ares_query_t *query); + 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); ares_socket_t ares__open_socket(ares_channel_t *channel, int af, int type, int protocol); -ares_ssize_t ares__socket_write(ares_channel_t *channel, ares_socket_t s, - const void *data, size_t len); +ares_bool_t ares__socket_try_again(int errnum); +ares_ssize_t ares__conn_write(ares_conn_t *conn, const void *data, size_t len); 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, @@ -612,11 +619,11 @@ ares_ssize_t ares__socket_recvfrom(ares_channel_t *channel, ares_socket_t s, ares_ssize_t ares__socket_recv(ares_channel_t *channel, ares_socket_t s, void *data, size_t data_len); 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, +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); + ares_socklen_t addrlen); +void ares__destroy_server(ares_server_t *server); ares_status_t ares__servers_update(ares_channel_t *channel, ares__llist_t *server_list, diff --git a/src/lib/ares_process.c b/src/lib/ares_process.c index 91d57c30..59f642ba 100644 --- a/src/lib/ares_process.c +++ b/src/lib/ares_process.c @@ -46,7 +46,6 @@ static void timeadd(ares_timeval_t *now, size_t millisecs); -static ares_bool_t try_again(int errnum); static void write_tcp_data(ares_channel_t *channel, fd_set *write_fds, ares_socket_t write_fd); static void read_packets(ares_channel_t *channel, fd_set *read_fds, @@ -236,34 +235,6 @@ void ares_process_fd(ares_channel_t *channel, processfds(channel, NULL, read_fd, NULL, write_fd); } -/* Return 1 if the specified error number describes a readiness error, or 0 - * otherwise. This is mostly for HP-UX, which could return EAGAIN or - * EWOULDBLOCK. See this man page - * - * http://devrsrc1.external.hp.com/STKS/cgi-bin/man2html? - * manpage=/usr/share/man/man2.Z/send.2 - */ -static ares_bool_t try_again(int errnum) -{ -#if !defined EWOULDBLOCK && !defined EAGAIN -# error "Neither EWOULDBLOCK nor EAGAIN defined" -#endif - -#ifdef EWOULDBLOCK - if (errnum == EWOULDBLOCK) { - return ARES_TRUE; - } -#endif - -#if defined EAGAIN && EAGAIN != EWOULDBLOCK - if (errnum == EAGAIN) { - return ARES_TRUE; - } -#endif - - return ARES_FALSE; -} - /* If any TCP sockets select true for writing, write out queued data * we have for them. */ @@ -310,9 +281,9 @@ static void write_tcp_data(ares_channel_t *channel, fd_set *write_fds, } data = ares__buf_peek(server->tcp_send, &data_len); - count = ares__socket_write(channel, server->tcp_conn->fd, data, data_len); + count = ares__conn_write(server->tcp_conn, data, data_len); if (count <= 0) { - if (!try_again(SOCKERRNO)) { + if (!ares__socket_try_again(SOCKERRNO)) { handle_conn_error(server->tcp_conn, ARES_TRUE, ARES_ECONNREFUSED); } continue; @@ -355,7 +326,7 @@ static void read_tcp_data(ares_channel_t *channel, ares_conn_t *conn, count = ares__socket_recv(channel, conn->fd, ptr, ptr_len); if (count <= 0) { ares__buf_append_finish(server->tcp_parser, 0); - if (!(count == -1 && try_again(SOCKERRNO))) { + if (!(count == -1 && ares__socket_try_again(SOCKERRNO))) { handle_conn_error(conn, ARES_TRUE, ARES_ECONNREFUSED); } return; @@ -508,7 +479,7 @@ static void read_udp_packets_fd(ares_channel_t *channel, ares_conn_t *conn, * tcp */ continue; } else if (read_len < 0) { - if (try_again(SOCKERRNO)) { + if (ares__socket_try_again(SOCKERRNO)) { break; } @@ -797,8 +768,8 @@ static void handle_conn_error(ares_conn_t *conn, ares_bool_t critical_failure, /* Increment failures first before requeue so it is unlikely to requeue * to the same server */ if (critical_failure) { - server_increment_failures(server, - (conn->flags & ARES_CONN_FLAG_TCP)?ARES_TRUE:ARES_FALSE); + server_increment_failures( + server, (conn->flags & ARES_CONN_FLAG_TCP) ? ARES_TRUE : ARES_FALSE); } /* This will requeue any connections automatically */ @@ -932,7 +903,6 @@ static ares_server_t *ares__failover_server(ares_channel_t *channel) return first_server; } - static size_t ares__calc_query_timeout(const ares_query_t *query, const ares_server_t *server, const ares_timeval_t *now) @@ -1017,63 +987,80 @@ static ares_conn_t *ares__fetch_connection(ares_channel_t *channel, return conn; } -static ares_status_t ares__query_write(ares_conn_t *conn, ares_query_t *query, - const ares_timeval_t *now) +static ares_status_t ares__conn_query_write(ares_conn_t *conn, + ares_query_t *query, + const ares_timeval_t *now) { unsigned char *qbuf = NULL; size_t qbuf_len = 0; - ares_server_t *server = conn->server; - ares_channel_t *channel = server->channel; + ares_ssize_t len; + ares_server_t *server = conn->server; + ares_channel_t *channel = server->channel; ares_status_t status; status = ares_cookie_apply(query->query, conn, now); if (status != ARES_SUCCESS) { - goto done; - } - - status = ares_dns_write(query->query, &qbuf, &qbuf_len); - if (status != ARES_SUCCESS) { - goto done; + return status; } if (conn->flags & ARES_CONN_FLAG_TCP) { size_t prior_len = ares__buf_len(server->tcp_send); - status = - ares__buf_append_be16(server->tcp_send, (unsigned short)qbuf_len); + status = ares_dns_write_buf_tcp(query->query, server->tcp_send); if (status != ARES_SUCCESS) { - goto done; /* LCOV_EXCL_LINE: OutOfMemory */ + return status; } - status = ares__buf_append(server->tcp_send, qbuf, qbuf_len); - if (status != ARES_SUCCESS) { - goto done; + if (conn->flags & ARES_CONN_FLAG_TFO_INITIAL) { + /* When using TFO, we need to put it on the wire immediately. */ + size_t data_len; + const unsigned char *data = NULL; + + data = ares__buf_peek(server->tcp_send, &data_len); + len = ares__conn_write(conn, data, data_len); + if (len <= 0) { + if (ares__socket_try_again(SOCKERRNO)) { + /* This means we must not have qualified for TFO, keep the data + * buffered, wait on write signal. */ + return ARES_SUCCESS; + } + + /* TCP TFO might delay failure. Reflect that here */ + return ARES_ECONNREFUSED; + } + + /* Consume what was written */ + ares__buf_consume(server->tcp_send, (size_t)len); + return ARES_SUCCESS; } if (prior_len == 0) { SOCK_STATE_CALLBACK(channel, conn->fd, 1, 1); } - } else { - if (ares__socket_write(channel, conn->fd, qbuf, qbuf_len) == - -1) { - if (try_again(SOCKERRNO)) { - status = ARES_ESERVFAIL; - goto done; - } else { - /* UDP is connection-less, but we might receive an ICMP unreachable which - * means we can't talk to the remote host at all and that will be - * reflected here */ - status = ARES_ECONNREFUSED; - goto done; - } - } + + return ARES_SUCCESS; } - status = ARES_SUCCESS; + /* UDP Here */ + status = ares_dns_write(query->query, &qbuf, &qbuf_len); + if (status != ARES_SUCCESS) { + return status; + } -done: + len = ares__conn_write(conn, qbuf, qbuf_len); ares_free(qbuf); - return status; + + if (len == -1) { + if (ares__socket_try_again(SOCKERRNO)) { + return ARES_ESERVFAIL; + } + /* UDP is connection-less, but we might receive an ICMP unreachable which + * means we can't talk to the remote host at all and that will be + * reflected here */ + return ARES_ECONNREFUSED; + } + + return ARES_SUCCESS; } ares_status_t ares__send_query(ares_query_t *query, const ares_timeval_t *now) @@ -1100,7 +1087,7 @@ ares_status_t ares__send_query(ares_query_t *query, const ares_timeval_t *now) conn = ares__fetch_connection(channel, server, query); if (conn == NULL) { - status = ares__open_connection(&conn, channel, server, query); + status = ares__open_connection(&conn, channel, server, query->using_tcp); switch (status) { /* Good result, continue on */ case ARES_SUCCESS: @@ -1120,7 +1107,8 @@ ares_status_t ares__send_query(ares_query_t *query, const ares_timeval_t *now) } } - status = ares__query_write(conn, query, now); + /* Write the query */ + status = ares__conn_query_write(conn, query, now); switch (status) { /* Good result, continue on */ case ARES_SUCCESS: diff --git a/src/lib/ares_setup.h b/src/lib/ares_setup.h index 6bd5dca8..b6ce077f 100644 --- a/src/lib/ares_setup.h +++ b/src/lib/ares_setup.h @@ -231,95 +231,6 @@ struct timeval { }; #endif - -/* - * If we have the MSG_NOSIGNAL define, make sure we use - * it as the fourth argument of function send() - */ - -#ifdef HAVE_MSG_NOSIGNAL -# define SEND_4TH_ARG MSG_NOSIGNAL -#else -# define SEND_4TH_ARG 0 -#endif - - -#if defined(__minix) -/* Minix doesn't support recv on TCP sockets */ -# define sread(x, y, z) \ - (ares_ssize_t) \ - read((RECV_TYPE_ARG1)(x), (RECV_TYPE_ARG2)(y), (RECV_TYPE_ARG3)(z)) - -#elif defined(HAVE_RECV) -/* - * The definitions for the return type and arguments types - * of functions recv() and send() belong and come from the - * configuration file. Do not define them in any other place. - * - * HAVE_RECV is defined if you have a function named recv() - * which is used to read incoming data from sockets. If your - * function has another name then don't define HAVE_RECV. - * - * If HAVE_RECV is defined then RECV_TYPE_ARG1, RECV_TYPE_ARG2, - * RECV_TYPE_ARG3, RECV_TYPE_ARG4 and RECV_TYPE_RETV must also - * be defined. - * - * HAVE_SEND is defined if you have a function named send() - * which is used to write outgoing data on a connected socket. - * If yours has another name then don't define HAVE_SEND. - * - * If HAVE_SEND is defined then SEND_TYPE_ARG1, - * SEND_TYPE_ARG2, SEND_TYPE_ARG3, SEND_TYPE_ARG4 and - * SEND_TYPE_RETV must also be defined. - */ - -# if !defined(RECV_TYPE_ARG1) || !defined(RECV_TYPE_ARG2) || \ - !defined(RECV_TYPE_ARG3) || !defined(RECV_TYPE_ARG4) || \ - !defined(RECV_TYPE_RETV) -/* */ -Error Missing_definition_of_return_and_arguments_types_of_recv -/* */ -# else -# define sread(x, y, z) \ - (ares_ssize_t) recv((RECV_TYPE_ARG1)(x), (RECV_TYPE_ARG2)(y), \ - (RECV_TYPE_ARG3)(z), (RECV_TYPE_ARG4)(0)) -# endif -#else /* HAVE_RECV */ -# ifndef sread -/* */ -Error Missing_definition_of_macro_sread -/* */ -# endif -#endif /* HAVE_RECV */ - - -#if defined(__minix) -/* Minix doesn't support send on TCP sockets */ -# define swrite(x, y, z) \ - (ares_ssize_t) \ - write((SEND_TYPE_ARG1)(x), (SEND_TYPE_ARG2)(y), (SEND_TYPE_ARG3)(z)) - -#elif defined(HAVE_SEND) -# if !defined(SEND_TYPE_ARG1) || !defined(SEND_TYPE_ARG2) || \ - !defined(SEND_TYPE_ARG3) || !defined(SEND_TYPE_ARG4) || \ - !defined(SEND_TYPE_RETV) - /* */ - Error Missing_definition_of_return_and_arguments_types_of_send -/* */ -# else -# define swrite(x, y, z) \ - (ares_ssize_t) send((SEND_TYPE_ARG1)(x), (SEND_TYPE_ARG2)(y), \ - (SEND_TYPE_ARG3)(z), (SEND_TYPE_ARG4)(SEND_4TH_ARG)) -# endif -#else /* HAVE_SEND */ -# ifndef swrite - /* */ - Error Missing_definition_of_macro_swrite -/* */ -# endif -#endif /* HAVE_SEND */ - - /* * Function-like macro definition used to close a socket. */ diff --git a/src/lib/legacy/ares_getsock.c b/src/lib/legacy/ares_getsock.c index 61d62582..8c8476fa 100644 --- a/src/lib/legacy/ares_getsock.c +++ b/src/lib/legacy/ares_getsock.c @@ -67,12 +67,11 @@ int ares_getsock(const ares_channel_t *channel, ares_socket_t *socks, socks[sockindex] = conn->fd; - if (active_queries || (conn->flags & ARES_CONN_FLAG_TCP)) { + if (active_queries || conn->flags & ARES_CONN_FLAG_TCP) { bitmap |= ARES_GETSOCK_READABLE(setbits, sockindex); } - if ((conn->flags & ARES_CONN_FLAG_TCP) && - ares__buf_len(server->tcp_send)) { + if (conn->flags & ARES_CONN_FLAG_TCP && ares__buf_len(server->tcp_send)) { /* then the tcp socket is also writable! */ bitmap |= ARES_GETSOCK_WRITABLE(setbits, sockindex); } diff --git a/src/lib/record/ares_dns_private.h b/src/lib/record/ares_dns_private.h index bffb1064..42cf412e 100644 --- a/src/lib/record/ares_dns_private.h +++ b/src/lib/record/ares_dns_private.h @@ -53,8 +53,16 @@ ares_status_t ares_dns_record_rr_prealloc(ares_dns_record_t *dnsrec, ares_dns_section_t sect, size_t cnt); ares_dns_rr_t *ares_dns_get_opt_rr(ares_dns_record_t *rec); const ares_dns_rr_t *ares_dns_get_opt_rr_const(const ares_dns_record_t *rec); -void ares_dns_record_write_ttl_decrement(ares_dns_record_t *dnsrec, - unsigned int ttl_decrement); +void ares_dns_record_write_ttl_decrement(ares_dns_record_t *dnsrec, + unsigned int ttl_decrement); + +/* Same as ares_dns_write() but appends to an existing buffer object */ +ares_status_t ares_dns_write_buf(const ares_dns_record_t *dnsrec, + ares__buf_t *buf); + +/* Same as ares_dns_write_buf(), but prepends a 16bit length */ +ares_status_t ares_dns_write_buf_tcp(const ares_dns_record_t *dnsrec, + ares__buf_t *buf); /*! Create a DNS record object for a query. The arguments are the same as * those for ares_create_query(). diff --git a/src/lib/record/ares_dns_write.c b/src/lib/record/ares_dns_write.c index ae1df8f9..d72bb16a 100644 --- a/src/lib/record/ares_dns_write.c +++ b/src/lib/record/ares_dns_write.c @@ -1090,52 +1090,118 @@ static ares_status_t ares_dns_write_rr(const ares_dns_record_t *dnsrec, return ARES_SUCCESS; } -ares_status_t ares_dns_write(const ares_dns_record_t *dnsrec, - unsigned char **buf, size_t *buf_len) +ares_status_t ares_dns_write_buf(const ares_dns_record_t *dnsrec, + ares__buf_t *buf) { - ares__buf_t *b = NULL; - ares_status_t status; ares__llist_t *namelist = NULL; + size_t orig_len; + ares_status_t status; - if (buf == NULL || buf_len == NULL || dnsrec == NULL) { + if (dnsrec == NULL || buf == NULL) { return ARES_EFORMERR; } - *buf = NULL; - *buf_len = 0; + orig_len = ares__buf_len(buf); - b = ares__buf_create(); - if (b == NULL) { - return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */ + status = ares_dns_write_header(dnsrec, buf); + if (status != ARES_SUCCESS) { + goto done; } - status = ares_dns_write_header(dnsrec, b); + status = ares_dns_write_questions(dnsrec, &namelist, buf); if (status != ARES_SUCCESS) { goto done; } - status = ares_dns_write_questions(dnsrec, &namelist, b); + status = ares_dns_write_rr(dnsrec, &namelist, ARES_SECTION_ANSWER, buf); if (status != ARES_SUCCESS) { goto done; } - status = ares_dns_write_rr(dnsrec, &namelist, ARES_SECTION_ANSWER, b); + status = ares_dns_write_rr(dnsrec, &namelist, ARES_SECTION_AUTHORITY, buf); if (status != ARES_SUCCESS) { goto done; } - status = ares_dns_write_rr(dnsrec, &namelist, ARES_SECTION_AUTHORITY, b); + status = ares_dns_write_rr(dnsrec, &namelist, ARES_SECTION_ADDITIONAL, buf); if (status != ARES_SUCCESS) { goto done; } - status = ares_dns_write_rr(dnsrec, &namelist, ARES_SECTION_ADDITIONAL, b); +done: + ares__llist_destroy(namelist); if (status != ARES_SUCCESS) { + ares__buf_set_length(buf, orig_len); + } + + return status; +} + +ares_status_t ares_dns_write_buf_tcp(const ares_dns_record_t *dnsrec, + ares__buf_t *buf) +{ + ares_status_t status; + size_t orig_len; + size_t msg_len; + size_t len; + + if (dnsrec == NULL || buf == NULL) { + return ARES_EFORMERR; + } + + orig_len = ares__buf_len(buf); + + /* Write placeholder for length */ + status = ares__buf_append_be16(buf, 0); + if (status != ARES_SUCCESS) { + goto done; /* LCOV_EXCL_LINE: OutOfMemory */ + } + + /* Write message */ + status = ares_dns_write_buf(dnsrec, buf); + if (status != ARES_SUCCESS) { + goto done; /* LCOV_EXCL_LINE: OutOfMemory */ + } + + len = ares__buf_len(buf); + msg_len = len - orig_len - 2; + if (msg_len > 65535) { + status = ARES_EBADQUERY; goto done; } + /* Now we need to overwrite the length, so we jump back to the original + * message length, overwrite the section and jump back */ + ares__buf_set_length(buf, orig_len); + ares__buf_append_be16(buf, (unsigned short)(msg_len & 0xFFFF)); + ares__buf_set_length(buf, len); + done: - ares__llist_destroy(namelist); + if (status != ARES_SUCCESS) { + ares__buf_set_length(buf, orig_len); + } + return status; +} + +ares_status_t ares_dns_write(const ares_dns_record_t *dnsrec, + unsigned char **buf, size_t *buf_len) +{ + ares__buf_t *b = NULL; + ares_status_t status; + + if (buf == NULL || buf_len == NULL || dnsrec == NULL) { + return ARES_EFORMERR; + } + + *buf = NULL; + *buf_len = 0; + + b = ares__buf_create(); + if (b == NULL) { + return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */ + } + + status = ares_dns_write_buf(dnsrec, b); if (status != ARES_SUCCESS) { ares__buf_destroy(b); diff --git a/src/lib/str/ares__buf.c b/src/lib/str/ares__buf.c index 320a73d6..b855260a 100644 --- a/src/lib/str/ares__buf.c +++ b/src/lib/str/ares__buf.c @@ -204,7 +204,7 @@ ares_status_t ares__buf_set_length(ares__buf_t *buf, size_t len) return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */ } - buf->data_len = len; + buf->data_len = len + buf->offset; return ARES_SUCCESS; } diff --git a/test/ares-test-mock.cc b/test/ares-test-mock.cc index 29b8c71d..dfa51794 100644 --- a/test/ares-test-mock.cc +++ b/test/ares-test-mock.cc @@ -876,6 +876,8 @@ TEST_P(MockChannelTest, SearchDomainsBare) { ares_gethostbyname(channel_, "www", AF_INET, HostCallback, &result); Process(); EXPECT_TRUE(result.done_); + EXPECT_EQ(0, result.timeouts_); + std::stringstream ss; ss << result.host_; EXPECT_EQ("{'www' aliases=[] addrs=[2.3.4.5]}", ss.str()); diff --git a/test/ares-test.cc b/test/ares-test.cc index 03c328f8..b41cc57d 100644 --- a/test/ares-test.cc +++ b/test/ares-test.cc @@ -437,6 +437,17 @@ MockServer::MockServer(int family, unsigned short port) setsockopt(tcpfd_, SOL_SOCKET, SO_NOSIGPIPE, (void *)&optval, sizeof(optval)); #endif + /* Test system enable TCP FastOpen */ +#if defined(TCP_FASTOPEN) +# ifdef __linux__ + int qlen = 32; + setsockopt(tcpfd_, IPPROTO_TCP, TCP_FASTOPEN, &qlen, sizeof(qlen)); +# else + int on = 1; + setsockopt(tcpfd_, IPPROTO_TCP, TCP_FASTOPEN, BYTE_CAST &on, sizeof(on)); +# endif +#endif + // Create a UDP socket to receive data on. udpfd_ = socket(family, SOCK_DGRAM, 0); EXPECT_NE(ARES_SOCKET_BAD, udpfd_);