[PATCH net-next v2 4/4] selftests: net: adopt harness for flow label mgr

Marcelo Mendes Spessoto Junior <[email protected]> Mon, 3 Aug 2026 23:59:10 -0300
Newsgroups gmane.linux.network,gmane.linux.kernel
Message-ID <[email protected]>
The kselftest_harness.h file contains modern helpers to build tests
for kselftest. Dropping current test helpers for ipv6_flowlabel_mgr
to use harness helps tests to be more legible and conform to the
structure of the latest tests. It also enforces TAP standard.

Another change made to the structure of ipv6_flowlabel_mgr test file
was the removal of parse_opts. The supported opts were already unused:
the binary is listed in TEST_GEN_FILES, and is driven solely by
ipv6_flowlabel.sh via "./in_netns.sh ./ipv6_flowlabel_mgr",
which never passed -l or -v. Dropping the -l gate means the two checks
it previously guarded (each with a 13-second sleep, ~26 seconds total)
are now unconditionally enabled on every run instead of never running
at all. The TH_LOG and code comments cover the information obtainable
from (-v).

Signed-off-by: Marcelo Mendes Spessoto Junior <[email protected]>
---
 .../selftests/net/ipv6_flowlabel_mgr.c        | 521 ++++++++++--------
 1 file changed, 299 insertions(+), 222 deletions(-)

diff --git a/tools/testing/selftests/net/ipv6_flowlabel_mgr.c b/tools/testing/selftests/net/ipv6_flowlabel_mgr.c
index af87eec799c8..482921f7ee11 100644
--- a/tools/testing/selftests/net/ipv6_flowlabel_mgr.c
+++ b/tools/testing/selftests/net/ipv6_flowlabel_mgr.c
@@ -20,6 +20,7 @@
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
+#include "kselftest_harness.h"
 
 /* uapi/glibc weirdness may leave this undefined */
 #ifndef IPV6_FLOWLABEL_MGR
@@ -33,35 +34,6 @@
 #define FL_MIN_LINGER		6
 #define FL_MAX_LINGER	150
 
