tests: socket: socketpair: tests for socketpair(2) syscall

Tests for issue #24366

Signed-off-by: Christopher Friedt <chrisfriedt@gmail.com>
diff --git a/CODEOWNERS b/CODEOWNERS
index aab64d1..499e7f2 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -498,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/subsys/net/lib/sockets/socketpair.c b/subsys/net/lib/sockets/socketpair.c
index a37bd09..5cec71b 100644
--- a/subsys/net/lib/sockets/socketpair.c
+++ b/subsys/net/lib/sockets/socketpair.c
@@ -568,7 +568,7 @@
  */
 static ssize_t spair_read(void *obj, void *buffer, size_t count)
 {
-	ssize_t res;
+	int res;
 
 	bool is_connected;
 	size_t avail;
@@ -783,7 +783,7 @@
 	if (pfd->events & ZSOCK_POLLOUT) {
 		if (!sock_is_connected(spair)) {
 			pfd->revents |= ZSOCK_POLLHUP;
-			goto check_pollin;
+			goto pollout_done;
 		}
 
 		remote = z_get_fd_obj(spair->remote,
@@ -850,7 +850,6 @@
 
 	(*pev)++;
 
-out:
 	if (remote != NULL && have_remote_sem) {
 		k_sem_give(&remote->sem);
 	}
diff --git a/tests/net/socket/socketpair/CMakeLists.txt b/tests/net/socket/socketpair/CMakeLists.txt
new file mode 100644
index 0000000..27b977b
--- /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 0000000..2d43936
--- /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 0000000..046f17b
--- /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 <ztest.h>
+
+/* 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 0000000..b5a9cad
--- /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 <fcntl.h>
+#include <string.h>
+
+#include <logging/log.h>
+LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL);
+
+#include <net/socket.h>
+#include <sys/util.h>
+#include <posix/unistd.h>
+
+#include <ztest_assert.h>
+
+#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 0000000..163f659
--- /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 <fcntl.h>
+
+#include <logging/log.h>
+LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL);
+
+#include <stdio.h>
+#include <string.h>
+#include <net/socket.h>
+#include <sys/util.h>
+#include <posix/unistd.h>
+
+#include <ztest_assert.h>
+
+#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 0000000..cf8ce6e
--- /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 <fcntl.h>
+
+#include <logging/log.h>
+LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL);
+
+#include <stdio.h>
+#include <string.h>
+#include <net/socket.h>
+#include <sys/util.h>
+#include <posix/unistd.h>
+
+#include <ztest_assert.h>
+
+#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 0000000..4839eeb
--- /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 <fcntl.h>
+
+#include <logging/log.h>
+LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL);
+
+#include <stdio.h>
+#include <string.h>
+#include <net/socket.h>
+#include <sys/util.h>
+#include <posix/unistd.h>
+
+#include <ztest_assert.h>
+
+#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 0000000..9922600
--- /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 <fcntl.h>
+
+#include <logging/log.h>
+LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL);
+
+#include <string.h>
+#include <net/socket.h>
+#include <sys/util.h>
+#include <posix/unistd.h>
+
+#include <ztest_assert.h>
+
+#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 0000000..b055cd1
--- /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 <fcntl.h>
+
+#include <logging/log.h>
+LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL);
+
+#include <stdio.h>
+#include <string.h>
+#include <net/socket.h>
+#include <sys/util.h>
+#include <posix/unistd.h>
+
+#include <ztest_assert.h>
+
+#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 0000000..2b3b9c7
--- /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 <fcntl.h>
+#include <string.h>
+
+#include <logging/log.h>
+LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL);
+
+#include <net/socket.h>
+#include <posix/unistd.h>
+#include <sys/util.h>
+
+#include <ztest_assert.h>
+
+#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 0000000..3a0852d
--- /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 <fcntl.h>
+
+#include <logging/log.h>
+LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL);
+
+#include <stdio.h>
+#include <string.h>
+#include <net/socket.h>
+#include <sys/util.h>
+#include <posix/unistd.h>
+
+#include <ztest_assert.h>
+
+#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 0000000..1da7159
--- /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