From b92222299b25d363dbe41759f0aa10d3e6ab82fb Mon Sep 17 00:00:00 2001 From: "Li, Xun" Date: Wed, 28 Sep 2022 06:29:54 +0000 Subject: [PATCH] [LibOS, PAL] Support corking flags in socket related syscalls Support `MSG_MORE` flag in `send` syscall. Support `UDP_CORK` flags in `setsockopt` and `getsockopt` syscalls. Signed-off-by: Li, Xun --- common/include/linux_socket.h | 5 +++ libos/include/libos_socket.h | 4 +- libos/src/net/ip.c | 64 +++++++++++++++++++++++++++- libos/src/net/unix.c | 7 ++- libos/src/sys/libos_socket.c | 12 +++++- libos/test/regression/getsockopt.c | 20 +++++++++ libos/test/regression/test_libos.py | 1 + pal/include/pal/pal.h | 5 ++- pal/include/pal_internal.h | 4 +- pal/regression/send_handle.c | 2 +- pal/src/host/linux-sgx/pal_host.h | 1 + pal/src/host/linux-sgx/pal_sockets.c | 29 ++++++++++--- pal/src/host/linux/pal_host.h | 1 + pal/src/host/linux/pal_sockets.c | 28 +++++++++--- pal/src/host/skeleton/pal_sockets.c | 2 +- pal/src/pal_sockets.c | 4 +- 16 files changed, 165 insertions(+), 24 deletions(-) diff --git a/common/include/linux_socket.h b/common/include/linux_socket.h index 26981296dc..befaec8bf1 100644 --- a/common/include/linux_socket.h +++ b/common/include/linux_socket.h @@ -61,11 +61,13 @@ struct mmsghdr { #define MSG_TRUNC 0x20 #define MSG_DONTWAIT 0x40 #define MSG_NOSIGNAL 0x4000 +#define MSG_MORE 0x8000 #define MSG_CMSG_CLOEXEC 0x40000000 /* Option levels. */ #define SOL_SOCKET 1 #define SOL_TCP 6 +#define SOL_UDP 17 /* Socket options. */ #define SO_REUSEADDR 2 @@ -86,6 +88,9 @@ struct mmsghdr { #define TCP_NODELAY 1 #define TCP_CORK 3 +/* UDP options. */ +#define UDP_CORK 1 + struct linger { int l_onoff; int l_linger; diff --git a/libos/include/libos_socket.h b/libos/include/libos_socket.h index d95ec06ce6..f3e4f8507d 100644 --- a/libos/include/libos_socket.h +++ b/libos/include/libos_socket.h @@ -87,9 +87,11 @@ struct libos_sock_ops { * \param addrlen The length of \p addr. * \param force_nonblocking If `true` this request should not block. Otherwise just use * whatever mode the handle is in. + * \param force_cork If `true` this request is corked. Otherwise just use + * whatever mode the handle is in. */ int (*send)(struct libos_handle* handle, struct iovec* iov, size_t iov_len, size_t* out_size, - void* addr, size_t addrlen, bool force_nonblocking); + void* addr, size_t addrlen, bool force_nonblocking, bool force_cork); /*! * \brief Receive continuous data into an array of buffers. diff --git a/libos/src/net/ip.c b/libos/src/net/ip.c index abdedad5a4..54d70c00b4 100644 --- a/libos/src/net/ip.c +++ b/libos/src/net/ip.c @@ -276,6 +276,31 @@ static int set_tcp_option(struct libos_handle* handle, int optname, void* optval return pal_to_unix_errno(ret); } +static int set_udp_option(struct libos_handle* handle, int optname, void* optval, size_t len) { + PAL_STREAM_ATTR attr; + int ret = PalStreamAttributesQueryByHandle(handle->info.sock.pal_handle, &attr); + if (ret < 0) { + return pal_to_unix_errno(ret); + } + assert(attr.handle_type == PAL_TYPE_SOCKET); + + if (len < sizeof(int)) { + /* All currently supported options use `int`. */ + return -EINVAL; + } + + switch (optname) { + case UDP_CORK: + attr.socket.udp_cork = *(int*)optval; + break; + default: + return -ENOPROTOOPT; + } + + ret = PalStreamAttributesSetByHandle(handle->info.sock.pal_handle, &attr); + return pal_to_unix_errno(ret); +} + static int set_ipv4_option(struct libos_handle* handle, int optname, void* optval, size_t len) { __UNUSED(handle); __UNUSED(optval); @@ -483,6 +508,11 @@ static int setsockopt(struct libos_handle* handle, int level, int optname, void* return -EOPNOTSUPP; } return set_tcp_option(handle, optname, optval, len); + case SOL_UDP: + if (sock->type != SOCK_DGRAM) { + return -EOPNOTSUPP; + } + return set_udp_option(handle, optname, optval, len); default: return -ENOPROTOOPT; } @@ -516,6 +546,31 @@ static int get_tcp_option(struct libos_handle* handle, int optname, void* optval return 0; } +static int get_udp_option(struct libos_handle* handle, int optname, void* optval, size_t* len) { + PAL_STREAM_ATTR attr; + int ret = PalStreamAttributesQueryByHandle(handle->info.sock.pal_handle, &attr); + if (ret < 0) { + return pal_to_unix_errno(ret); + } + assert(attr.handle_type == PAL_TYPE_SOCKET); + + int val; + switch (optname) { + case UDP_CORK: + val = attr.socket.udp_cork; + break; + default: + return -ENOPROTOOPT; + } + + if (*len > sizeof(val)) { + /* Cap the buffer size to the option size. */ + *len = sizeof(val); + } + memcpy(optval, &val, *len); + return 0; +} + static int get_ipv6_option(struct libos_handle* handle, int optname, void* optval, size_t* len) { PAL_STREAM_ATTR attr; int ret = PalStreamAttributesQueryByHandle(handle->info.sock.pal_handle, &attr); @@ -606,13 +661,18 @@ static int getsockopt(struct libos_handle* handle, int level, int optname, void* return -EOPNOTSUPP; } return get_tcp_option(handle, optname, optval, len); + case SOL_UDP: + if (sock->type != SOCK_DGRAM) { + return -EOPNOTSUPP; + } + return get_udp_option(handle, optname, optval, len); default: return -EOPNOTSUPP; } } static int send(struct libos_handle* handle, struct iovec* iov, size_t iov_len, size_t* out_size, - void* addr, size_t addrlen, bool force_nonblocking) { + void* addr, size_t addrlen, bool force_nonblocking, bool force_cork) { assert(handle->type == TYPE_SOCK); struct libos_sock_handle* sock = &handle->info.sock; @@ -662,7 +722,7 @@ static int send(struct libos_handle* handle, struct iovec* iov, size_t iov_len, } int ret = PalSocketSend(sock->pal_handle, pal_iov, iov_len, out_size, - addr ? &pal_ip_addr : NULL, force_nonblocking); + addr ? &pal_ip_addr : NULL, force_nonblocking, force_cork); ret = (ret == -PAL_ERROR_TOOLONG) ? -EMSGSIZE : pal_to_unix_errno(ret); free(pal_iov); return ret; diff --git a/libos/src/net/unix.c b/libos/src/net/unix.c index e1fa04244b..bbcac1aa74 100644 --- a/libos/src/net/unix.c +++ b/libos/src/net/unix.c @@ -404,7 +404,7 @@ static int maybe_force_nonblocking_wrapper(bool force_nonblocking, struct libos_ } static int send(struct libos_handle* handle, struct iovec* iov, size_t iov_len, size_t* out_size, - void* addr, size_t addrlen, bool force_nonblocking) { + void* addr, size_t addrlen, bool force_nonblocking, bool force_cork) { __UNUSED(addr); __UNUSED(addrlen); @@ -413,6 +413,11 @@ static int send(struct libos_handle* handle, struct iovec* iov, size_t iov_len, BUG(); } + if (force_cork == true) { + /* MSG_MORE flag is not supported by UNIX domain sockets. */ + BUG(); + } + PAL_HANDLE pal_handle = __atomic_load_n(&handle->info.sock.pal_handle, __ATOMIC_ACQUIRE); if (!pal_handle) { return -ENOTCONN; diff --git a/libos/src/sys/libos_socket.c b/libos/src/sys/libos_socket.c index 13bad81d14..a43711b3d4 100644 --- a/libos/src/sys/libos_socket.c +++ b/libos/src/sys/libos_socket.c @@ -613,13 +613,14 @@ ssize_t do_sendmsg(struct libos_handle* handle, struct iovec* iov, size_t iov_le if (handle->type != TYPE_SOCK) { return -ENOTSOCK; } - if (!WITHIN_MASK(flags, MSG_NOSIGNAL | MSG_DONTWAIT)) { + if (!WITHIN_MASK(flags, MSG_NOSIGNAL | MSG_DONTWAIT | MSG_MORE)) { return -EOPNOTSUPP; } /* Note this only indicates whether this operation was requested to be nonblocking. If it's * `false`, but the handle is in nonblocking mode, this send won't block. */ bool force_nonblocking = flags & MSG_DONTWAIT; + bool force_cork = flags & MSG_MORE; struct libos_sock_handle* sock = &handle->info.sock; lock(&sock->lock); @@ -632,6 +633,13 @@ ssize_t do_sendmsg(struct libos_handle* handle, struct iovec* iov, size_t iov_le ret = -EPIPE; } + if (!ret && force_cork) { + if (sock->domain != AF_INET && sock->domain != AF_INET6) { + log_warning("%s: MSG_MORE on non IPv4 or IPv6 sockets is not supported", __func__); + ret = -EOPNOTSUPP; + } + } + unlock(&sock->lock); if (ret < 0) { @@ -644,7 +652,7 @@ ssize_t do_sendmsg(struct libos_handle* handle, struct iovec* iov, size_t iov_le } size_t size = 0; - ret = sock->ops->send(handle, iov, iov_len, &size, addr, addrlen, force_nonblocking); + ret = sock->ops->send(handle, iov, iov_len, &size, addr, addrlen, force_nonblocking, force_cork); maybe_epoll_et_trigger(handle, ret, /*in=*/false, !ret ? size < total_size : false); if (!ret) { ret = size; diff --git a/libos/test/regression/getsockopt.c b/libos/test/regression/getsockopt.c index 115843d931..6c18426881 100644 --- a/libos/test/regression/getsockopt.c +++ b/libos/test/regression/getsockopt.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -47,5 +48,24 @@ int main(int argc, char** argv) { } printf("getsockopt: Got TCP_NODELAY flag OK\n"); + + int fd2 = socket(PF_INET, SOCK_DGRAM, 0); + if (fd2 < 0) { + perror("socket failed"); + return 1; + } + ret = getsockopt(fd2, SOL_UDP, UDP_CORK, (void*)&so_flags, &optlen); + if (ret < 0) { + perror("getsockopt(SOL_UDP, UDP_CORK) failed"); + return 1; + } + + if (optlen != sizeof(so_flags) || (so_flags != 0 && so_flags != 1)) { + fprintf(stderr, "getsockopt(SOL_UDP, UDP_CORK) failed\n"); + return 1; + } + + printf("getsockopt: Got UDP_CORK flag OK\n"); + return 0; } diff --git a/libos/test/regression/test_libos.py b/libos/test/regression/test_libos.py index a453abd8b6..a3792c3470 100644 --- a/libos/test/regression/test_libos.py +++ b/libos/test/regression/test_libos.py @@ -1215,6 +1215,7 @@ def test_000_getsockopt(self): stdout, _ = self.run_binary(['getsockopt']) self.assertIn('getsockopt: Got socket type OK', stdout) self.assertIn('getsockopt: Got TCP_NODELAY flag OK', stdout) + self.assertIn('getsockopt: Got UDP_CORK flag OK', stdout) def test_010_epoll(self): stdout, _ = self.run_binary(['epoll_test']) diff --git a/pal/include/pal/pal.h b/pal/include/pal/pal.h index 80421e1d24..4da74ad0f3 100644 --- a/pal/include/pal/pal.h +++ b/pal/include/pal/pal.h @@ -460,6 +460,7 @@ typedef struct _PAL_STREAM_ATTR { bool tcp_cork; bool tcp_nodelay; bool ipv6_v6only; + bool udp_cork; } socket; }; } PAL_STREAM_ATTR; @@ -596,13 +597,15 @@ int PalSocketConnect(PAL_HANDLE handle, struct pal_socket_addr* addr, * \param addr Destination address. Can be NULL if the socket was connected. * \param force_nonblocking If `true` this request should not block. Otherwise just use * whatever mode the handle is in. + * \param force_cork If `true` this request is corked. Otherwise just use + * whatever mode the handle is in. * * \returns 0 on success, negative error code on failure. * * Data is sent atomically, i.e. data from two `PalSocketSend` calls will not be interleaved. */ int PalSocketSend(PAL_HANDLE handle, struct pal_iovec* iov, size_t iov_len, size_t* out_size, - struct pal_socket_addr* addr, bool force_nonblocking); + struct pal_socket_addr* addr, bool force_nonblocking, bool force_cork); /*! * \brief Receive data. diff --git a/pal/include/pal_internal.h b/pal/include/pal_internal.h index 980a3cd68a..573c4df094 100644 --- a/pal/include/pal_internal.h +++ b/pal/include/pal_internal.h @@ -119,7 +119,7 @@ struct socket_ops { int (*connect)(PAL_HANDLE handle, struct pal_socket_addr* addr, struct pal_socket_addr* out_local_addr); int (*send)(PAL_HANDLE handle, struct pal_iovec* iov, size_t iov_len, size_t* out_size, - struct pal_socket_addr* addr, bool force_nonblocking); + struct pal_socket_addr* addr, bool force_nonblocking, bool force_cork); int (*recv)(PAL_HANDLE handle, struct pal_iovec* iov, size_t iov_len, size_t* out_size, struct pal_socket_addr* addr, bool force_nonblocking); }; @@ -192,7 +192,7 @@ int _PalSocketAccept(PAL_HANDLE handle, pal_stream_options_t options, PAL_HANDLE int _PalSocketConnect(PAL_HANDLE handle, struct pal_socket_addr* addr, struct pal_socket_addr* out_local_addr); int _PalSocketSend(PAL_HANDLE handle, struct pal_iovec* iov, size_t iov_len, size_t* out_size, - struct pal_socket_addr* addr, bool force_nonblocking); + struct pal_socket_addr* addr, bool force_nonblocking, bool force_cork); int _PalSocketRecv(PAL_HANDLE handle, struct pal_iovec* iov, size_t iov_len, size_t* out_total_size, struct pal_socket_addr* addr, bool force_nonblocking); diff --git a/pal/regression/send_handle.c b/pal/regression/send_handle.c index a2be37dc82..d8be4c721d 100644 --- a/pal/regression/send_handle.c +++ b/pal/regression/send_handle.c @@ -22,7 +22,7 @@ static void write_all(PAL_HANDLE handle, int type, char* buf, size_t size) { .iov_len = this_size, }; CHECK(PalSocketSend(handle, &iov, 1, &this_size, /*addr=*/NULL, - /*force_nonblocking=*/false)); + /*force_nonblocking=*/false, /*force_cork=*/false)); break; default: BUG(); diff --git a/pal/src/host/linux-sgx/pal_host.h b/pal/src/host/linux-sgx/pal_host.h index 901dbad558..8b807161f8 100644 --- a/pal/src/host/linux-sgx/pal_host.h +++ b/pal/src/host/linux-sgx/pal_host.h @@ -103,6 +103,7 @@ typedef struct { bool tcp_cork; bool tcp_nodelay; bool ipv6_v6only; + bool udp_cork; } sock; struct { diff --git a/pal/src/host/linux-sgx/pal_sockets.c b/pal/src/host/linux-sgx/pal_sockets.c index 293fb48c5d..d41e4a819c 100644 --- a/pal/src/host/linux-sgx/pal_sockets.c +++ b/pal/src/host/linux-sgx/pal_sockets.c @@ -85,6 +85,7 @@ static PAL_HANDLE create_sock_handle(int fd, enum pal_socket_domain domain, handle->sock.tcp_cork = false; handle->sock.tcp_nodelay = false; handle->sock.ipv6_v6only = false; + handle->sock.udp_cork = false; return handle; } @@ -295,6 +296,7 @@ static int attrquerybyhdl(PAL_HANDLE handle, PAL_STREAM_ATTR* attr) { attr->socket.tcp_cork = handle->sock.tcp_cork; attr->socket.tcp_nodelay = handle->sock.tcp_nodelay; attr->socket.ipv6_v6only = handle->sock.ipv6_v6only; + attr->socket.udp_cork = handle->sock.udp_cork; return 0; }; @@ -451,11 +453,25 @@ static int attrsetbyhdl_tcp(PAL_HANDLE handle, PAL_STREAM_ATTR* attr) { static int attrsetbyhdl_udp(PAL_HANDLE handle, PAL_STREAM_ATTR* attr) { assert(handle->sock.type == PAL_SOCKET_UDP); - return attrsetbyhdl_common(handle, attr); + int ret = attrsetbyhdl_common(handle, attr); + if (ret < 0) { + return ret; + } + + if (attr->socket.udp_cork != handle->sock.udp_cork) { + int val = attr->socket.udp_cork; + int ret = ocall_setsockopt(handle->sock.fd, SOL_UDP, UDP_CORK, &val, sizeof(val)); + if (ret < 0) { + return unix_to_pal_error(ret); + } + handle->sock.udp_cork = attr->socket.udp_cork; + } + + return 0; } static int send(PAL_HANDLE handle, struct pal_iovec* pal_iov, size_t iov_len, size_t* out_size, - struct pal_socket_addr* addr, bool force_nonblocking) { + struct pal_socket_addr* addr, bool force_nonblocking, bool force_cork) { assert(handle->hdr.type == PAL_TYPE_SOCKET); struct sockaddr_storage sa_storage; @@ -476,8 +492,8 @@ static int send(PAL_HANDLE handle, struct pal_iovec* pal_iov, size_t iov_len, si iov[i].iov_base = pal_iov[i].iov_base; iov[i].iov_len = pal_iov[i].iov_len; } - - unsigned int flags = force_nonblocking ? MSG_DONTWAIT : 0; + unsigned int flags = (force_nonblocking ? MSG_DONTWAIT : 0) | + (force_cork ? MSG_MORE : 0); ssize_t ret = ocall_send(handle->sock.fd, iov, iov_len, addr ? &sa_storage : NULL, linux_addrlen, /*control=*/NULL, /*controllen=*/0, flags); free(iov); @@ -634,11 +650,12 @@ int _PalSocketConnect(PAL_HANDLE handle, struct pal_socket_addr* addr, } int _PalSocketSend(PAL_HANDLE handle, struct pal_iovec* iov, size_t iov_len, size_t* out_size, - struct pal_socket_addr* addr, bool force_nonblocking) { + struct pal_socket_addr* addr, bool force_nonblocking, bool force_cork) { if (!handle->sock.ops->send) { return -PAL_ERROR_NOTSUPPORT; } - return handle->sock.ops->send(handle, iov, iov_len, out_size, addr, force_nonblocking); + return handle->sock.ops->send(handle, iov, iov_len, out_size, addr, force_nonblocking, + force_cork); } int _PalSocketRecv(PAL_HANDLE handle, struct pal_iovec* iov, size_t iov_len, size_t* out_total_size, diff --git a/pal/src/host/linux/pal_host.h b/pal/src/host/linux/pal_host.h index 82fc80b6b8..89fb961ca3 100644 --- a/pal/src/host/linux/pal_host.h +++ b/pal/src/host/linux/pal_host.h @@ -80,6 +80,7 @@ typedef struct { bool tcp_cork; bool tcp_nodelay; bool ipv6_v6only; + bool udp_cork; } sock; struct { diff --git a/pal/src/host/linux/pal_sockets.c b/pal/src/host/linux/pal_sockets.c index fb85519f3a..22c127ede7 100644 --- a/pal/src/host/linux/pal_sockets.c +++ b/pal/src/host/linux/pal_sockets.c @@ -76,6 +76,7 @@ static PAL_HANDLE create_sock_handle(int fd, enum pal_socket_domain domain, handle->sock.tcp_cork = false; handle->sock.tcp_nodelay = false; handle->sock.ipv6_v6only = false; + handle->sock.udp_cork = false; return handle; } @@ -327,6 +328,7 @@ static int attrquerybyhdl(PAL_HANDLE handle, PAL_STREAM_ATTR* attr) { attr->socket.tcp_cork = handle->sock.tcp_cork; attr->socket.tcp_nodelay = handle->sock.tcp_nodelay; attr->socket.ipv6_v6only = handle->sock.ipv6_v6only; + attr->socket.udp_cork = handle->sock.udp_cork; return 0; }; @@ -498,11 +500,25 @@ static int attrsetbyhdl_tcp(PAL_HANDLE handle, PAL_STREAM_ATTR* attr) { static int attrsetbyhdl_udp(PAL_HANDLE handle, PAL_STREAM_ATTR* attr) { assert(handle->sock.type == PAL_SOCKET_UDP); - return attrsetbyhdl_common(handle, attr); + int ret = attrsetbyhdl_common(handle, attr); + if (ret < 0) { + return ret; + } + + if (attr->socket.udp_cork != handle->sock.udp_cork) { + int val = attr->socket.udp_cork; + int ret = DO_SYSCALL(setsockopt, handle->sock.fd, SOL_UDP, UDP_CORK, &val, sizeof(val)); + if (ret < 0) { + return unix_to_pal_error(ret); + } + handle->sock.udp_cork = attr->socket.udp_cork; + } + + return 0; } static int send(PAL_HANDLE handle, struct pal_iovec* pal_iov, size_t iov_len, size_t* out_size, - struct pal_socket_addr* addr, bool force_nonblocking) { + struct pal_socket_addr* addr, bool force_nonblocking, bool force_cork) { assert(handle->hdr.type == PAL_TYPE_SOCKET); struct sockaddr_storage sa_storage; @@ -524,7 +540,8 @@ static int send(PAL_HANDLE handle, struct pal_iovec* pal_iov, size_t iov_len, si iov[i].iov_len = pal_iov[i].iov_len; } - unsigned int flags = force_nonblocking ? MSG_DONTWAIT : 0; + unsigned int flags = (force_nonblocking ? MSG_DONTWAIT : 0) | + (force_cork ? MSG_MORE : 0); struct msghdr msg = { .msg_name = addr ? &sa_storage : NULL, .msg_namelen = linux_addrlen, @@ -684,11 +701,12 @@ int _PalSocketConnect(PAL_HANDLE handle, struct pal_socket_addr* addr, } int _PalSocketSend(PAL_HANDLE handle, struct pal_iovec* iov, size_t iov_len, size_t* out_size, - struct pal_socket_addr* addr, bool force_nonblocking) { + struct pal_socket_addr* addr, bool force_nonblocking, bool force_cork) { if (!handle->sock.ops->send) { return -PAL_ERROR_NOTSUPPORT; } - return handle->sock.ops->send(handle, iov, iov_len, out_size, addr, force_nonblocking); + return handle->sock.ops->send(handle, iov, iov_len, out_size, addr, force_nonblocking, + force_cork); } int _PalSocketRecv(PAL_HANDLE handle, struct pal_iovec* iov, size_t iov_len, size_t* out_total_size, diff --git a/pal/src/host/skeleton/pal_sockets.c b/pal/src/host/skeleton/pal_sockets.c index 53ddd38948..cfdb4477a0 100644 --- a/pal/src/host/skeleton/pal_sockets.c +++ b/pal/src/host/skeleton/pal_sockets.c @@ -30,7 +30,7 @@ int _PalSocketConnect(PAL_HANDLE handle, struct pal_socket_addr* addr, } int _PalSocketSend(PAL_HANDLE handle, struct pal_iovec* iov, size_t iov_len, size_t* out_size, - struct pal_socket_addr* addr, bool force_nonblocking) { + struct pal_socket_addr* addr, bool force_nonblocking, bool force_cork) { return -PAL_ERROR_NOTIMPLEMENTED; } diff --git a/pal/src/pal_sockets.c b/pal/src/pal_sockets.c index 8527c37786..8e312b0b36 100644 --- a/pal/src/pal_sockets.c +++ b/pal/src/pal_sockets.c @@ -35,9 +35,9 @@ int PalSocketConnect(PAL_HANDLE handle, struct pal_socket_addr* addr, } int PalSocketSend(PAL_HANDLE handle, struct pal_iovec* iov, size_t iov_len, size_t* out_size, - struct pal_socket_addr* addr, bool force_nonblocking) { + struct pal_socket_addr* addr, bool force_nonblocking, bool force_cork) { assert(handle->hdr.type == PAL_TYPE_SOCKET); - return _PalSocketSend(handle, iov, iov_len, out_size, addr, force_nonblocking); + return _PalSocketSend(handle, iov, iov_len, out_size, addr, force_nonblocking, force_cork); } int PalSocketRecv(PAL_HANDLE handle, struct pal_iovec* iov, size_t iov_len, size_t* out_total_size,