-#define explain(x)							\
-	do { if (cfg_verbose) fprintf(stderr, "       " x "\n"); } while (0)
-
-#define __expect(x)							\
-	do {								\
-		if (!(x))						\
-			fprintf(stderr, "[OK]   " #x "\n");		\
-		else							\
-			error(1, 0, "[ERR]  " #x " (line %d)", __LINE__); \
-	} while (0)
-
-#define expect_pass(x)	__expect(x)
-#define expect_fail(x)	__expect(!(x))
-
-#define expect_fail_errno(x, e)						\
-	do {								\
-		int __exp = (e);					\
-		int __ret = (x);					\
-		int __err = errno;					\
-		if (__ret && __err == __exp)				\
-			fprintf(stderr, "[OK]   " #x "\n");		\
-		else							\
-			error(1, 0, "[ERR]  " #x " (line %d): expected errno %d, got %d", \
-			      __LINE__, __exp, __err);			\
-	} while (0)
-
-static bool cfg_long_running;
-static bool cfg_verbose;
-
 static int flowlabel_get(int fd, uint32_t label, uint8_t share, uint16_t flags)
 {
 	struct in6_flowlabel_req req = {
@@ -159,230 +131,335 @@ static void tcp_connect(int listener, uint32_t flowlabel, int *client, int *acce
 	*accepted = afd;
 }
 
-static bool flowlabel_consistency_enabled(void)
+TEST(cannot_get_non_existent_label)
 {
-	char buf[2] = {};
-	int fd;
+	int fd, err;
 
-	fd = open("/proc/sys/net/ipv6/flowlabel_consistency", O_RDONLY);
-	if (fd == -1)
-		return true;
+	fd = socket(PF_INET6, SOCK_DGRAM, 0);
+	ASSERT_GE(fd, 0) TH_LOG("socket failed");
 
-	if (read(fd, buf, sizeof(buf) - 1) < 0)
-		buf[0] = '1';
-	close(fd);
+	err = flowlabel_get(fd, 9, IPV6_FL_S_ANY, 0);
+	ASSERT_TRUE(err) TH_LOG("expected get of a non-existent label to fail");
+	ASSERT_EQ(ENOENT, errno) TH_LOG("expected ENOENT, got %d", errno);
 
-	return buf[0] != '0';
+	ASSERT_EQ(0, close(fd));
+}
+
+TEST(cannot_put_non_existent_label)
+{
+	int fd, err;
+
+	fd = socket(PF_INET6, SOCK_DGRAM, 0);
+	ASSERT_GE(fd, 0) TH_LOG("socket failed");
+
+	err = flowlabel_put(fd, 10);
+	ASSERT_TRUE(err) TH_LOG("expected put of a non-existent label to fail");
+	ASSERT_EQ(ESRCH, errno) TH_LOG("expected ESRCH, got %d", errno);
+
+	ASSERT_EQ(0, close(fd));
+}
+
+TEST(cannot_create_label_greater_than_20_bits)
+{
+	int fd, err;
+
+	fd = socket(PF_INET6, SOCK_DGRAM, 0);
+	ASSERT_GE(fd, 0) TH_LOG("socket failed");
+
+	err = flowlabel_get(fd, 0x1FFFFF, IPV6_FL_S_ANY, IPV6_FL_F_CREATE);
+	ASSERT_TRUE(err) TH_LOG("expected label > 20 bits to be rejected");
+	ASSERT_EQ(EINVAL, errno) TH_LOG("expected EINVAL, got %d", errno);
+
+	ASSERT_EQ(0, close(fd));
+}
+
+TEST(can_create_and_get_and_put_labels)
+{
+	int fd, err;
+
+	fd = socket(PF_INET6, SOCK_DGRAM, 0);
+	ASSERT_GE(fd, 0) TH_LOG("socket failed");
+
+	err = flowlabel_get(fd, 1, IPV6_FL_S_ANY, IPV6_FL_F_CREATE);
+	ASSERT_TRUE(!err) TH_LOG("failed to create label (FL_F_CREATE)");
+
+	err = flowlabel_get(fd, 1, IPV6_FL_S_ANY, 0);
+	ASSERT_TRUE(!err) TH_LOG("failed to get the label without FL_F_CREATE");
+
+	err = flowlabel_get(fd, 1, IPV6_FL_S_ANY, IPV6_FL_F_CREATE);
+	ASSERT_TRUE(!err) TH_LOG("failed to get it again with create flag set, too");
+
+	err = flowlabel_get(fd, 1, IPV6_FL_S_ANY, IPV6_FL_F_CREATE | IPV6_FL_F_EXCL);
+	ASSERT_TRUE(err) TH_LOG("expected FL_F_EXCL to reject an already-existing label");
+	ASSERT_EQ(EEXIST, errno) TH_LOG("expected EEXIST, got %d", errno);
+
+	err = flowlabel_put(fd, 1);
+	ASSERT_TRUE(!err) TH_LOG("failed to put first reference");
+	err = flowlabel_put(fd, 1);
+	ASSERT_TRUE(!err) TH_LOG("failed to put second reference");
+	err = flowlabel_put(fd, 1);
+	ASSERT_TRUE(!err) TH_LOG("failed to put third reference");
+	err = flowlabel_put(fd, 1);
+	ASSERT_TRUE(err) TH_LOG("expected fourth put to fail, no references left");
+	ASSERT_EQ(ESRCH, errno) TH_LOG("expected ESRCH, got %d", errno);
+
+	ASSERT_EQ(0, close(fd));
+}
+
+TEST(exclusive_label_share)
+{
+	int fd, err;
+
+	fd = socket(PF_INET6, SOCK_DGRAM, 0);
+	ASSERT_GE(fd, 0) TH_LOG("socket failed");
+
+	err = flowlabel_get(fd, 2, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE);
+	ASSERT_TRUE(!err) TH_LOG("failed to create a new exclusive label (FL_S_EXCL)");
+
+	err = flowlabel_get(fd, 2, IPV6_FL_S_ANY, IPV6_FL_F_CREATE);
+	ASSERT_TRUE(err) TH_LOG("expected reuse in non-exclusive mode to fail");
+	ASSERT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno);
+
+	err = flowlabel_get(fd, 2, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE);
+	ASSERT_TRUE(err) TH_LOG("expected reuse in exclusive mode to fail too");
+	ASSERT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno);
+
+	err = flowlabel_put(fd, 2);
+	ASSERT_TRUE(!err) TH_LOG("failed to put the exclusive label");
+
+	err = flowlabel_get(fd, 2, IPV6_FL_S_ANY, IPV6_FL_F_CREATE);
+	ASSERT_TRUE(err) TH_LOG("expected reuse to fail, due to linger");
+	ASSERT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno);
+
+	sleep(FL_MIN_LINGER * 2 + 1);
+
+	err = flowlabel_get(fd, 2, IPV6_FL_S_ANY, IPV6_FL_F_CREATE);
+	ASSERT_TRUE(!err) TH_LOG("expected reuse to succeed after linger");
+
+	ASSERT_EQ(0, close(fd));
 }
 
-static void run_tests(int fd)
+TEST(user_private_label_share)
 {
-	int wstatus;
+	int fd, err, wstatus;
 	pid_t pid;
 
-	explain("cannot get non-existent label");
-	expect_fail(flowlabel_get(fd, 1, IPV6_FL_S_ANY, 0));
-
-	explain("cannot put non-existent label");
-	expect_fail(flowlabel_put(fd, 1));
-
-	explain("cannot create label greater than 20 bits");
-	expect_fail(flowlabel_get(fd, 0x1FFFFF, IPV6_FL_S_ANY,
-				  IPV6_FL_F_CREATE));
-
-	explain("create a new label (FL_F_CREATE)");
-	expect_pass(flowlabel_get(fd, 1, IPV6_FL_S_ANY, IPV6_FL_F_CREATE));
-	explain("can get the label (without FL_F_CREATE)");
-	expect_pass(flowlabel_get(fd, 1, IPV6_FL_S_ANY, 0));
-	explain("can get it again with create flag set, too");
-	expect_pass(flowlabel_get(fd, 1, IPV6_FL_S_ANY, IPV6_FL_F_CREATE));
-	explain("cannot get it again with the exclusive (FL_FL_EXCL) flag");
-	expect_fail(flowlabel_get(fd, 1, IPV6_FL_S_ANY,
-					 IPV6_FL_F_CREATE | IPV6_FL_F_EXCL));
-	explain("can now put exactly three references");
-	expect_pass(flowlabel_put(fd, 1));
-	expect_pass(flowlabel_put(fd, 1));
-	expect_pass(flowlabel_put(fd, 1));
-	expect_fail(flowlabel_put(fd, 1));
-
-	explain("create a new exclusive label (FL_S_EXCL)");
-	expect_pass(flowlabel_get(fd, 2, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE));
-	explain("cannot get it again in non-exclusive mode");
-	expect_fail(flowlabel_get(fd, 2, IPV6_FL_S_ANY,  IPV6_FL_F_CREATE));
-	explain("cannot get it again in exclusive mode either");
-	expect_fail(flowlabel_get(fd, 2, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE));
-	expect_pass(flowlabel_put(fd, 2));
-
-	if (cfg_long_running) {
-		explain("cannot reuse the label, due to linger");
-		expect_fail(flowlabel_get(fd, 2, IPV6_FL_S_ANY,
-					  IPV6_FL_F_CREATE));
-		explain("after sleep, can reuse");
-		sleep(FL_MIN_LINGER * 2 + 1);
-		expect_pass(flowlabel_get(fd, 2, IPV6_FL_S_ANY,
-					  IPV6_FL_F_CREATE));
-	}
+	fd = socket(PF_INET6, SOCK_DGRAM, 0);
+	ASSERT_GE(fd, 0) TH_LOG("socket failed");
+
+	err = flowlabel_get(fd, 3, IPV6_FL_S_USER, IPV6_FL_F_CREATE);
+	ASSERT_TRUE(!err) TH_LOG("failed to create a new user-private label (FL_S_USER)");
+
+	err = flowlabel_get(fd, 3, IPV6_FL_S_ANY, 0);
+	ASSERT_TRUE(err) TH_LOG("expected get in non-exclusive mode to fail");
+	ASSERT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno);
+
+	err = flowlabel_get(fd, 3, IPV6_FL_S_EXCL, 0);
+	ASSERT_TRUE(err) TH_LOG("expected get in exclusive mode to fail");
+	ASSERT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno);
+
+	err = flowlabel_get(fd, 3, IPV6_FL_S_USER, 0);
+	ASSERT_TRUE(!err) TH_LOG("failed to get it again in user mode");
 
-	explain("create a new user-private label (FL_S_USER)");
-	expect_pass(flowlabel_get(fd, 3, IPV6_FL_S_USER, IPV6_FL_F_CREATE));
-	explain("cannot get it again in non-exclusive mode");
-	expect_fail(flowlabel_get(fd, 3, IPV6_FL_S_ANY, 0));
-	explain("cannot get it again in exclusive mode");
-	expect_fail(flowlabel_get(fd, 3, IPV6_FL_S_EXCL, 0));
-	explain("can get it again in user mode");
-	expect_pass(flowlabel_get(fd, 3, IPV6_FL_S_USER, 0));
-	explain("child process can get it too, but not after setuid(nobody)");
 	pid = fork();
-	if (pid == -1)
-		error(1, errno, "fork");
+	ASSERT_NE(-1, pid) TH_LOG("fork failed");
 	if (!pid) {
-		expect_pass(flowlabel_get(fd, 3, IPV6_FL_S_USER, 0));
-		if (setuid(USHRT_MAX))
+		err = flowlabel_get(fd, 3, IPV6_FL_S_USER, 0);
+		ASSERT_TRUE(!err) TH_LOG("child failed to get the user-private label");
+
+		if (setuid(USHRT_MAX)) {
 			fprintf(stderr, "[INFO] skip setuid child test\n");
-		else
-			expect_fail(flowlabel_get(fd, 3, IPV6_FL_S_USER, 0));
+			exit(0);
+		}
+
+		err = flowlabel_get(fd, 3, IPV6_FL_S_USER, 0);
+		ASSERT_TRUE(err) TH_LOG("child unexpectedly got the label after setuid(nobody)");
+		ASSERT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno);
 		exit(0);
 	}
