Re: [RFC PATCH 2/2] selftests/landlock: Add test for TCP fast open
From: Mickaël Salaün <mic@digikod.net>
Date: 2026-06-26 21:30:09
Also in:
lkml, netdev
On Wed, Jun 17, 2026 at 08:05:24PM +0200, Matthieu Buffet wrote:
quoted hunk ↗ jump to hunk
Enforce that TCP Fast Open is controlled by LANDLOCK_ACCESS_NET_CONNECT_TCP. Semantics of connect() and sendmsg(MSG_FASTOPEN) should be identical from Landlock's perspective. Also enforce error code consistency, since UDP sockets ignore the MSG_FASTOPEN flag while Unix sockets reject it. Signed-off-by: Matthieu Buffet <redacted> --- tools/testing/selftests/landlock/net_test.c | 155 ++++++++++++++++++++ 1 file changed, 155 insertions(+)diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index 0c256e7c8675..177ed28e70f6 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c@@ -258,6 +258,64 @@ static int connect_variant(const int sock_fd, return connect_variant_addrlen(sock_fd, srv, get_addrlen(srv, false)); } +static int sendto_variant_addrlen(const int sock_fd, + const struct service_fixture *const srv, + const socklen_t addrlen, void *buf, + size_t len, size_t flags) +{ + const struct sockaddr *dst = NULL; + ssize_t ret; + + /* + * We never want our processes to be killed by SIGPIPE: we check return + * codes and errno, so that we have actual error messages. + */
There are some extra spaces above.
+ flags |= MSG_NOSIGNAL;
+
+ if (srv != NULL) {
Just `if (srv) {`
+ switch (srv->protocol.domain) {
+ case AF_UNSPEC:
+ case AF_INET:
+ dst = (const struct sockaddr *)&srv->ipv4_addr;
+ break;
+
+ case AF_INET6:
+ dst = (const struct sockaddr *)&srv->ipv6_addr;
+ break;
+
+ case AF_UNIX:
+ dst = (const struct sockaddr *)&srv->unix_addr;
+ break;
+
+ default:
+ errno = EAFNOSUPPORT;
+ return -errno;
+ }
+ }
+
+ ret = sendto(sock_fd, buf, len, flags, dst, addrlen);
+ if (ret < 0)
+ return -errno;
+
+ /* errno is not set in cases of partial writes. */
+ if (ret != len)
+ return -EINTR;
+
+ return 0;
+}
+
+static int sendto_variant(const int sock_fd,
+ const struct service_fixture *const srv, void *buf,
+ size_t len, size_t flags)
+{
+ socklen_t addrlen = 0;
+
+ if (srv != NULL)ditto
quoted hunk ↗ jump to hunk
+ addrlen = get_addrlen(srv, false); + + return sendto_variant_addrlen(sock_fd, srv, addrlen, buf, len, flags); +} + FIXTURE(protocol) { struct service_fixture srv0, srv1, srv2, unspec_any0, unspec_srv0;@@ -950,6 +1008,103 @@ TEST_F(protocol, connect_unspec) EXPECT_EQ(0, close(bind_fd)); } +TEST_F(protocol, tcp_fastopen) +{ + const bool restricted = variant->sandbox == TCP_SANDBOX && + variant->prot.type == SOCK_STREAM && + (variant->prot.protocol == IPPROTO_TCP || + variant->prot.protocol == IPPROTO_IP) && + (variant->prot.domain == AF_INET || + variant->prot.domain == AF_INET6); + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + int bind_fd, client_fd, status; + char buf; + pid_t child; + + bind_fd = socket_variant(&self->srv0); + ASSERT_LE(0, bind_fd); + EXPECT_EQ(0, bind_variant(bind_fd, &self->srv0)); + if (self->srv0.protocol.type == SOCK_STREAM) + EXPECT_EQ(0, listen(bind_fd, backlog)); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + int connect_fd, ret; + + /* Closes listening socket for the child. */ + EXPECT_EQ(0, close(bind_fd)); + + connect_fd = socket_variant(&self->srv0); + ASSERT_LE(0, connect_fd); + + if (variant->sandbox == TCP_SANDBOX) { + const int ruleset_fd = landlock_create_ruleset( + &ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + } + + /* Fast Open with no address. */ + ret = sendto_variant(connect_fd, NULL, NULL, 0, MSG_FASTOPEN);
+ if (self->srv0.protocol.domain == AF_UNIX) {
+ ASSERT_EQ(-ENOTCONN, ret);
+ } else if (self->srv0.protocol.type == SOCK_DGRAM) {
+ ASSERT_EQ(-EDESTADDRREQ, ret);
+ } else {
+ ASSERT_EQ(-EINVAL, ret);
+ }
+
+ /* Fast Open to a denied address. */
+ ret = sendto_variant(connect_fd, &self->srv0, "A", 1,
+ MSG_FASTOPEN);
+ if (restricted) {
+ ASSERT_EQ(-EACCES, ret);
+ } else if (self->srv0.protocol.domain == AF_UNIX &&
+ self->srv0.protocol.type == SOCK_STREAM) {
+ ASSERT_EQ(-EOPNOTSUPP, ret);
+ } else {
+ ASSERT_EQ(0, ret);
+ }All these ret checks should be done with EXPECT_EQ because they don't block the test itself and we can get more info by checking more after that.
+
+ EXPECT_EQ(0, close(connect_fd));
+ _exit(_metadata->exit_code);
+ return;
+ }
+
+ client_fd = bind_fd;
+ if (!restricted && self->srv0.protocol.type == SOCK_STREAM &&
+ self->srv0.protocol.domain != AF_UNIX) {
+ client_fd = accept(bind_fd, NULL, 0);
+ ASSERT_LE(0, client_fd);
+ }
+
+ if (restricted) {
+ EXPECT_EQ(-1, read(client_fd, &buf, 1));
+ EXPECT_EQ(ENOTCONN, errno);
+ } else if (self->srv0.protocol.domain == AF_UNIX &&
+ self->srv0.protocol.type == SOCK_STREAM) {
+ EXPECT_EQ(-1, read(client_fd, &buf, 1));
+ EXPECT_EQ(EINVAL, errno);
+ } else {
+ EXPECT_EQ(1, read(client_fd, &buf, 1));
+ EXPECT_EQ('A', buf);
+ }
+
+ EXPECT_EQ(child, waitpid(child, &status, 0));
+ EXPECT_EQ(1, WIFEXITED(status));
+ EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+
+ if (client_fd != bind_fd)
+ EXPECT_LE(0, close(client_fd));
+
+ EXPECT_EQ(0, close(bind_fd));
+}
+
FIXTURE(ipv4)
{
struct service_fixture srv0, srv1;
--
2.47.3