diff --git a/CODEOWNERS b/CODEOWNERS index 9d0c0cf202aeb..499e7f2360194 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -457,6 +457,7 @@ /subsys/net/lib/config/ @jukkar @tbursztyka @pfalcon /subsys/net/lib/mqtt/ @jukkar @tbursztyka @rlubos /subsys/net/lib/coap/ @rveerama1 +/subsys/net/lib/sockets/socketpair.c @cfriedt /subsys/net/lib/sockets/ @jukkar @tbursztyka @pfalcon /subsys/net/lib/tls_credentials/ @rlubos /subsys/net/l2/ @jukkar @tbursztyka @@ -497,6 +498,7 @@ /tests/net/lib/http_header_fields/ @jukkar @tbursztyka /tests/net/lib/mqtt_packet/ @jukkar @tbursztyka /tests/net/lib/coap/ @rveerama1 +/tests/net/socket/socketpair/ @cfriedt /tests/net/socket/ @jukkar @tbursztyka @pfalcon /tests/subsys/fs/ @nashif @wentongwu /tests/subsys/settings/ @nvlsianpu diff --git a/include/net/net_ip.h b/include/net/net_ip.h index 8fbeb3b12bb15..256e01a7e2800 100644 --- a/include/net/net_ip.h +++ b/include/net/net_ip.h @@ -45,6 +45,8 @@ extern "C" { #define PF_PACKET 3 /**< Packet family. */ #define PF_CAN 4 /**< Controller Area Network. */ #define PF_NET_MGMT 5 /**< Network management info. */ +#define PF_LOCAL 6 /**< Inter-process communication */ +#define PF_UNIX PF_LOCAL /**< Inter-process communication */ /* Address families. */ #define AF_UNSPEC PF_UNSPEC /**< Unspecified address family. */ @@ -53,6 +55,8 @@ extern "C" { #define AF_PACKET PF_PACKET /**< Packet family. */ #define AF_CAN PF_CAN /**< Controller Area Network. */ #define AF_NET_MGMT PF_NET_MGMT /**< Network management info. */ +#define AF_LOCAL PF_LOCAL /**< Inter-process communication */ +#define AF_UNIX PF_UNIX /**< Inter-process communication */ /** Protocol numbers from IANA/BSD */ enum net_ip_protocol { @@ -341,6 +345,12 @@ struct sockaddr_storage { char data[NET_SOCKADDR_MAX_SIZE - sizeof(sa_family_t)]; }; +/* Socket address struct for UNIX domain sockets */ +struct sockaddr_un { + sa_family_t sun_family; /* AF_UNIX */ + char sun_path[NET_SOCKADDR_MAX_SIZE - sizeof(sa_family_t)]; +}; + struct net_addr { sa_family_t family; union { diff --git a/include/net/socket.h b/include/net/socket.h index 6384ed667388a..4011f6fd2e0c8 100644 --- a/include/net/socket.h +++ b/include/net/socket.h @@ -159,6 +159,20 @@ struct zsock_addrinfo { */ __syscall int zsock_socket(int family, int type, int proto); +/** + * @brief Create an unnamed pair of connected sockets + * + * @details + * @rst + * See `POSIX.1-2017 article + * `__ + * for normative description. + * This function is also exposed as ``socketpair()`` + * if :option:`CONFIG_NET_SOCKETS_POSIX_NAMES` is defined. + * @endrst + */ +__syscall int zsock_socketpair(int family, int type, int proto, int *sv); + /** * @brief Close a network socket * @@ -566,6 +580,11 @@ static inline int socket(int family, int type, int proto) return zsock_socket(family, type, proto); } +static inline int socketpair(int family, int type, int proto, int sv[2]) +{ + return zsock_socketpair(family, type, proto, sv); +} + static inline int close(int sock) { return zsock_close(sock); diff --git a/include/posix/sys/socket.h b/include/posix/sys/socket.h index 11e6e2c5b59d8..6cafb2ede6fb7 100644 --- a/include/posix/sys/socket.h +++ b/include/posix/sys/socket.h @@ -18,6 +18,11 @@ static inline int socket(int family, int type, int proto) return zsock_socket(family, type, proto); } +static inline int socketpair(int family, int type, int proto, int sv[2]) +{ + return zsock_socketpair(family, type, proto, sv); +} + #define SHUT_RD ZSOCK_SHUT_RD #define SHUT_WR ZSOCK_SHUT_WR #define SHUT_RDWR ZSOCK_SHUT_RDWR diff --git a/subsys/net/lib/sockets/CMakeLists.txt b/subsys/net/lib/sockets/CMakeLists.txt index c61620e496f93..42f4637d89bc5 100644 --- a/subsys/net/lib/sockets/CMakeLists.txt +++ b/subsys/net/lib/sockets/CMakeLists.txt @@ -28,4 +28,6 @@ if(CONFIG_SOCKS) zephyr_include_directories(${ZEPHYR_BASE}/subsys/net/lib/socks) endif() +zephyr_sources_ifdef(CONFIG_NET_SOCKETPAIR socketpair.c) + zephyr_link_libraries_ifdef(CONFIG_MBEDTLS mbedTLS) diff --git a/subsys/net/lib/sockets/Kconfig b/subsys/net/lib/sockets/Kconfig index e77080f8f9fbc..2026d0eb569df 100644 --- a/subsys/net/lib/sockets/Kconfig +++ b/subsys/net/lib/sockets/Kconfig @@ -139,6 +139,21 @@ config NET_SOCKETS_CAN_RECEIVERS The value tells how many sockets can receive data from same Socket-CAN interface. +config NET_SOCKETPAIR + bool "Support for the socketpair syscall [EXPERIMENTAL]" + depends on HEAP_MEM_POOL_SIZE != 0 + help + Choose y here if you would like to use the socketpair(2) + system call. + +config NET_SOCKETPAIR_BUFFER_SIZE + int "Size of the intermediate buffer, in bytes" + default 64 + range 1 4096 + depends on NET_SOCKETPAIR + help + Buffer size for socketpair(2) + config NET_SOCKETS_NET_MGMT bool "Enable network management socket support [EXPERIMENTAL]" depends on NET_MGMT_EVENT diff --git a/subsys/net/lib/sockets/socketpair.c b/subsys/net/lib/sockets/socketpair.c new file mode 100644 index 0000000000000..5cec71bdd43ef --- /dev/null +++ b/subsys/net/lib/sockets/socketpair.c @@ -0,0 +1,1121 @@ +/* + * Copyright (c) 2020 Friedt Professional Engineering Services, Inc + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/* Zephyr headers */ +#include +LOG_MODULE_REGISTER(net_spair, CONFIG_NET_SOCKETS_LOG_LEVEL); + +#include +#include +#include +#include +#include + +#include "sockets_internal.h" + +enum { + SPAIR_SIG_CANCEL, /**< operation has been canceled */ + SPAIR_SIG_DATA, /**< @ref spair.recv_q has been updated */ +}; + +enum { + SPAIR_FLAG_NONBLOCK = (1 << 0), /**< socket is non-blocking */ +}; + +#define SPAIR_FLAGS_DEFAULT 0 + +/** + * Socketpair endpoint structure + * + * This structure represents one half of a socketpair (an 'endpoint'). + * + * The implementation strives for compatibility with socketpair(2). + * + * Resources contained within this structure are said to be 'local', while + * reources contained within the other half of the socketpair (or other + * endpoint) are said to be 'remote'. + * + * Theory of operation: + * - each end of a socketpair owns a @a recv_q + * - since there is no write queue, data is either written or not + * - read and write operations may return partial transfers + * - read operations may block if the local @a recv_q is empty + * - write operations may block if the remote @a recv_q is full + * - each endpoint may be blocking or non-blocking + */ +struct spair { + int remote; /**< the remote endpoint file descriptor */ + u32_t flags; /**< status and option bits */ + struct k_sem sem; /**< semaphore for exclusive structure access */ + struct k_pipe recv_q; /**< receive queue of local endpoint */ + /** indicates write of local @a recv_q occurred */ + struct k_poll_signal write_signal; + /** indicates read of local @a recv_q occurred */ + struct k_poll_signal read_signal; + /** buffer for @a recv_q recv_q */ + u8_t buf[CONFIG_NET_SOCKETPAIR_BUFFER_SIZE]; +}; + +/* forward declaration */ +static const struct socket_op_vtable spair_fd_op_vtable; + +#undef sock_is_nonblock +/** Determine if a @ref spair is in non-blocking mode */ +static inline bool sock_is_nonblock(const struct spair *spair) +{ + return !!(spair->flags & SPAIR_FLAG_NONBLOCK); +} + +/** Determine if a @ref spair is connected */ +static inline bool sock_is_connected(const struct spair *spair) +{ + const struct spair *remote = z_get_fd_obj(spair->remote, + (const struct fd_op_vtable *)&spair_fd_op_vtable, 0); + + if (remote == NULL) { + return false; + } + + return true; +} + +#undef sock_is_eof +/** Determine if a @ref spair has encountered end-of-file */ +static inline bool sock_is_eof(const struct spair *spair) +{ + return !sock_is_connected(spair); +} + +/** + * Determine bytes available to write + * + * Specifically, this function calculates the number of bytes that may be + * written to a given @ref spair without blocking. + */ +static inline size_t spair_write_avail(struct spair *spair) +{ + struct spair *const remote = z_get_fd_obj(spair->remote, + (const struct fd_op_vtable *)&spair_fd_op_vtable, 0); + + if (remote == NULL) { + return 0; + } + + return k_pipe_write_avail(&remote->recv_q); +} + +/** + * Determine bytes available to read + * + * Specifically, this function calculates the number of bytes that may be + * read from a given @ref spair without blocking. + */ +static inline size_t spair_read_avail(struct spair *spair) +{ + return k_pipe_read_avail(&spair->recv_q); +} + +/** Swap two 32-bit integers */ +static inline void swap32(u32_t *a, u32_t *b) +{ + u32_t c; + + c = *b; + *b = *a; + *a = c; +} + +/** + * Delete @param spair + * + * This function deletes one endpoint of a socketpair. + * + * Theory of operation: + * - we have a socketpair with two endpoints: A and B + * - we have two threads: T1 and T2 + * - T1 operates on endpoint A + * - T2 operates on endpoint B + * + * There are two possible cases where a blocking operation must be notified + * when one endpoint is closed: + * -# T1 is blocked reading from A and T2 closes B + * T1 waits on A's write signal. T2 triggers the remote + * @ref spair.write_signal + * -# T1 is blocked writing to A and T2 closes B + * T1 is waits on B's read signal. T2 triggers the local + * @ref spair.read_signal. + * + * If the remote endpoint is already closed, the former operation does not + * take place. Otherwise, the @ref spair.remote of the local endpoint is + * set to -1. + * + * If no threads are blocking on A, then the signals have no effect. + * + * The memeory associated with the local endpoint is cleared and freed. + */ +static void spair_delete(struct spair *spair) +{ + int res; + struct spair *remote = NULL; + bool have_remote_sem = false; + + if (spair == NULL) { + return; + } + + if (spair->remote != -1) { + remote = z_get_fd_obj(spair->remote, + (const struct fd_op_vtable *)&spair_fd_op_vtable, 0); + + if (remote != NULL) { + res = k_sem_take(&remote->sem, K_FOREVER); + if (res == 0) { + have_remote_sem = true; + remote->remote = -1; + res = k_poll_signal_raise(&remote->write_signal, + SPAIR_SIG_CANCEL); + __ASSERT(res == 0, + "k_poll_signal_raise() failed: %d", + res); + } + } + } + + spair->remote = -1; + + res = k_poll_signal_raise(&spair->read_signal, SPAIR_SIG_CANCEL); + __ASSERT(res == 0, "k_poll_signal_raise() failed: %d", res); + + /* ensure no private information is released to the memory pool */ + memset(spair, 0, sizeof(*spair)); + + k_free(spair); + + if (remote != NULL && have_remote_sem) { + k_sem_give(&remote->sem); + } +} + +/** + * Create a @ref spair (1/2 of a socketpair) + * + * The idea is to call this twice, but store the "local" side in the + * @ref spair.remote field initially. + * + * If both allocations are successful, then swap the @ref spair.remote + * fields in the two @ref spair instances. + */ +static struct spair *spair_new(void) +{ + struct spair *spair; + + spair = k_malloc(sizeof(*spair)); + if (spair == NULL) { + errno = ENOMEM; + goto out; + } + memset(spair, 0, sizeof(*spair)); + + /* initialize any non-zero default values */ + spair->remote = -1; + spair->flags = SPAIR_FLAGS_DEFAULT; + + k_sem_init(&spair->sem, 1, 1); + k_pipe_init(&spair->recv_q, spair->buf, sizeof(spair->buf)); + k_poll_signal_init(&spair->write_signal); + k_poll_signal_init(&spair->read_signal); + + spair->remote = z_reserve_fd(); + if (spair->remote == -1) { + errno = ENFILE; + goto cleanup; + } + + z_finalize_fd(spair->remote, spair, + (const struct fd_op_vtable *)&spair_fd_op_vtable); + + goto out; + +cleanup: + spair_delete(spair); + spair = NULL; + +out: + return spair; +} + +int z_impl_zsock_socketpair(int family, int type, int proto, int *sv) +{ + int res; + size_t i; + struct spair *obj[2] = {}; + + if (family != AF_UNIX) { + errno = EAFNOSUPPORT; + res = -1; + goto errout; + } + + if (type != SOCK_STREAM) { + errno = EPROTOTYPE; + res = -1; + goto errout; + } + + if (proto != 0) { + errno = EPROTONOSUPPORT; + res = -1; + goto errout; + } + + if (sv == NULL) { + /* not listed in normative spec, but mimics Linux behaviour */ + errno = EFAULT; + res = -1; + goto errout; + } + + for (i = 0; i < 2; ++i) { + obj[i] = spair_new(); + if (!obj[i]) { + res = -1; + goto cleanup; + } + } + + /* connect the two endpoints */ + swap32(&obj[0]->remote, &obj[1]->remote); + + for (i = 0; i < 2; ++i) { + sv[i] = obj[i]->remote; + k_sem_give(&obj[0]->sem); + } + + return 0; + +cleanup: + for (i = 0; i < 2; ++i) { + spair_delete(obj[i]); + } + +errout: + return res; +} + +#ifdef CONFIG_USERSPACE +int z_vrfy_zsock_socketpair(int family, int type, int proto, + int *sv) +{ + int ret; + int tmp[2]; + + if (Z_SYSCALL_MEMORY_WRITE(sv, sizeof(tmp)) != 0) { + /* not listed in normative spec, but mimics linux behaviour */ + errno = EFAULT; + ret = -1; + goto out; + } + + ret = z_impl_zsock_socketpair(family, type, proto, tmp); + if (ret == 0) { + Z_OOPS(z_user_to_copy(sv, tmp, sizeof(tmp))); + } + +out: + return ret; +} + +#include +#endif /* CONFIG_USERSPACE */ + +/** + * Write data to one end of a @ref spair + * + * Data written on one file descriptor of a socketpair can be read at the + * other end using common POSIX calls such as read(2) or recv(2). + * + * If the underlying file descriptor has the @ref O_NONBLOCK flag set then + * this function will return immediately. If no data was written on a + * non-blocking file descriptor, then -1 will be returned and @ref errno will + * be set to @ref EAGAIN. + * + * Blocking write operations occur when the @ref O_NONBLOCK flag is @em not + * set and there is insufficient space in the @em remote @ref spair.pipe. + * + * Such a blocking write will suspend execution of the current thread until + * one of two possible results is received on the @em remote + * @ref spair.read_signal: + * + * 1) @ref SPAIR_SIG_DATA - data has been read from the @em remote + * @ref spair.pipe. Thus, allowing more data to be written. + * + * 2) @ref SPAIR_SIG_CANCEL - the @em remote socketpair endpoint was closed + * Receipt of this result is analagous to SIGPIPE from POSIX + * ("Write on a pipe with no one to read it."). In this case, the function + * will return -1 and set @ref errno to @ref EPIPE. + * + * @param obj the address of an @ref spair object cast to `void *` + * @param buffer the buffer to write + * @param count the number of bytes to write from @p buffer + * + * @return on success, a number > 0 representing the number of bytes written + * @return -1 on error, with @ref errno set appropriately. + */ +static ssize_t spair_write(void *obj, const void *buffer, size_t count) +{ + int res; + bool is_connected; + size_t avail; + bool is_nonblock; + bool will_block; + size_t bytes_written; + bool have_local_sem = false; + bool have_remote_sem = false; + struct spair *const spair = (struct spair *)obj; + struct spair *remote = NULL; + + if (obj == NULL || buffer == NULL || count == 0) { + errno = EINVAL; + res = -1; + goto out; + } + + is_nonblock = sock_is_nonblock(spair); + + res = k_sem_take(&spair->sem, K_NO_WAIT); + if (res < 0) { + if (is_nonblock) { + errno = EAGAIN; + res = -1; + goto out; + } + + res = k_sem_take(&spair->sem, K_FOREVER); + if (res < 0) { + errno = -res; + res = -1; + goto out; + } + } + + have_local_sem = true; + + remote = z_get_fd_obj(spair->remote, + (const struct fd_op_vtable *)&spair_fd_op_vtable, 0); + + is_connected = sock_is_connected(spair); + is_nonblock = sock_is_nonblock(spair); + + if (!is_connected) { + errno = EPIPE; + res = -1; + goto out; + } + + res = k_sem_take(&remote->sem, K_NO_WAIT); + if (res < 0) { + if (is_nonblock) { + errno = EAGAIN; + res = -1; + goto out; + } + res = k_sem_take(&remote->sem, K_FOREVER); + if (res < 0) { + errno = -res; + res = -1; + goto out; + } + } + + have_remote_sem = true; + + avail = is_connected ? spair_write_avail(spair) : 0; + if (avail == 0 && is_nonblock) { + errno = EAGAIN; + res = -1; + goto out; + } + + will_block = (count > avail) && !is_nonblock; + if (will_block) { + + for (int signaled = false, result = -1; !signaled; + result = -1) { + + struct k_poll_event events[] = { + K_POLL_EVENT_INITIALIZER( + K_POLL_TYPE_SIGNAL, + K_POLL_MODE_NOTIFY_ONLY, + &remote->read_signal), + }; + + k_sem_give(&remote->sem); + have_remote_sem = false; + + res = k_poll(events, ARRAY_SIZE(events), K_FOREVER); + if (res < 0) { + errno = -res; + res = -1; + goto out; + } + + res = k_sem_take(&remote->sem, K_NO_WAIT); + if (res < 0) { + if (is_nonblock) { + errno = -res; + res = -1; + goto out; + } + res = k_sem_take(&remote->sem, K_FOREVER); + if (res < 0) { + errno = -res; + res = -1; + goto out; + } + } + + have_remote_sem = true; + + k_poll_signal_check(&remote->read_signal, &signaled, + &result); + if (!signaled) { + continue; + } + + switch (result) { + case SPAIR_SIG_DATA: { + break; + } + + case SPAIR_SIG_CANCEL: { + errno = EPIPE; + res = -1; + goto out; + } + + default: { + __ASSERT(false, + "unrecognized result: %d", + result); + continue; + } + } + + /* SPAIR_SIG_DATA was received */ + break; + } + } + + res = k_pipe_put(&remote->recv_q, (void *)buffer, count, + &bytes_written, 1, K_NO_WAIT); + __ASSERT(res == 0, "k_pipe_put() failed: %d", res); + + res = k_poll_signal_raise(&remote->write_signal, SPAIR_SIG_DATA); + __ASSERT(res == 0, "k_poll_signal_raise() failed: %d", res); + + res = bytes_written; + +out: + + if (remote != NULL && have_remote_sem) { + k_sem_give(&remote->sem); + } + if (spair != NULL && have_local_sem) { + k_sem_give(&spair->sem); + } + + return res; +} + +/** + * Read data from one end of a @ref spair + * + * Data written on one file descriptor of a socketpair (with e.g. write(2) or + * send(2)) can be read at the other end using common POSIX calls such as + * read(2) or recv(2). + * + * If the underlying file descriptor has the @ref O_NONBLOCK flag set then + * this function will return immediately. If no data was read from a + * non-blocking file descriptor, then -1 will be returned and @ref errno will + * be set to @ref EAGAIN. + * + * Blocking read operations occur when the @ref O_NONBLOCK flag is @em not set + * and there are no bytes to read in the @em local @ref spair.pipe. + * + * Such a blocking read will suspend execution of the current thread until + * one of two possible results is received on the @em local + * @ref spair.write_signal: + * + * -# @ref SPAIR_SIG_DATA - data has been written to the @em local + * @ref spair.pipe. Thus, allowing more data to be read. + * + * -# @ref SPAIR_SIG_CANCEL - read of the the @em local @spair.pipe + * must be cancelled for some reason (e.g. the file descriptor will be + * closed imminently). In this case, the function will return -1 and set + * @ref errno to @ref EINTR. + * + * @param obj the address of an @ref spair object cast to `void *` + * @param buffer the buffer in which to read + * @param count the number of bytes to read + * + * @return on success, a number > 0 representing the number of bytes written + * @return -1 on error, with @ref errno set appropriately. + */ +static ssize_t spair_read(void *obj, void *buffer, size_t count) +{ + int res; + + bool is_connected; + size_t avail; + bool is_nonblock; + bool will_block; + size_t bytes_read; + + bool have_local_sem = false; + struct spair *const spair = (struct spair *)obj; + + if (obj == NULL || buffer == NULL || count == 0) { + errno = EINVAL; + res = -1; + goto out; + } + + is_nonblock = sock_is_nonblock(spair); + + res = k_sem_take(&spair->sem, K_NO_WAIT); + if (res < 0) { + if (is_nonblock) { + errno = EAGAIN; + res = -1; + goto out; + } + + res = k_sem_take(&spair->sem, K_FOREVER); + if (res < 0) { + errno = -res; + res = -1; + goto out; + } + } + + have_local_sem = true; + + is_connected = sock_is_connected(spair); + avail = spair_read_avail(spair); + will_block = (avail == 0) && !is_nonblock; + + if (avail == 0 && !is_connected) { + /* signal EOF */ + res = 0; + goto out; + } + + if (avail == 0 && is_nonblock) { + errno = EAGAIN; + res = -1; + goto out; + } + + if (will_block) { + + for (int signaled = false, result = -1; !signaled; + result = -1) { + + struct k_poll_event events[] = { + K_POLL_EVENT_INITIALIZER( + K_POLL_TYPE_SIGNAL, + K_POLL_MODE_NOTIFY_ONLY, + &spair->write_signal + ), + }; + + k_sem_give(&spair->sem); + have_local_sem = false; + + res = k_poll(events, ARRAY_SIZE(events), K_FOREVER); + __ASSERT(res == 0, "k_poll() failed: %d", res); + + res = k_sem_take(&spair->sem, K_FOREVER); + __ASSERT(res == 0, "failed to take local sem: %d", res); + + have_local_sem = true; + + k_poll_signal_check(&spair->write_signal, &signaled, + &result); + if (!signaled) { + continue; + } + + switch (result) { + case SPAIR_SIG_DATA: { + break; + } + + case SPAIR_SIG_CANCEL: { + errno = EPIPE; + res = -1; + goto out; + } + + default: { + __ASSERT(false, + "unrecognized result: %d", + result); + continue; + } + } + + /* SPAIR_SIG_DATA was received */ + break; + } + } + + res = k_pipe_get(&spair->recv_q, (void *)buffer, count, &bytes_read, + 1, K_NO_WAIT); + __ASSERT(res == 0, "k_pipe_get() failed: %d", res); + + if (is_connected) { + res = k_poll_signal_raise(&spair->read_signal, SPAIR_SIG_DATA); + __ASSERT(res == 0, "k_poll_signal_raise() failed: %d", res); + } + + res = bytes_read; + +out: + + if (spair != NULL && have_local_sem) { + k_sem_give(&spair->sem); + } + + return res; +} + +static int zsock_poll_prepare_ctx(struct spair *const spair, + struct zsock_pollfd *const pfd, + struct k_poll_event **pev, + struct k_poll_event *pev_end) +{ + int res; + + struct spair *remote = NULL; + bool have_remote_sem = false; + + if (pfd->events & ZSOCK_POLLIN) { + + /* Tell poll() to short-circuit wait */ + if (sock_is_eof(spair)) { + res = -EALREADY; + goto out; + } + + if (*pev == pev_end) { + res = -ENOMEM; + goto out; + } + + /* Wait until data has been written to the local end */ + (*pev)->obj = &spair->write_signal; + } + + if (pfd->events & ZSOCK_POLLOUT) { + + /* Tell poll() to short-circuit wait */ + if (!sock_is_connected(spair)) { + res = -EALREADY; + goto out; + } + + if (*pev == pev_end) { + res = -ENOMEM; + goto out; + } + + remote = z_get_fd_obj(spair->remote, + (const struct fd_op_vtable *) + &spair_fd_op_vtable, 0); + + __ASSERT(remote != NULL, "remote is NULL"); + + res = k_sem_take(&remote->sem, K_FOREVER); + if (res < 0) { + goto out; + } + + have_remote_sem = true; + + /* Wait until data has been read from the remote end */ + (*pev)->obj = &remote->read_signal; + } + + (*pev)->type = K_POLL_TYPE_SIGNAL; + (*pev)->mode = K_POLL_MODE_NOTIFY_ONLY; + (*pev)->state = K_POLL_STATE_NOT_READY; + k_poll_signal_reset((*pev)->obj); + + (*pev)++; + + res = 0; + +out: + + if (remote != NULL && have_remote_sem) { + k_sem_give(&remote->sem); + } + + return res; +} + +static int zsock_poll_update_ctx(struct spair *const spair, + struct zsock_pollfd *const pfd, + struct k_poll_event **pev) +{ + int res; + int signaled; + int result; + struct spair *remote = NULL; + bool have_remote_sem = false; + + if (pfd->events & ZSOCK_POLLOUT) { + if (!sock_is_connected(spair)) { + pfd->revents |= ZSOCK_POLLHUP; + goto pollout_done; + } + + remote = z_get_fd_obj(spair->remote, + (const struct fd_op_vtable *) &spair_fd_op_vtable, 0); + + __ASSERT(remote != NULL, "remote is NULL"); + + res = k_sem_take(&remote->sem, K_FOREVER); + if (res < 0) { + /* if other end is deleted, this might occur */ + goto pollout_done; + } + + have_remote_sem = true; + + if (spair_write_avail(spair) > 0) { + pfd->revents |= ZSOCK_POLLOUT; + goto pollout_done; + } + + /* check to see if op was canceled */ + signaled = false; + k_poll_signal_check(&remote->read_signal, &signaled, &result); + if (signaled) { + /* Cannot be SPAIR_SIG_DATA, because + * spair_write_avail() would have + * returned 0 + */ + __ASSERT(result == SPAIR_SIG_CANCEL, + "invalid result %d", result); + pfd->revents |= ZSOCK_POLLHUP; + } + } + +pollout_done: + + if (pfd->events & ZSOCK_POLLIN) { + if (sock_is_eof(spair)) { + pfd->revents |= ZSOCK_POLLIN; + goto pollin_done; + } + + if (spair_read_avail(spair) > 0) { + pfd->revents |= ZSOCK_POLLIN; + goto pollin_done; + } + + /* check to see if op was canceled */ + signaled = false; + k_poll_signal_check(&spair->write_signal, &signaled, &result); + if (signaled) { + /* Cannot be SPAIR_SIG_DATA, because + * spair_read_avail() would have + * returned 0 + */ + __ASSERT(result == SPAIR_SIG_CANCEL, + "invalid result %d", result); + pfd->revents |= ZSOCK_POLLIN; + } + } + +pollin_done: + res = 0; + + (*pev)++; + + if (remote != NULL && have_remote_sem) { + k_sem_give(&remote->sem); + } + + return res; +} + +static int spair_ioctl(void *obj, unsigned int request, va_list args) +{ + int res; + struct zsock_pollfd *pfd; + struct k_poll_event **pev; + struct k_poll_event *pev_end; + int flags = 0; + bool have_local_sem = false; + struct spair *const spair = (struct spair *)obj; + + if (spair == NULL) { + errno = EINVAL; + res = -1; + goto out; + } + + /* The local sem is always taken in this function. If a subsequent + * function call requires the remote sem, it must acquire and free the + * remote sem. + */ + res = k_sem_take(&spair->sem, K_FOREVER); + __ASSERT(res == 0, "failed to take local sem: %d", res); + + have_local_sem = true; + + switch (request) { + case F_GETFL: { + if (sock_is_nonblock(spair)) { + flags |= O_NONBLOCK; + } + + res = flags; + goto out; + } + + case F_SETFL: { + flags = va_arg(args, int); + + if (flags & O_NONBLOCK) { + spair->flags |= SPAIR_FLAG_NONBLOCK; + } else { + spair->flags &= ~SPAIR_FLAG_NONBLOCK; + } + + res = 0; + goto out; + } + + case ZFD_IOCTL_CLOSE: { + /* disconnect the remote endpoint */ + spair_delete(spair); + have_local_sem = false; + res = 0; + goto out; + } + + case ZFD_IOCTL_POLL_PREPARE: { + pfd = va_arg(args, struct zsock_pollfd *); + pev = va_arg(args, struct k_poll_event **); + pev_end = va_arg(args, struct k_poll_event *); + + res = zsock_poll_prepare_ctx(obj, pfd, pev, pev_end); + goto out; + } + + case ZFD_IOCTL_POLL_UPDATE: { + pfd = va_arg(args, struct zsock_pollfd *); + pev = va_arg(args, struct k_poll_event **); + + res = zsock_poll_update_ctx(obj, pfd, pev); + goto out; + } + + default: { + errno = EOPNOTSUPP; + res = -1; + goto out; + } + } + +out: + if (spair != NULL && have_local_sem) { + k_sem_give(&spair->sem); + } + + return res; +} + +static int spair_bind(void *obj, const struct sockaddr *addr, + socklen_t addrlen) +{ + ARG_UNUSED(obj); + ARG_UNUSED(addr); + ARG_UNUSED(addrlen); + + errno = EISCONN; + return -1; +} + +static int spair_connect(void *obj, const struct sockaddr *addr, + socklen_t addrlen) +{ + ARG_UNUSED(obj); + ARG_UNUSED(addr); + ARG_UNUSED(addrlen); + + errno = EISCONN; + return -1; +} + +static int spair_listen(void *obj, int backlog) +{ + ARG_UNUSED(obj); + ARG_UNUSED(backlog); + + errno = EINVAL; + return -1; +} + +static int spair_accept(void *obj, struct sockaddr *addr, + socklen_t *addrlen) +{ + ARG_UNUSED(obj); + ARG_UNUSED(addr); + ARG_UNUSED(addrlen); + + errno = EOPNOTSUPP; + return -1; +} + +static ssize_t spair_sendto(void *obj, const void *buf, size_t len, + int flags, const struct sockaddr *dest_addr, + socklen_t addrlen) +{ + ARG_UNUSED(flags); + ARG_UNUSED(dest_addr); + ARG_UNUSED(addrlen); + + return spair_write(obj, buf, len); +} + +static ssize_t spair_sendmsg(void *obj, const struct msghdr *msg, + int flags) +{ + ARG_UNUSED(flags); + + int res; + size_t len = 0; + struct spair *const spair = (struct spair *)obj; + const bool is_connected = sock_is_connected(spair); + const size_t avail = is_connected ? spair_write_avail(spair) : 0; + const bool is_nonblock = sock_is_nonblock(spair); + + if (spair == NULL || msg == NULL) { + errno = EINVAL; + res = -1; + goto out; + } + + for (size_t i = 0; i < msg->msg_iovlen; ++i) { + /* check & msg->msg_iov[i]? */ + /* check & msg->msg_iov[i].iov_base? */ + len += msg->msg_iov[i].iov_len; + } + + if (!is_connected) { + errno = EPIPE; + res = -1; + goto out; + } + + if (len == 0) { + res = 0; + goto out; + } + + if (len > avail && is_nonblock) { + errno = EMSGSIZE; + res = -1; + goto out; + } + + for (size_t i = 0; i < msg->msg_iovlen; ++i) { + res = spair_write(spair, msg->msg_iov[i].iov_base, + msg->msg_iov[i].iov_len); + if (res == -1) { + goto out; + } + } + + res = len; + +out: + return res; +} + +static ssize_t spair_recvfrom(void *obj, void *buf, size_t max_len, + int flags, struct sockaddr *src_addr, + socklen_t *addrlen) +{ + (void)flags; + (void)src_addr; + (void)addrlen; + + if (addrlen != NULL) { + /* Protocol (PF_UNIX) does not support addressing with connected + * sockets and, therefore, it is unspecified behaviour to modify + * src_addr. However, it would be ambiguous to leave addrlen + * untouched if the user expects it to be updated. It is not + * mentioned that modifying addrlen is unspecified. Therefore + * we choose to eliminate ambiguity. + * + * Setting it to zero mimics Linux's behaviour. + */ + *addrlen = 0; + } + + return spair_read(obj, buf, max_len); +} + +static int spair_getsockopt(void *obj, int level, int optname, + void *optval, socklen_t *optlen) +{ + ARG_UNUSED(obj); + ARG_UNUSED(level); + ARG_UNUSED(optname); + ARG_UNUSED(optval); + ARG_UNUSED(optlen); + + errno = ENOPROTOOPT; + return -1; +} + +static int spair_setsockopt(void *obj, int level, int optname, + const void *optval, socklen_t optlen) +{ + ARG_UNUSED(obj); + ARG_UNUSED(level); + ARG_UNUSED(optname); + ARG_UNUSED(optval); + ARG_UNUSED(optlen); + + errno = ENOPROTOOPT; + return -1; +} + +static const struct socket_op_vtable spair_fd_op_vtable = { + .fd_vtable = { + .read = spair_read, + .write = spair_write, + .ioctl = spair_ioctl, + }, + .bind = spair_bind, + .connect = spair_connect, + .listen = spair_listen, + .accept = spair_accept, + .sendto = spair_sendto, + .sendmsg = spair_sendmsg, + .recvfrom = spair_recvfrom, + .getsockopt = spair_getsockopt, + .setsockopt = spair_setsockopt, +}; diff --git a/tests/net/socket/socketpair/CMakeLists.txt b/tests/net/socket/socketpair/CMakeLists.txt new file mode 100644 index 0000000000000..27b977b095b55 --- /dev/null +++ b/tests/net/socket/socketpair/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) +find_package(Zephyr HINTS $ENV{ZEPHYR_BASE}) +project(socketpair) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/net/socket/socketpair/prj.conf b/tests/net/socket/socketpair/prj.conf new file mode 100644 index 0000000000000..2d43936f3b0d6 --- /dev/null +++ b/tests/net/socket/socketpair/prj.conf @@ -0,0 +1,34 @@ +CONFIG_MP_NUM_CPUS=1 + +# General config +CONFIG_NEWLIB_LIBC=y + +# Networking config +CONFIG_NETWORKING=y +CONFIG_NET_TEST=y +CONFIG_NET_LOOPBACK=y +CONFIG_NET_IPV4=y +CONFIG_NET_IPV6=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_SOCKETPAIR=y +CONFIG_NET_SOCKETPAIR_BUFFER_SIZE=64 +CONFIG_NET_SOCKETS_POSIX_NAMES=y +# Defines fd_set size +CONFIG_POSIX_MAX_FDS=33 + +# Network driver config +CONFIG_TEST_RANDOM_GENERATOR=y + +# Network address config +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1" +CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1" + +CONFIG_MAIN_STACK_SIZE=2048 +CONFIG_ZTEST=y + +# User mode requirements +CONFIG_TEST_USERSPACE=y +CONFIG_HEAP_MEM_POOL_SIZE=2048 + +CONFIG_QEMU_TICKLESS_WORKAROUND=y diff --git a/tests/net/socket/socketpair/src/main.c b/tests/net/socket/socketpair/src/main.c new file mode 100644 index 0000000000000..046f17b0d1780 --- /dev/null +++ b/tests/net/socket/socketpair/src/main.c @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2020 Friedt Professional Engineering Services, Inc + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/* in happy_path.c */ +extern void test_socketpair_AF_LOCAL__SOCK_STREAM__0(void); +extern void test_socketpair_AF_UNIX__SOCK_STREAM__0(void); + +/* in nonblock.c */ +extern void test_socketpair_write_nonblock(void); +extern void test_socketpair_read_nonblock(void); + +/* in block.c */ +extern void test_socketpair_write_block(void); +extern void test_socketpair_read_block(void); + +/* in closed_ends.c */ +extern void test_socketpair_close_one_end_and_write_to_the_other(void); +extern void test_socketpair_close_one_end_and_read_from_the_other(void); + +/* in expected_failures.c */ +extern void test_socketpair_expected_failures(void); + +/* in unsupported_calls.c */ +extern void test_socketpair_unsupported_calls(void); + +/* in fcntl.c */ +extern void test_socketpair_fcntl(void); + +/* in poll.c */ +extern void test_socketpair_poll_timeout(void); +extern void test_socketpair_poll_timeout_nonblocking(void); +extern void test_socketpair_poll_close_remote_end_POLLIN(void); +extern void test_socketpair_poll_close_remote_end_POLLOUT(void); +extern void test_socketpair_poll_immediate_data(void); +extern void test_socketpair_poll_delayed_data(void); + +void test_main(void) +{ + k_thread_system_pool_assign(k_current_get()); + + ztest_test_suite( + socketpair, + ztest_user_unit_test(test_socketpair_AF_LOCAL__SOCK_STREAM__0), + ztest_user_unit_test(test_socketpair_AF_UNIX__SOCK_STREAM__0), + + ztest_user_unit_test(test_socketpair_write_nonblock), + ztest_user_unit_test(test_socketpair_read_nonblock), + + ztest_user_unit_test( + test_socketpair_close_one_end_and_write_to_the_other), + ztest_user_unit_test( + test_socketpair_close_one_end_and_read_from_the_other), + + ztest_user_unit_test(test_socketpair_expected_failures), + + ztest_user_unit_test(test_socketpair_unsupported_calls), + + ztest_user_unit_test(test_socketpair_fcntl), + + ztest_user_unit_test(test_socketpair_poll_timeout), + ztest_user_unit_test( + test_socketpair_poll_timeout_nonblocking), + ztest_user_unit_test(test_socketpair_poll_immediate_data) + ); + + ztest_run_test_suite(socketpair); + +/* 20200509: @cfriedt: experiencing some issues with userspace thread + * / memory permissions that will require some sorting out. Currently + * these tests succeeed for native_posix_64. + * + * This feature is experimental at present. + */ +#if 0 + ztest_test_suite( + socketpair_only_kernel, + ztest_user_unit_test(test_socketpair_write_block), + ztest_user_unit_test(test_socketpair_read_block), + ztest_user_unit_test(test_socketpair_poll_delayed_data), + ztest_user_unit_test( + test_socketpair_poll_close_remote_end_POLLIN), + ztest_user_unit_test( + test_socketpair_poll_close_remote_end_POLLOUT) + ); + + ztest_run_test_suite(socketpair_only_kernel); +#endif +} diff --git a/tests/net/socket/socketpair/src/test_socketpair_block.c b/tests/net/socket/socketpair/src/test_socketpair_block.c new file mode 100644 index 0000000000000..b5a9cada0a6d9 --- /dev/null +++ b/tests/net/socket/socketpair/src/test_socketpair_block.c @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2020 Friedt Professional Engineering Services, Inc + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL); + +#include +#include +#include + +#include + +#undef read +#define read(fd, buf, len) zsock_recv(fd, buf, len, 0) + +#undef write +#define write(fd, buf, len) zsock_send(fd, buf, len, 0) + +#define TIMEOUT K_FOREVER + +struct ctx { + /* true if test is write_block(), false if test is read_block() */ + bool write; + /* the secondary-side socket of the socketpair */ + int fd; + /* the count of the main thread */ + atomic_t m; + /* the count of the secondary thread */ + size_t n; + /* true when secondary thread is done */ + bool done; + /* true if both main and secondary thread should immediately quit */ + bool fail; + /* thread id of the secondary thread */ + k_tid_t tid; +}; +ZTEST_BMEM struct ctx ctx; +#define STACK_SIZE 512 +/* thread structure for secondary thread */ +ZTEST_BMEM struct k_thread th; +/* stack for the secondary thread */ +static K_THREAD_STACK_DEFINE(th_stack, STACK_SIZE); + +static void th_fun(void *arg0, void *arg1, void *arg2) +{ + (void) arg0; + (void) arg1; + (void) arg2; + + int res; + char c = '\0'; + + LOG_DBG("secondary thread running"); + + while (true) { + if (ctx.write) { + LOG_DBG("ctx.m: %u", ctx.m); + if (atomic_get(&ctx.m) + < CONFIG_NET_SOCKETPAIR_BUFFER_SIZE) { + continue; + } + LOG_DBG("ready to read!"); + } else { + LOG_DBG("sleeping for 100ms.."); + k_sleep(K_MSEC(100)); + LOG_DBG("ready to write!"); + } + break; + } + + LOG_DBG("%sing 1 byte %s fd %d", ctx.write ? "read" : "write", + ctx.write ? "from" : "to", ctx.fd); + if (ctx.write) { + res = read(ctx.fd, &c, 1); + } else { + res = write(ctx.fd, "x", 1); + } + if (-1 == res || 1 != res) { + LOG_DBG("%s(2) failed: %d", ctx.write ? "read" : "write", + errno); + goto out; + } + LOG_DBG("%s 1 byte", ctx.write ? "read" : "wrote"); + ctx.n = 1; + +out: + ctx.done = true; + + LOG_DBG("terminating.."); +} + +void test_socketpair_write_block(void) +{ + int res; + int sv[2] = {-1, -1}; + + LOG_DBG("creating socketpair.."); + res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + zassert_equal(res, 0, "socketpair(2) failed: %d", errno); + + for (size_t i = 0; i < 2; ++i) { + + LOG_DBG("data direction %d -> %d", sv[i], sv[(!i) & 1]); + + LOG_DBG("setting up context"); + memset(&ctx, 0, sizeof(ctx)); + ctx.write = true; + ctx.fd = sv[(!i) & 1]; + + LOG_DBG("creating secondary thread"); + ctx.tid = k_thread_create(&th, th_stack, + STACK_SIZE, th_fun, + NULL, NULL, NULL, + CONFIG_MAIN_THREAD_PRIORITY, + K_INHERIT_PERMS, K_NO_WAIT); + LOG_DBG("created secondary thread %p", ctx.tid); + + /* fill up the buffer */ + for (ctx.m = 0; atomic_get(&ctx.m) + < CONFIG_NET_SOCKETPAIR_BUFFER_SIZE;) { + + res = write(sv[i], "x", 1); + zassert_not_equal(res, -1, "write(2) failed: %d", + errno); + zassert_equal(res, 1, "wrote %d bytes instead of 1", + res); + + atomic_inc(&ctx.m); + LOG_DBG("have written %u bytes", ctx.m); + } + + /* try to write one more byte */ + LOG_DBG("writing to fd %d", sv[i]); + res = write(sv[i], "x", 1); + zassert_not_equal(res, -1, "write(2) failed: %d", errno); + zassert_equal(res, 1, "wrote %d bytes instead of 1", res); + + LOG_DBG("success!"); + + k_thread_join(&th, K_MSEC(1000)); + } + + close(sv[0]); + close(sv[1]); +} + +void test_socketpair_read_block(void) +{ + int res; + int sv[2] = {-1, -1}; + char x; + + LOG_DBG("creating socketpair.."); + res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + zassert_equal(res, 0, "socketpair(2) failed: %d", errno); + + for (size_t i = 0; i < 2; ++i) { + + LOG_DBG("data direction %d -> %d", sv[i], sv[(!i) & 1]); + + LOG_DBG("setting up context"); + memset(&ctx, 0, sizeof(ctx)); + ctx.write = false; + ctx.fd = sv[(!i) & 1]; + + LOG_DBG("creating secondary thread"); + ctx.tid = k_thread_create(&th, th_stack, + STACK_SIZE, th_fun, + NULL, NULL, NULL, + CONFIG_MAIN_THREAD_PRIORITY, + K_INHERIT_PERMS, K_NO_WAIT); + LOG_DBG("created secondary thread %p", ctx.tid); + + /* try to read one byte */ + LOG_DBG("reading from fd %d", sv[i]); + x = '\0'; + res = read(sv[i], &x, 1); + zassert_not_equal(res, -1, "read(2) failed: %d", errno); + zassert_equal(res, 1, "read %d bytes instead of 1", res); + + LOG_DBG("success!"); + + k_thread_join(&th, K_MSEC(1000)); + } + + close(sv[0]); + close(sv[1]); +} diff --git a/tests/net/socket/socketpair/src/test_socketpair_closed_ends.c b/tests/net/socket/socketpair/src/test_socketpair_closed_ends.c new file mode 100644 index 0000000000000..163f65904c047 --- /dev/null +++ b/tests/net/socket/socketpair/src/test_socketpair_closed_ends.c @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2020 Friedt Professional Engineering Services, Inc + */ +#include + +#include +LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL); + +#include +#include +#include +#include +#include + +#include + +#undef read +#define read(fd, buf, len) zsock_recv(fd, buf, len, 0) + +#undef write +#define write(fd, buf, len) zsock_send(fd, buf, len, 0) + +void test_socketpair_close_one_end_and_write_to_the_other(void) +{ + int res; + int sv[2] = {-1, -1}; + + for (size_t i = 0; i < 2; ++i) { + res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + zassert_equal(res, 0, "socketpair(2) failed: %d", errno); + + res = close(sv[i]); + zassert_equal(res, 0, "close(sv[%u]) failed: %d", i, errno); + + res = write(sv[(!i) & 1], "x", 1); + zassert_equal(res, -1, "expected write(2) to fail"); + zassert_equal(res, -1, "errno: expected: EPIPE actual: %d", + errno); + + res = close(sv[(!i) & 1]); + zassert_equal(res, 0, "close(sv[%u]) failed: %d", i, errno); + } +} + +void test_socketpair_close_one_end_and_read_from_the_other(void) +{ + int res; + int sv[2] = {-1, -1}; + char xx[16]; + + for (size_t i = 0; i < 2; ++i) { + res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + zassert_equal(res, 0, "socketpair(2) failed: %d", errno); + + /* We want to write some bytes to the closing end of the + * socket before it gets shut down, so that we can prove that + * reading is possible from the other end still and that data + * is not lost. + */ + res = write(sv[i], "xx", 2); + zassert_not_equal(res, -1, "write(2) failed: %d", errno); + zassert_equal(res, 2, "write(2) failed to write 2 bytes"); + + res = close(sv[i]); + zassert_equal(res, 0, "close(sv[%u]) failed: %d", i, errno); + + memset(xx, 0, sizeof(xx)); + res = read(sv[(!i) & 1], xx, sizeof(xx)); + zassert_not_equal(res, -1, "read(2) failed: %d", errno); + zassert_equal(res, 2, "expected to read 2 bytes but read %d", + res); + + res = read(sv[(!i) & 1], xx, sizeof(xx)); + zassert_equal(res, 0, + "expected read(2) to succeed but read 0 bytes"); + + res = close(sv[(!i) & 1]); + zassert_equal(res, 0, "close(sv[%u]) failed: %d", i, errno); + } +} diff --git a/tests/net/socket/socketpair/src/test_socketpair_expected_failures.c b/tests/net/socket/socketpair/src/test_socketpair_expected_failures.c new file mode 100644 index 0000000000000..cf8ce6edbdb93 --- /dev/null +++ b/tests/net/socket/socketpair/src/test_socketpair_expected_failures.c @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2020 Friedt Professional Engineering Services, Inc + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL); + +#include +#include +#include +#include +#include + +#include + +#undef read +#define read(fd, buf, len) zsock_recv(fd, buf, len, 0) + +#undef write +#define write(fd, buf, len) zsock_send(fd, buf, len, 0) + +void test_socketpair_expected_failures(void) +{ + int res; + int sv[2] = {-1, -1}; + + /* Use invalid values in fields starting from left to right */ + + res = socketpair(AF_INET, SOCK_STREAM, 0, sv); + if (res != -1) { + close(sv[0]); + close(sv[1]); + } + zassert_equal(res, -1, "socketpair with fail with bad address family"); + zassert_equal(errno, EAFNOSUPPORT, + "errno should be EAFNOSUPPORT with bad adddress family"); + + res = socketpair(AF_UNIX, 42, 0, sv); + if (res != -1) { + close(sv[0]); + close(sv[1]); + } + zassert_equal(res, -1, + "socketpair should fail with unsupported socket type"); + zassert_equal(errno, EPROTOTYPE, + "errno should be EPROTOTYPE with bad socket type"); + + res = socketpair(AF_UNIX, SOCK_STREAM, IPPROTO_TLS_1_0, sv); + if (res != -1) { + close(sv[0]); + close(sv[1]); + } + zassert_equal(res, -1, + "socketpair should fail with unsupported protocol"); + zassert_equal(errno, EPROTONOSUPPORT, + "errno should be EPROTONOSUPPORT with bad protocol"); + + /* This is not a POSIX requirement, but should be safe */ + res = socketpair(AF_UNIX, SOCK_STREAM, 0, NULL); + if (res != -1) { + close(sv[0]); + close(sv[1]); + } + zassert_equal(res, -1, + "socketpair should fail with invalid socket vector"); + zassert_equal(errno, EFAULT, + "errno should be EFAULT with bad socket vector"); +} diff --git a/tests/net/socket/socketpair/src/test_socketpair_fcntl.c b/tests/net/socket/socketpair/src/test_socketpair_fcntl.c new file mode 100644 index 0000000000000..4839eeba21290 --- /dev/null +++ b/tests/net/socket/socketpair/src/test_socketpair_fcntl.c @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 Friedt Professional Engineering Services, Inc + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL); + +#include +#include +#include +#include +#include + +#include + +#undef read +#define read(fd, buf, len) zsock_recv(fd, buf, len, 0) + +#undef write +#define write(fd, buf, len) zsock_send(fd, buf, len, 0) + +void test_socketpair_fcntl(void) +{ + int res; + int sv[2] = {-1, -1}; + int flags; + + res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + zassert_equal(res, 0, + "socketpair(AF_UNIX, SOCK_STREAM, 0, sv) failed"); + + res = fcntl(sv[0], F_GETFL, 0); + zassert_not_equal(res, -1, + "fcntl(sv[0], F_GETFL) failed. errno: %d", errno); + + flags = res; + zassert_equal(res & O_NONBLOCK, 0, + "socketpair should block by default"); + + res = fcntl(sv[0], F_SETFL, flags | O_NONBLOCK); + zassert_not_equal(res, -1, + "fcntl(sv[0], F_SETFL, flags | O_NONBLOCK) failed. errno: %d", + errno); + + res = fcntl(sv[0], F_GETFL, 0); + zassert_equal(res ^ flags, O_NONBLOCK, "expected O_NONBLOCK set"); + + close(sv[0]); + close(sv[1]); +} diff --git a/tests/net/socket/socketpair/src/test_socketpair_happy_path.c b/tests/net/socket/socketpair/src/test_socketpair_happy_path.c new file mode 100644 index 0000000000000..992260031b661 --- /dev/null +++ b/tests/net/socket/socketpair/src/test_socketpair_happy_path.c @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2020 Friedt Professional Engineering Services, Inc + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL); + +#include +#include +#include +#include + +#include + +#undef read +#define read(fd, buf, len) zsock_recv(fd, buf, len, 0) + +#undef write +#define write(fd, buf, len) zsock_send(fd, buf, len, 0) + +static void happy_path( + const int family, const char *family_s, + const int type, const char *type_s, + const int proto, const char *proto_s +) +{ + int res; + int sv[2] = {-1, -1}; + + const char *expected_msg = "Hello, socketpair(2) world!"; + const unsigned int expected_msg_len = strlen(expected_msg); + char actual_msg[32]; + size_t actual_msg_len; + struct iovec iovec; + struct msghdr msghdr; + + LOG_DBG("calling socketpair(%u, %u, %u, %p)", family, type, proto, sv); + res = socketpair(family, type, proto, sv); + zassert_true(res == -1 || res == 0, + "socketpair returned an unspecified value"); + zassert_equal(res, 0, "socketpair failed"); + LOG_DBG("sv: {%d, %d}", sv[0], sv[1]); + + socklen_t len; + + /* sockets are bidirectional. test functions from both ends */ + for (int i = 0; i < 2; ++i) { + + /* + * Test with write(2) / read(2) + */ + + LOG_DBG("calling write(%d, '%s', %u)", sv[i], expected_msg, + expected_msg_len); + res = write(sv[i], expected_msg, expected_msg_len); + + zassert_not_equal(res, -1, "write(2) failed: %d", errno); + actual_msg_len = res; + zassert_equal(actual_msg_len, expected_msg_len, + "did not write entire message"); + + memset(actual_msg, 0, sizeof(actual_msg)); + + LOG_DBG("calling read(%d, %p, %u)", sv[i], actual_msg, + (unsigned int)sizeof(actual_msg)); + res = read(sv[(!i) & 1], actual_msg, sizeof(actual_msg)); + + zassert_not_equal(res, -1, "read(2) failed: %d", errno); + actual_msg_len = res; + zassert_equal(actual_msg_len, expected_msg_len, + "wrong return value"); + + zassert_true(strncmp(expected_msg, actual_msg, + actual_msg_len) == 0, + "the wrong message was passed through the socketpair"); + + /* + * Test with send(2) / recv(2) + */ + + res = send(sv[i], expected_msg, expected_msg_len, 0); + + zassert_not_equal(res, -1, "send(2) failed: %d", errno); + actual_msg_len = res; + zassert_equal(actual_msg_len, expected_msg_len, + "did not send entire message"); + + memset(actual_msg, 0, sizeof(actual_msg)); + + res = recv(sv[(!i) & 1], actual_msg, sizeof(actual_msg), 0); + + zassert_not_equal(res, -1, "recv(2) failed: %d", errno); + actual_msg_len = res; + zassert_equal(actual_msg_len, expected_msg_len, + "wrong return value"); + + zassert_true(strncmp(expected_msg, actual_msg, + actual_msg_len) == 0, + "the wrong message was passed through the socketpair"); + + /* + * Test with sendto(2) / recvfrom(2) + */ + + res = sendto(sv[i], expected_msg, expected_msg_len, 0, NULL, 0); + + zassert_not_equal(res, -1, "sendto(2) failed: %d", errno); + actual_msg_len = res; + zassert_equal(actual_msg_len, expected_msg_len, + "did not sendto entire message"); + + memset(actual_msg, 0, sizeof(actual_msg)); + + res = recvfrom(sv[(!i) & 1], actual_msg, sizeof(actual_msg), 0, + NULL, &len); + + zassert_not_equal(res, -1, "recvfrom(2) failed: %d", errno); + actual_msg_len = res; + zassert_equal(actual_msg_len, expected_msg_len, + "wrong return value"); + + zassert_true(strncmp(expected_msg, actual_msg, + actual_msg_len) == 0, + "the wrong message was passed through the socketpair"); + + /* + * Test with sendmsg(2) / recv(2) - Zephyr lacks recvmsg atm + */ + + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + iovec.iov_base = (void *)expected_msg; + iovec.iov_len = expected_msg_len; + + res = sendmsg(sv[i], &msghdr, 0); + + zassert_not_equal(res, -1, "sendmsg(2) failed: %d", errno); + actual_msg_len = res; + zassert_equal(actual_msg_len, expected_msg_len, + "did not sendmsg entire message"); + + res = read(sv[(!i) & 1], actual_msg, sizeof(actual_msg)); + + zassert_not_equal(res, -1, "read(2) failed: %d", errno); + actual_msg_len = res; + zassert_equal(actual_msg_len, expected_msg_len, + "wrong return value"); + + zassert_true(strncmp(expected_msg, actual_msg, + actual_msg_len) == 0, + "the wrong message was passed through the socketpair"); + } + + res = close(sv[0]); + zassert_equal(res, 0, "close failed"); + + res = close(sv[1]); + zassert_equal(res, 0, "close failed"); +} + +void test_socketpair_AF_LOCAL__SOCK_STREAM__0(void) +{ + happy_path( + AF_LOCAL, "AF_LOCAL", + SOCK_STREAM, "SOCK_STREAM", + 0, "0" + ); +} + +void test_socketpair_AF_UNIX__SOCK_STREAM__0(void) +{ + happy_path( + AF_UNIX, "AF_UNIX", + SOCK_STREAM, "SOCK_STREAM", + 0, "0" + ); +} diff --git a/tests/net/socket/socketpair/src/test_socketpair_nonblock.c b/tests/net/socket/socketpair/src/test_socketpair_nonblock.c new file mode 100644 index 0000000000000..b055cd1b97531 --- /dev/null +++ b/tests/net/socket/socketpair/src/test_socketpair_nonblock.c @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2020 Friedt Professional Engineering Services, Inc + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL); + +#include +#include +#include +#include +#include + +#include + +#undef read +#define read(fd, buf, len) zsock_recv(fd, buf, len, 0) + +#undef write +#define write(fd, buf, len) zsock_send(fd, buf, len, 0) + +void test_socketpair_write_nonblock(void) +{ + int res; + int sv[2] = {-1, -1}; + + res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + zassert_equal(res, 0, "socketpair(2) failed: %d", errno); + + for (size_t i = 0; i < 2; ++i) { + /* first, fill up the buffer */ + for (size_t k = 0; k < CONFIG_NET_SOCKETPAIR_BUFFER_SIZE; + ++k) { + res = write(sv[i], "x", 1); + zassert_equal(res, 1, "write(2) failed: %d", errno); + } + + /* then set the O_NONBLOCK flag */ + res = fcntl(sv[i], F_GETFL, 0); + zassert_not_equal(res, -1, "fcntl() failed: %d", i, errno); + + res = fcntl(sv[i], F_SETFL, res | O_NONBLOCK); + zassert_not_equal(res, -1, "fcntl() failed: %d", i, errno); + + /* then, try to write one more byte */ + res = write(sv[i], "x", 1); + zassert_equal(res, -1, "expected write to fail"); + zassert_equal(errno, EAGAIN, "errno: exected: EAGAIN " + "actual: %d", errno); + } + + close(sv[0]); + close(sv[1]); +} + +void test_socketpair_read_nonblock(void) +{ + int res; + int sv[2] = {-1, -1}; + char c; + + res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + zassert_equal(res, 0, "socketpair(2) failed: %d", errno); + + for (size_t i = 0; i < 2; ++i) { + /* set the O_NONBLOCK flag */ + res = fcntl(sv[i], F_GETFL, 0); + zassert_not_equal(res, -1, "fcntl() failed: %d", i, errno); + + res = fcntl(sv[i], F_SETFL, res | O_NONBLOCK); + zassert_not_equal(res, -1, "fcntl() failed: %d", i, errno); + + /* then, try to read one byte */ + res = read(sv[i], &c, 1); + zassert_equal(res, -1, "expected read to fail"); + zassert_equal(errno, EAGAIN, "errno: exected: EAGAIN " + "actual: %d", errno); + } + + close(sv[0]); + close(sv[1]); +} diff --git a/tests/net/socket/socketpair/src/test_socketpair_poll.c b/tests/net/socket/socketpair/src/test_socketpair_poll.c new file mode 100644 index 0000000000000..2b3b9c7364f4c --- /dev/null +++ b/tests/net/socket/socketpair/src/test_socketpair_poll.c @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2020 Friedt Professional Engineering Services, Inc + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL); + +#include +#include +#include + +#include + +#undef read +#define read(fd, buf, len) zsock_recv(fd, buf, len, 0) + +#undef write +#define write(fd, buf, len) zsock_send(fd, buf, len, 0) + +#define STACK_SIZE 512 +/* stack for the secondary thread */ +static K_THREAD_STACK_DEFINE(th_stack, STACK_SIZE); +static struct k_thread th; +static k_tid_t tid; + +/* + * Timeout should work the same for blocking & non-blocking threads + * + * - no bytes available to read after timeout, r: 0 (timeout) + * - no bytes available to write after timeout, r: 0 (timeout) + */ + +static void test_socketpair_poll_timeout_common(int sv[2]) +{ + int res; + + struct pollfd fds[1]; + + memset(fds, 0, sizeof(fds)); + fds[0].fd = sv[0]; + fds[0].events |= POLLIN; + res = poll(fds, 1, 1); + zassert_equal(res, 0, "poll: expected: 0 actual: %d", res); + + for (size_t i = 0; i < CONFIG_NET_SOCKETPAIR_BUFFER_SIZE; ++i) { + res = write(sv[0], "x", 1); + zassert_equal(res, 1, "write failed: %d", res); + } + + memset(fds, 0, sizeof(fds)); + fds[0].fd = sv[0]; + fds[0].events |= POLLOUT; + res = poll(fds, 1, 1); + zassert_equal(res, 0, "poll: expected: 0 actual: %d", res); + + close(sv[0]); + close(sv[1]); +} + +void test_socketpair_poll_timeout(void) +{ + int sv[2] = {-1, -1}; + int res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + + zassert_not_equal(res, -1, "socketpair failed: %d", errno); + + test_socketpair_poll_timeout_common(sv); +} + +/* O_NONBLOCK should have no affect on poll(2) */ +void test_socketpair_poll_timeout_nonblocking(void) +{ + int sv[2] = {-1, -1}; + int res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + + zassert_not_equal(res, -1, "socketpair failed: %d", errno); + + res = fcntl(sv[0], F_GETFL, 0); + zassert_not_equal(res, -1, "fcntl failed: %d", errno); + + int flags = res; + + res = fcntl(sv[0], F_SETFL, O_NONBLOCK | flags); + zassert_not_equal(res, -1, "fcntl failed: %d", errno); + + res = fcntl(sv[1], F_SETFL, O_NONBLOCK | flags); + zassert_not_equal(res, -1, "fcntl failed: %d", errno); + + test_socketpair_poll_timeout_common(sv); +} + +static void close_fun(void *arg1, void *arg2, void *arg3) +{ + (void)arg2; + (void)arg3; + + const int *const fd = (const int *)arg1; + + k_sleep(K_MSEC(1000)); + + LOG_DBG("about to close fd %d", *fd); + close(*fd); +} + +/* + * Hangup should cause the following behaviour + * - close remote fd while the local fd is blocking in poll. r: 1, + * POLLIN, read -> r: 0, errno: 0 -> EOF + * - close remote fd while the local fd is blocking in poll. r: 1, + * POLLOUT, write -> r: -1, errno: EPIPE. + */ +void test_socketpair_poll_close_remote_end_POLLIN(void) +{ + int res; + char c; + struct pollfd fds[1]; + + int sv[2] = {-1, -1}; + + res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + zassert_not_equal(res, -1, "socketpair(2) failed: %d", errno); + + /* + * poll until there are bytes to read. + * But rather than writing, close the other end of the channel + */ + + memset(fds, 0, sizeof(fds)); + fds[0].fd = sv[0]; + fds[0].events |= POLLIN; + + tid = k_thread_create(&th, th_stack, + K_THREAD_STACK_SIZEOF(th_stack), close_fun, + &sv[1], NULL, NULL, + CONFIG_MAIN_THREAD_PRIORITY + 1, + K_USER, K_NO_WAIT); + + res = poll(fds, 1, -1); + zassert_equal(res, 1, "poll(2) failed: %d", res); + zassert_equal(fds[0].revents & POLLIN, POLLIN, "POLLIN not set"); + + res = k_thread_join(&th, K_MSEC(5000)); + zassert_false(res < 0, "k_thread_join failed: %d", res); + + res = read(sv[0], &c, 1); + zassert_equal(res, 0, "read did not return EOF"); + + close(sv[0]); +} + +void test_socketpair_poll_close_remote_end_POLLOUT(void) +{ + int res; + struct pollfd fds[1]; + + int sv[2] = {-1, -1}; + + /* + * Fill up the remote q and then poll until write space is available. + * But rather than reading, close the other end of the channel + */ + + res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + zassert_not_equal(res, -1, "socketpair(2) failed: %d", errno); + + for (size_t i = 0; i < CONFIG_NET_SOCKETPAIR_BUFFER_SIZE; ++i) { + res = write(sv[0], "x", 1); + zassert_equal(res, 1, "write failed: %d", res); + } + + memset(fds, 0, sizeof(fds)); + fds[0].fd = sv[0]; + fds[0].events |= POLLOUT; + + tid = k_thread_create(&th, th_stack, + K_THREAD_STACK_SIZEOF(th_stack), close_fun, + &sv[1], NULL, NULL, + CONFIG_MAIN_THREAD_PRIORITY + 1, + K_USER, K_NO_WAIT); + + res = poll(fds, 1, -1); + zassert_equal(res, 1, "poll(2) failed: %d", res); + zassert_equal(fds[0].revents & POLLHUP, POLLHUP, "POLLHUP not set"); + + res = k_thread_join(&th, K_MSEC(5000)); + zassert_false(res < 0, "k_thread_join failed: %d", res); + + res = write(sv[0], "x", 1); + zassert_equal(res, -1, "write(2): expected: -1 actual: %d", res); + zassert_equal(errno, EPIPE, "errno: expected: EPIPE actual: %d", errno); + + close(sv[0]); +} + +/* + * Data available immediately + * - even with a timeout value of 0 us, poll should return immediately with + * a value of 1 (for either read or write cases) + * - even with a timeout value of 0us, poll should return immediately with + * a value of 2 if both read and write are available + */ +void test_socketpair_poll_immediate_data(void) +{ + int sv[2] = {-1, -1}; + int res; + + struct pollfd fds[2]; + + res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + zassert_not_equal(res, -1, "socketpair(2) failed: %d", errno); + + memset(fds, 0, sizeof(fds)); + fds[0].fd = sv[0]; + fds[0].events |= POLLOUT; + res = poll(fds, 1, 0); + zassert_not_equal(res, -1, "poll(2) failed: %d", errno); + zassert_equal(res, 1, "poll(2): expected: 1 actual: %d", res); + zassert_not_equal(fds[0].revents & POLLOUT, 0, "POLLOUT not set"); + + res = write(sv[0], "x", 1); + zassert_not_equal(res, -1, "write(2) failed: %d", errno); + zassert_equal(res, 1, "write(2): expected: 1 actual: %d", res); + + memset(fds, 0, sizeof(fds)); + fds[0].fd = sv[1]; + fds[0].events |= POLLIN; + res = poll(fds, 1, 0); + zassert_not_equal(res, -1, "poll(2) failed: %d", errno); + zassert_equal(res, 1, "poll(2): expected: 1 actual: %d", res); + zassert_not_equal(fds[0].revents & POLLIN, 0, "POLLIN not set"); + + memset(fds, 0, sizeof(fds)); + fds[0].fd = sv[0]; + fds[0].events |= POLLOUT; + fds[1].fd = sv[1]; + fds[1].events |= POLLIN; + res = poll(fds, 2, 0); + zassert_not_equal(res, -1, "poll(2) failed: %d", errno); + zassert_equal(res, 2, "poll(2): expected: 1 actual: %d", res); + zassert_not_equal(fds[0].revents & POLLOUT, 0, "POLLOUT not set"); + zassert_not_equal(fds[1].revents & POLLIN, 0, "POLLIN not set"); + + close(sv[0]); + close(sv[1]); +} + +static void rw_fun(void *arg1, void *arg2, void *arg3) +{ + (void)arg3; + + const bool *const should_write = (const bool *) arg1; + const int *const fd = (const int *)arg2; + + int res; + char c; + + k_sleep(K_MSEC(1000)); + + if (*should_write) { + LOG_DBG("about to write 1 byte"); + res = write(*fd, "x", 1); + if (-1 == res) { + LOG_DBG("write(2) failed: %d", errno); + } else { + LOG_DBG("wrote 1 byte"); + } + } else { + LOG_DBG("about to read 1 byte"); + res = read(*fd, &c, 1); + if (-1 == res) { + LOG_DBG("read(2) failed: %d", errno); + } else { + LOG_DBG("read 1 byte"); + } + } +} + +/* + * Data only available but after some short period + * - say there is a timeout value of 5 s, poll should return immediately + * with the a value of 1 (for either read or write cases) + */ +void test_socketpair_poll_delayed_data(void) +{ + int sv[2] = {-1, -1}; + int res; + + bool should_write; + + struct pollfd fds[1]; + + res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + zassert_not_equal(res, -1, "socketpair(2) failed: %d", errno); + + memset(fds, 0, sizeof(fds)); + fds[0].fd = sv[0]; + fds[0].events |= POLLIN; + should_write = true; + + tid = k_thread_create(&th, th_stack, + K_THREAD_STACK_SIZEOF(th_stack), rw_fun, + &should_write, &sv[1], NULL, + CONFIG_MAIN_THREAD_PRIORITY + 1, + K_USER, K_NO_WAIT); + + res = poll(fds, 1, 5000); + zassert_not_equal(res, -1, "poll(2) failed: %d", errno); + zassert_equal(res, 1, "poll(2): expected: 1 actual: %d", res); + zassert_not_equal(fds[0].revents & POLLIN, 0, "POLLIN not set"); + k_thread_join(&th, K_FOREVER); + + for (size_t i = 0; i < CONFIG_NET_SOCKETPAIR_BUFFER_SIZE; ++i) { + res = write(sv[0], "x", 1); + zassert_equal(res, 1, "write failed: %d", res); + } + + memset(fds, 0, sizeof(fds)); + fds[0].fd = sv[0]; + fds[0].events |= POLLOUT; + should_write = false; + + tid = k_thread_create(&th, th_stack, + K_THREAD_STACK_SIZEOF(th_stack), rw_fun, + &should_write, &sv[1], NULL, + CONFIG_MAIN_THREAD_PRIORITY + 1, + K_USER, K_NO_WAIT); + + res = poll(fds, 1, 5000); + zassert_not_equal(res, -1, "poll(2) failed: %d", errno); + zassert_equal(res, 1, "poll(2): expected: 1 actual: %d", res); + zassert_not_equal(fds[0].revents & POLLOUT, 0, "POLLOUT was not set"); + k_thread_join(&th, K_FOREVER); + + close(sv[0]); + close(sv[1]); +} diff --git a/tests/net/socket/socketpair/src/test_socketpair_unsupported_calls.c b/tests/net/socket/socketpair/src/test_socketpair_unsupported_calls.c new file mode 100644 index 0000000000000..3a0852d29befa --- /dev/null +++ b/tests/net/socket/socketpair/src/test_socketpair_unsupported_calls.c @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2020 Friedt Professional Engineering Services, Inc + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL); + +#include +#include +#include +#include +#include + +#include + +#undef read +#define read(fd, buf, len) zsock_recv(fd, buf, len, 0) + +#undef write +#define write(fd, buf, len) zsock_send(fd, buf, len, 0) + +void test_socketpair_unsupported_calls(void) +{ + int res; + int sv[2] = {-1, -1}; + struct sockaddr_un addr = { + .sun_family = AF_UNIX, + }; + socklen_t len = sizeof(addr); + + res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + zassert_equal(res, 0, + "socketpair(AF_UNIX, SOCK_STREAM, 0, sv) failed"); + + + for (size_t i = 0; i < 2; ++i) { + + res = bind(sv[i], (struct sockaddr *)&addr, len); + zassert_equal(res, -1, + "bind should fail on a socketpair endpoint"); + zassert_equal(errno, EISCONN, + "bind should set errno to EISCONN"); + + res = connect(sv[i], (struct sockaddr *)&addr, len); + zassert_equal(res, -1, + "connect should fail on a socketpair endpoint"); + zassert_equal(errno, EISCONN, + "connect should set errno to EISCONN"); + + res = listen(sv[i], 1); + zassert_equal(res, -1, + "listen should fail on a socketpair endpoint"); + zassert_equal(errno, EINVAL, + "listen should set errno to EINVAL"); + + res = accept(sv[i], (struct sockaddr *)&addr, &len); + zassert_equal(res, -1, + "accept should fail on a socketpair endpoint"); + zassert_equal(errno, EOPNOTSUPP, + "accept should set errno to EOPNOTSUPP"); + } + + res = close(sv[0]); + zassert_equal(res, 0, "close failed"); + + res = close(sv[1]); + zassert_equal(res, 0, "close failed"); +} diff --git a/tests/net/socket/socketpair/testcase.yaml b/tests/net/socket/socketpair/testcase.yaml new file mode 100644 index 0000000000000..1da7159174d27 --- /dev/null +++ b/tests/net/socket/socketpair/testcase.yaml @@ -0,0 +1,5 @@ +common: + tags: net socket userspace +tests: + net.socket.socketpair: + min_ram: 21