-	if (wait(&wstatus) == -1)
-		error(1, errno, "wait");
-	if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0)
-		error(1, errno, "wait: unexpected child result");
-
-	explain("create a new process-private label (FL_S_PROCESS)");
-	expect_pass(flowlabel_get(fd, 4, IPV6_FL_S_PROCESS, IPV6_FL_F_CREATE));
-	explain("can get it again");
-	expect_pass(flowlabel_get(fd, 4, IPV6_FL_S_PROCESS, 0));
-	explain("child process cannot can get it");
+	ASSERT_EQ(pid, wait(&wstatus)) TH_LOG("wait failed");
+	ASSERT_TRUE(WIFEXITED(wstatus)) TH_LOG("child did not exit normally");
+	ASSERT_EQ(0, WEXITSTATUS(wstatus)) TH_LOG("child reported unexpected result");
+
+	ASSERT_EQ(0, close(fd));
+}
+
+TEST(process_private_label_share)
+{
+	int fd, err, wstatus;
+	pid_t pid;
+
+	fd = socket(PF_INET6, SOCK_DGRAM, 0);
+	ASSERT_GE(fd, 0) TH_LOG("socket failed");
+
+	err = flowlabel_get(fd, 4, IPV6_FL_S_PROCESS, IPV6_FL_F_CREATE);
+	ASSERT_TRUE(!err) TH_LOG("failed to create a new process-private label (FL_S_PROCESS)");
+
+	err = flowlabel_get(fd, 4, IPV6_FL_S_PROCESS, 0);
+	ASSERT_TRUE(!err) TH_LOG("failed to get it again");
+
 	pid = fork();
-	if (pid == -1)
-		error(1, errno, "fork");
+	ASSERT_NE(-1, pid) TH_LOG("fork failed");
 	if (!pid) {
-		expect_fail(flowlabel_get(fd, 4, IPV6_FL_S_PROCESS, 0));
+		err = flowlabel_get(fd, 4, IPV6_FL_S_PROCESS, 0);
+		ASSERT_TRUE(err) TH_LOG("child unexpectedly got the process-private label");
+		ASSERT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno);
 		exit(0);
 	}
