diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 21e2617c93..14a276384c 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -37,6 +37,7 @@ permissions: {} # # - 10.7 Lion (2011) - GSS (build-time, deprecated MIT Kerberos shim) # - 10.9 Mavericks (2013) - LDAP (build-time, deprecated), memset_s(), OCSP (runtime) +# - 10.10 Yosemite (2014) - SYS_recvmsg_x, SYS_sendmsg_x (runtime) # - 10.11 El Capitan (2015) - connectx() (runtime) # - 10.12 Sierra (2016) - clock_gettime() (build-time, runtime) # - 10.14 Mojave (2018) - SecTrustEvaluateWithError() (runtime), GSS Framework @@ -281,7 +282,9 @@ jobs: compiler: clang install: libnghttp3 libngtcp2 install_steps: pytest - configure: --enable-debug --with-openssl=/opt/homebrew/opt/openssl --with-ngtcp2 --with-apple-sectrust --enable-ntlm --enable-proxy-http3 --with-gssapi + configure: >- + --enable-debug --with-openssl=/opt/homebrew/opt/openssl --with-ngtcp2 --with-apple-sectrust --enable-ntlm --enable-proxy-http3 --with-gssapi + --enable-apple-fast-udp - name: 'OpenSSL event-based' compiler: clang @@ -326,7 +329,7 @@ jobs: -DCURL_USE_GSSAPI=ON -DGSS_ROOT_DIR=/opt/homebrew/opt/krb5 -DCURL_BROTLI=ON -DCURL_ZSTD=ON -DCURL_CLANG_TIDY=ON -DCLANG_TIDY=/opt/homebrew/opt/llvm/bin/clang-tidy - -DCURL_ENABLE_NTLM=ON -DUSE_PROXY_HTTP3=ON + -DCURL_ENABLE_NTLM=ON -DUSE_PROXY_HTTP3=ON -DCURL_ENABLE_APPLE_FAST_UDP=ON - name: 'LibreSSL openldap AppleGSS c-ares +examples' compiler: clang diff --git a/CMakeLists.txt b/CMakeLists.txt index dcb735d0ce..1cf5d8dce4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -805,6 +805,14 @@ else() set(USE_APPLE_SECTRUST OFF) endif() +set(USE_APPLE_FAST_UDP 0) +if(APPLE) + option(CURL_ENABLE_APPLE_FAST_UDP "Use Apple fast UDP (experimental)" OFF) + if(CURL_ENABLE_APPLE_FAST_UDP) + set(USE_APPLE_FAST_UDP 1) + endif() +endif() + if(_use_core_foundation_and_core_services) find_library(COREFOUNDATION_FRAMEWORK NAMES "CoreFoundation") mark_as_advanced(COREFOUNDATION_FRAMEWORK) diff --git a/configure.ac b/configure.ac index c40bfb2a1c..5c83942fbc 100644 --- a/configure.ac +++ b/configure.ac @@ -2852,6 +2852,28 @@ AC_SUBST([CURL_LIBCURL_VERSIONED_SYMBOLS_SONAME], ["4"]) dnl Keep in sync with AM_CONDITIONAL([CURL_LT_SHLIB_USE_VERSIONED_SYMBOLS], [test "$versioned_symbols" = "yes"]) +dnl --------------------------- +dnl check Apple fast UDP option +dnl --------------------------- + +if test "$curl_cv_apple" = "yes"; then + AC_MSG_CHECKING([whether to use Apple fast UDP]) + AC_ARG_ENABLE(apple-fast-udp, +AS_HELP_STRING([--enable-apple-fast-udp],[Enable using Apple fast UDP (experimental)]) +AS_HELP_STRING([--disable-apple-fast-udp],[Disable using Apple fast UDP (experimental) (default)]), + [ case "$enableval" in + yes) + AC_MSG_RESULT([yes]) + AC_DEFINE(USE_APPLE_FAST_UDP, 1, [to use Apple fast UDP (SYS_recvmsg_x, SYS_sendmsg_x)]) + ;; + *) + AC_MSG_RESULT([no]) + ;; + esac ], + AC_MSG_RESULT([no]) + ) +fi + dnl ---------------------------- dnl check Windows Unicode option dnl ---------------------------- diff --git a/docs/INSTALL-CMAKE.md b/docs/INSTALL-CMAKE.md index f5af80a418..1e7b1639e9 100644 --- a/docs/INSTALL-CMAKE.md +++ b/docs/INSTALL-CMAKE.md @@ -242,6 +242,7 @@ target_link_libraries(my_target PRIVATE CURL::libcurl) ## Enabling features +- `CURL_ENABLE_APPLE_FAST_UDP`: Use Apple fast UDP (experimental). Default: `OFF` - `CURL_ENABLE_NTLM`: Enable NTLM support. Default: `OFF` - `CURL_ENABLE_SMB`: Enable SMB. Default: `OFF` - `CURL_ENABLE_SSL`: Enable SSL support. Default: `ON` diff --git a/lib/curl_config-cmake.h.in b/lib/curl_config-cmake.h.in index c992434530..26379e88a9 100644 --- a/lib/curl_config-cmake.h.in +++ b/lib/curl_config-cmake.h.in @@ -774,6 +774,9 @@ ${SIZEOF_TIME_T_CODE} /* to enable Apple OS-native certificate verification */ #cmakedefine USE_APPLE_SECTRUST 1 +/* to use Apple fast UDP (SYS_recvmsg_x, SYS_sendmsg_x) */ +#cmakedefine USE_APPLE_FAST_UDP + /* Define to 1 to query for HTTPSRR when using DoH */ #cmakedefine USE_HTTPSRR 1 diff --git a/lib/vquic/vquic.c b/lib/vquic/vquic.c index 1f5e5bc6b8..384935f87a 100644 --- a/lib/vquic/vquic.c +++ b/lib/vquic/vquic.c @@ -37,6 +37,13 @@ #include #endif +#if defined(USE_APPLE_FAST_UDP) && defined(__APPLE__) +#include +#if defined(SYS_recvmsg_x) && defined(SYS_sendmsg_x) +#define HAVE_APPLE_MSG_X +#endif +#endif + #include "bufq.h" #include "curlx/dynbuf.h" #include "curlx/fopen.h" @@ -57,6 +64,29 @@ #define NW_CHUNK_SIZE (64 * 1024) #define NW_SEND_CHUNKS 1 +#ifdef HAVE_APPLE_MSG_X +struct msghdr_x { + void *msg_name; /* optional address */ + socklen_t msg_namelen; /* size of address */ + struct iovec *msg_iov; /* scatter/gather array */ + int msg_iovlen; /* # elements in msg_iov */ + void *msg_control; /* ancillary data, see below */ + socklen_t msg_controllen; /* ancillary data buffer len */ + int msg_flags; /* flags on received message */ + size_t msg_datalen; /* byte length of buffer in msg_iov */ +}; +#endif + +#ifdef CURLVERBOSE +#ifdef HAVE_APPLE_MSG_X +#define VQUIC_SEND_METHOD "sendmsg_x" +#elif defined(HAVE_SENDMSG) +#define VQUIC_SEND_METHOD "sendmsg" +#else +#define VQUIC_SEND_METHOD "send" +#endif +#endif + int Curl_vquic_init(void) { #if defined(USE_NGTCP2) && defined(OPENSSL_QUIC_API2) @@ -91,6 +121,8 @@ CURLcode Curl_vquic_ctx_init(struct Curl_easy *data, BUFQ_OPT_SOFT_LIMIT); #if defined(__linux__) && defined(UDP_SEGMENT) && defined(HAVE_SENDMSG) qctx->no_gso = FALSE; +#elif defined(HAVE_APPLE_MSG_X) + qctx->no_gso = FALSE; #else qctx->no_gso = TRUE; #endif @@ -126,6 +158,79 @@ void Curl_vquic_ctx_update_time(struct cf_quic_ctx *qctx, qctx->last_op = *pnow; } +#ifdef HAVE_APPLE_MSG_X +static CURLcode do_sendmsg(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_quic_ctx *qctx, + const uint8_t *pkt, size_t pktlen, size_t gsolen, + size_t *psent) +{ +#define MSG_X_SNUM 64 + struct iovec msg_iov[MSG_X_SNUM]; + struct msghdr_x mmsg[MSG_X_SNUM]; + char errstr[STRERROR_LEN]; + size_t n, i = 0, sent = 0; + int rc; + CURLcode result = CURLE_OK; + VERBOSE(size_t calls = 0); + + *psent = 0; + if(!pktlen) + return CURLE_OK; + if(!gsolen || (pktlen < gsolen)) + gsolen = pktlen; + n = pktlen / gsolen; + while(i < n) { + size_t j, batch = CURLMIN(n - i, MSG_X_SNUM); + + for(j = 0; j < batch; ++j) { + msg_iov[j].iov_base = CURL_UNCONST(&pkt[(i + j) * gsolen]); + msg_iov[j].iov_len = gsolen; + memset(&mmsg[j], 0, sizeof(mmsg[j])); + mmsg[j].msg_iov = &msg_iov[j]; + mmsg[j].msg_iovlen = 1; + } + +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + while((rc = syscall(SYS_sendmsg_x, qctx->sockfd, &mmsg, batch, 0)) == -1 && + (SOCKERRNO == SOCKEINTR || SOCKERRNO == SOCKEMSGSIZE)) + ; +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#pragma GCC diagnostic pop +#endif + + if(rc == -1) { + if(SOCK_EAGAIN(SOCKERRNO)) { + CURL_TRC_CF(data, cf, "egress, sendmsg_x -> EAGAIN"); + result = sent ? CURLE_OK : CURLE_AGAIN; + goto out; + } + curlx_strerror(SOCKERRNO, errstr, sizeof(errstr)); + failf(data, "QUIC: sendmsg_x() unexpectedly returned %d (errno=%d; %s)", + rc, SOCKERRNO, errstr); + result = CURLE_RECV_ERROR; + goto out; + } + VERBOSE(++calls); + i += batch; + sent += (batch * gsolen); + } + +out: + *psent = sent; + if(sent || result) + CURL_TRC_CF(data, cf, + "vquic_sendmsg_x(len=%zu, gso=%zu, packets=%zu, " + "calls=%zu) -> %d", + sent, gsolen, n, calls, (int)result); + return result; +} + +#else /* HAVE_APPLE_MSG_X */ + static CURLcode send_packet_no_gso(struct Curl_cfilter *cf, struct Curl_easy *data, struct cf_quic_ctx *qctx, @@ -231,16 +336,13 @@ static CURLcode do_sendmsg(struct Curl_cfilter *cf, (void)cf; out: + CURL_TRC_CF(data, cf, + "vquic_%s(len=%zu, gso=%zu, calls=1) -> %d, sent=%zu", + VQUIC_SEND_METHOD, pktlen, gsolen, (int)result, *psent); return result; } -#ifdef CURLVERBOSE -#ifdef HAVE_SENDMSG -#define VQUIC_SEND_METHOD "sendmsg" -#else -#define VQUIC_SEND_METHOD "send" -#endif -#endif +#endif /* !HAVE_APPLE_MSG_X */ static CURLcode send_packet_no_gso(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -333,9 +435,6 @@ static CURLcode vquic_send_packets(struct Curl_cfilter *cf, } else { result = do_sendmsg(cf, data, qctx, pkt, pktlen, gsolen, psent); - CURL_TRC_CF(data, cf, - "vquic_%s(len=%zu, gso=%zu, calls=1) -> %d, sent=%zu", - VQUIC_SEND_METHOD, pktlen, gsolen, (int)result, *psent); } if(!result) qctx->last_io = qctx->last_op; @@ -405,13 +504,13 @@ CURLcode Curl_vquic_send_tail_split(struct Curl_cfilter *cf, qctx->split_len = Curl_bufq_len(&qctx->sendbuf) - tail_len; qctx->split_gsolen = gsolen; qctx->gsolen = tail_gsolen; - CURL_TRC_CF(data, cf, "Curl_vquic_send_tail_split: " - "[%zu gso=%zu][%zu gso=%zu]", + CURL_TRC_CF(data, cf, "vquic_send_tail_split: [%zu gso=%zu][%zu gso=%zu]", qctx->split_len, qctx->split_gsolen, tail_len, qctx->gsolen); return Curl_vquic_flush(cf, data, qctx); } -#if defined(HAVE_SENDMMSG) || defined(HAVE_SENDMSG) +#if (defined(HAVE_SENDMMSG) || defined(HAVE_SENDMSG)) && \ + !defined(HAVE_APPLE_MSG_X) static size_t vquic_msghdr_get_udp_gro(struct msghdr *msg) { int gso_size = 0; @@ -547,6 +646,112 @@ out: return result; } +#elif defined(HAVE_APPLE_MSG_X) + +static CURLcode recvmsg_x_packets(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_quic_ctx *qctx, + size_t max_pkts, + Curl_vquic_recv_pkts_cb *recv_cb, + void *userp) +{ +#define MSG_X_NUM 64 +#define MSG_BUF_SIZE (2048) + struct iovec msg_iov[MSG_X_NUM]; + struct msghdr_x mmsg[MSG_X_NUM]; + uint8_t msg_ctrl[MSG_X_NUM * CMSG_SPACE(sizeof(int))]; + struct sockaddr_storage remote_addr[MSG_X_NUM]; + size_t total_nread = 0, pkts = 0; +#ifdef CURLVERBOSE + size_t calls = 0; +#endif + int mcount, i; + char errstr[STRERROR_LEN]; + CURLcode result = CURLE_OK; + size_t gso_size; + char *sockbuf = NULL; + uint8_t (*bufs)[MSG_BUF_SIZE] = NULL; + + DEBUGASSERT(max_pkts > 0); + result = Curl_multi_xfer_sockbuf_borrow(data, MSG_X_NUM * MSG_BUF_SIZE, + &sockbuf); + if(result) + goto out; + bufs = (uint8_t (*)[MSG_BUF_SIZE])sockbuf; + + total_nread = 0; + while(pkts < max_pkts) { + int n = (int)CURLMIN(CURLMIN(MSG_X_NUM, IOV_MAX), max_pkts); + memset(&mmsg, 0, sizeof(mmsg)); + for(i = 0; i < n; ++i) { + msg_iov[i].iov_base = bufs[i]; + msg_iov[i].iov_len = sizeof(bufs[i]); + mmsg[i].msg_iov = &msg_iov[i]; + mmsg[i].msg_iovlen = 1; + mmsg[i].msg_name = &remote_addr[i]; + mmsg[i].msg_namelen = sizeof(remote_addr[i]); + mmsg[i].msg_control = &msg_ctrl[i * CMSG_SPACE(sizeof(int))]; + mmsg[i].msg_controllen = CMSG_SPACE(sizeof(int)); + } + +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + while((mcount = syscall(SYS_recvmsg_x, qctx->sockfd, mmsg, n, 0)) == -1 && + (SOCKERRNO == SOCKEINTR || SOCKERRNO == SOCKEMSGSIZE)) + ; +#if defined(CURL_HAVE_DIAG) && defined(__APPLE__) +#pragma GCC diagnostic pop +#endif + if(mcount == -1) { + if(SOCK_EAGAIN(SOCKERRNO)) { + CURL_TRC_CF(data, cf, "ingress, recvmsg_x -> EAGAIN"); + goto out; + } + if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) { + struct ip_quadruple ip; + if(!Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip)) + failf(data, "QUIC: connection to %s port %u refused", + ip.remote_ip, ip.remote_port); + result = CURLE_COULDNT_CONNECT; + goto out; + } + curlx_strerror(SOCKERRNO, errstr, sizeof(errstr)); + failf(data, "QUIC: recvmsg_x() unexpectedly returned %d (errno=%d; %s)", + mcount, SOCKERRNO, errstr); + result = CURLE_RECV_ERROR; + goto out; + } + + VERBOSE(++calls); + for(i = 0; i < mcount; ++i) { + /* A zero-length UDP packet is no QUIC packet. Ignore. */ + if(!mmsg[i].msg_datalen) { + ++pkts; + continue; + } + total_nread += mmsg[i].msg_datalen; + gso_size = mmsg[i].msg_datalen; + + result = recv_cb(bufs[i], mmsg[i].msg_datalen, gso_size, + mmsg[i].msg_name, + mmsg[i].msg_namelen, 0, userp); + if(result) + goto out; + pkts += (mmsg[i].msg_datalen + gso_size - 1) / gso_size; + } + } + +out: + if(total_nread || result) + CURL_TRC_CF(data, cf, + "vquic_recvmsg_x(len=%zu, packets=%zu, calls=%zu) -> %d", + total_nread, pkts, calls, (int)result); + Curl_multi_xfer_sockbuf_release(data, sockbuf); + return result; +} + #elif defined(HAVE_SENDMSG) static CURLcode recvmsg_packets(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -705,6 +910,8 @@ CURLcode Curl_vquic_recv_packets(struct Curl_cfilter *cf, CURLcode result; #ifdef HAVE_SENDMMSG result = recvmmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp); +#elif defined(HAVE_APPLE_MSG_X) + result = recvmsg_x_packets(cf, data, qctx, max_pkts, recv_cb, userp); #elif defined(HAVE_SENDMSG) result = recvmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp); #else