-	if (wait(&wstatus) == -1)
-		error(1, errno, "wait");
-	if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0)
-		error(1, errno, "wait: unexpected child result");
-
-	explain("It is not possible to renew a label that does not exist");
-	expect_fail_errno(flowlabel_renew(fd, 5, 2 * (FL_MIN_LINGER * 2 + 1)), ESRCH);
-
-	explain("Create a label for basic renew validation");
-	expect_pass(flowlabel_get(fd, 5, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE));
-	explain("Check if renew does not return errors for existing and valid label");
-	expect_pass(flowlabel_renew(fd, 5, 2 * (FL_MIN_LINGER * 2 + 1)));
-
-	if (cfg_long_running) {
-		explain("create a new label with FL_MIN_LINGER linger time");
-		expect_pass(flowlabel_get(fd, 6, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE));
-		explain("renew the label to increase its linger time and put it");
-		expect_pass(flowlabel_renew(fd, 6, 2 * (FL_MIN_LINGER * 2 + 1)));
-		expect_pass(flowlabel_put(fd, 6));
-		sleep(FL_MIN_LINGER * 2 + 1);
-		explain("The label cannot be created because the new linger time is not over yet");
-		expect_fail_errno(flowlabel_get(fd, 6, IPV6_FL_S_ANY, IPV6_FL_F_CREATE), EPERM);
-	}
+	ASSERT_EQ(pid, wait(&wstatus)) TH_LOG("wait failed");
+	ASSERT_TRUE(WIFEXITED(wstatus)) TH_LOG("child did not exit normally");
+	ASSERT_EQ(0, WEXITSTATUS(wstatus)) TH_LOG("child reported unexpected result");
 
-	{
-		struct in6_flowlabel_req freq = {
-			.flr_action = IPV6_FL_A_GET,
-			.flr_flags = IPV6_FL_F_REMOTE,
-		};
-		socklen_t freq_len = sizeof(freq);
-		int remote_listener = tcp_listen();
-		int remote_cfd, remote_afd;
-
-		explain("Prepare TCP SYN for REMOTE flag validation");
-		tcp_connect(remote_listener, 7, &remote_cfd, &remote_afd);
-
-		explain("Query for label sent by client with IPV6_FL_F_REMOTE");
-		expect_pass(getsockopt(remote_afd, SOL_IPV6, IPV6_FLOWLABEL_MGR, &freq, &freq_len));
-		if (ntohl(freq.flr_label) != 7)
-			error(1, 0, "unexpected remote flowlabel %u", ntohl(freq.flr_label));
-
-		close(remote_afd);
-		close(remote_cfd);
-		close(remote_listener);
-	}
-
-	if (flowlabel_consistency_enabled()) {
-		fprintf(stderr,
-			"[INFO] skip REFLECT flag validation (net.ipv6.flowlabel_consistency must be 0)\n");
-	} else {
-		struct in6_flowlabel_req reflect_query = {
-			.flr_action = IPV6_FL_A_GET,
-		};
-		struct in6_flowlabel_req reflect_off = {
-			.flr_action = IPV6_FL_A_PUT,
-			.flr_flags = IPV6_FL_F_REFLECT,
-		};
-		struct in6_flowlabel_req reflect_on = {
-			.flr_action = IPV6_FL_A_GET,
-			.flr_flags = IPV6_FL_F_REFLECT,
-		};
-		socklen_t reflect_query_len = sizeof(reflect_query);
-		int reflect_listener = tcp_listen();
-		int reflect_cfd, reflect_afd;
-
-		explain("Enable REFLECT on the listener before the client connects");
-		expect_pass(setsockopt(reflect_listener, SOL_IPV6, IPV6_FLOWLABEL_MGR,
-				       &reflect_on, sizeof(reflect_on)));
-
-		tcp_connect(reflect_listener, 8, &reflect_cfd, &reflect_afd);
-
-		explain("Query the accepted socket's outgoing label, should be reflected");
-		expect_pass(getsockopt(reflect_afd, SOL_IPV6, IPV6_FLOWLABEL_MGR,
-				       &reflect_query, &reflect_query_len));
-		if (ntohl(reflect_query.flr_label) != 8)
-			error(1, 0, "unexpected reflected flowlabel %u",
-			      ntohl(reflect_query.flr_label));
-
-		explain("PUT+REFLECT disables reflection on the accepted socket");
-		expect_pass(setsockopt(reflect_afd, SOL_IPV6, IPV6_FLOWLABEL_MGR,
-				       &reflect_off, sizeof(reflect_off)));
-		explain("cannot disable reflection twice");
-		expect_fail(setsockopt(reflect_afd, SOL_IPV6, IPV6_FLOWLABEL_MGR,
-				       &reflect_off, sizeof(reflect_off)));
-
-		close(reflect_afd);
-		close(reflect_cfd);
-		close(reflect_listener);
-	}
+	ASSERT_EQ(0, close(fd));
 }
 
-static void parse_opts(int argc, char **argv)
+TEST(cannot_renew_non_existent_label)
 {
-	int c;
-
-	while ((c = getopt(argc, argv, "lv")) != -1) {
-		switch (c) {
-		case 'l':
-			cfg_long_running = true;
-			break;
-		case 'v':
-			cfg_verbose = true;
-			break;
-		default:
-			error(1, 0, "%s: parse error", argv[0]);
-		}
-	}
+	int fd, err;
+
+	fd = socket(PF_INET6, SOCK_DGRAM, 0);
+	ASSERT_GE(fd, 0) TH_LOG("socket failed");
+
+	err = flowlabel_renew(fd, 5, 2 * (FL_MIN_LINGER * 2 + 1));
+	ASSERT_TRUE(err) TH_LOG("expected renew of a non-existent label to fail");
+	ASSERT_EQ(ESRCH, errno) TH_LOG("expected ESRCH, got %d", errno);
+
+	ASSERT_EQ(0, close(fd));
 }
 
-int main(int argc, char **argv)
+TEST(can_renew_existing_label)
 {
-	int fd;
+	int fd, err;
+
+	fd = socket(PF_INET6, SOCK_DGRAM, 0);
+	ASSERT_GE(fd, 0) TH_LOG("socket failed");
+
+	err = flowlabel_get(fd, 5, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE);
+	ASSERT_TRUE(!err) TH_LOG("failed to create a new label for renew validation");
 
-	parse_opts(argc, argv);
+	err = flowlabel_renew(fd, 5, 2 * (FL_MIN_LINGER * 2 + 1));
+	ASSERT_TRUE(!err) TH_LOG("failed to renew an existing valid label");
+
+	err = flowlabel_put(fd, 5);
+	ASSERT_TRUE(!err) TH_LOG("failed to put the label");
+
+	ASSERT_EQ(0, close(fd));
+}
+
+TEST(renew_label_linger)
+{
+	/* RENEW must extend a label's linger period: putting a renewed
+	 * label and waiting out its original linger time must not be
+	 * enough to allow the label to be recreated.
+	 */
+	int fd, err;
 
 	fd = socket(PF_INET6, SOCK_DGRAM, 0);
+	ASSERT_GE(fd, 0) TH_LOG("socket failed");
+
+	err = flowlabel_get(fd, 6, IPV6_FL_S_EXCL, IPV6_FL_F_CREATE);
+	ASSERT_TRUE(!err) TH_LOG("failed to create a new label with FL_MIN_LINGER linger time");
+
+	err = flowlabel_renew(fd, 6, 2 * (FL_MIN_LINGER * 2 + 1));
+	ASSERT_TRUE(!err) TH_LOG("failed to renew the label to increase its linger time");
+
+	err = flowlabel_put(fd, 6);
+	ASSERT_TRUE(!err) TH_LOG("failed to put the label");
+
+	sleep(FL_MIN_LINGER * 2 + 1);
+
+	err = flowlabel_get(fd, 6, IPV6_FL_S_ANY, IPV6_FL_F_CREATE);
+	ASSERT_TRUE(err) TH_LOG("expected reuse to fail, new linger time not over yet");
+	ASSERT_EQ(EPERM, errno) TH_LOG("expected EPERM, got %d", errno);
+
+	ASSERT_EQ(0, close(fd));
+}
+
+TEST(remote_flag)
+{
+	/* The REMOTE flag, used for getsockopt, is expected to retrieve the
+	 * label from the latest received header.
+	 */
+	struct in6_flowlabel_req freq = {
+		.flr_action = IPV6_FL_A_GET,
+		.flr_flags = IPV6_FL_F_REMOTE,
+	};
+	socklen_t freq_len = sizeof(freq);
+	int listener, cfd, afd, err;
+
+	listener = tcp_listen();
+	tcp_connect(listener, 7, &cfd, &afd);
+
+	err = getsockopt(afd, SOL_IPV6, IPV6_FLOWLABEL_MGR, &freq, &freq_len);
+	ASSERT_TRUE(!err) TH_LOG("getsockopt with IPV6_FL_F_REMOTE failed");
+	ASSERT_EQ(7, ntohl(freq.flr_label)) TH_LOG("unexpected remote flow label");
+
+	ASSERT_EQ(0, close(afd));
+	ASSERT_EQ(0, close(cfd));
+	ASSERT_EQ(0, close(listener));
+}
+
+static bool flowlabel_consistency_enabled(void)
+{
+	char buf[2] = {};
+	int fd;
+
+	fd = open("/proc/sys/net/ipv6/flowlabel_consistency", O_RDONLY);
 	if (fd == -1)
-		error(1, errno, "socket");
+		return true;
+
+	if (read(fd, buf, sizeof(buf) - 1) < 0)
+		buf[0] = '1';
+	close(fd);
 
-	run_tests(fd);
+	return buf[0] != '0';
+}
+
+TEST(reflect_flag)
+{
+	/* The REFLECT flag acts as a trigger to the REPFLOW bit. When REPFLOW
+	 * is triggered for a socket, it adopts the label received from the
+	 * connected socket.
+	 */
+	struct in6_flowlabel_req reflect_on = {
+		.flr_action = IPV6_FL_A_GET,
+		.flr_flags = IPV6_FL_F_REFLECT,
+	};
+	struct in6_flowlabel_req reflect_query = {
+		.flr_action = IPV6_FL_A_GET,
+	};
+	struct in6_flowlabel_req reflect_off = {
+		.flr_action = IPV6_FL_A_PUT,
+		.flr_flags = IPV6_FL_F_REFLECT,
+	};
+	socklen_t reflect_query_len = sizeof(reflect_query);
+	int listener, cfd, afd, err;
+
+	if (flowlabel_consistency_enabled())
+		SKIP(return,
+		     "net.ipv6.flowlabel_consistency must be 0 (run via ipv6_flowlabel.sh)");
+
+	listener = tcp_listen();
+	err = setsockopt(listener, SOL_IPV6, IPV6_FLOWLABEL_MGR, &reflect_on, sizeof(reflect_on));
+	ASSERT_TRUE(!err) TH_LOG("failed to enable REFLECT on the listener");
 
-	if (close(fd))
-		error(1, errno, "close");
+	tcp_connect(listener, 8, &cfd, &afd);
 
-	return 0;
+	err = getsockopt(afd, SOL_IPV6, IPV6_FLOWLABEL_MGR, &reflect_query, &reflect_query_len);
+	ASSERT_TRUE(!err) TH_LOG("failed to query the accepted socket's outgoing label");
+	ASSERT_EQ(8, ntohl(reflect_query.flr_label))
+		TH_LOG("accepted socket did not reflect client's label");
+
+	err = setsockopt(afd, SOL_IPV6, IPV6_FLOWLABEL_MGR, &reflect_off, sizeof(reflect_off));
+	ASSERT_TRUE(!err) TH_LOG("failed to disable REFLECT on the accepted socket");
+
+	err = setsockopt(afd, SOL_IPV6, IPV6_FLOWLABEL_MGR, &reflect_off, sizeof(reflect_off));
+	ASSERT_TRUE(err) TH_LOG("expected disabling REFLECT twice to fail");
+	ASSERT_EQ(ESRCH, errno) TH_LOG("expected ESRCH, got %d", errno);
+
+	ASSERT_EQ(0, close(afd));
+	ASSERT_EQ(0, close(cfd));
+	ASSERT_EQ(0, close(listener));
 }
+
+TEST_HARNESS_MAIN
-- 
2.55.0