diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index 883e6648e79a..a02307ba069c 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -285,6 +285,29 @@ static int connect_variant(const int sock_fd,
 	return connect_variant_addrlen(sock_fd, srv, get_addrlen(srv, false));
 }
 
+static int get_msg_addr_variant(struct msghdr *msg,
+				const struct service_fixture *const srv)
+{
+	switch (srv->protocol.domain) {
+	case AF_UNSPEC:
+	case AF_INET:
+		msg->msg_name = (void *)&srv->ipv4_addr;
+		break;
+	case AF_INET6:
+		msg->msg_name = (void *)&srv->ipv6_addr;
+		break;
+	case AF_UNIX:
+		msg->msg_name = (void *)&srv->unix_addr;
+		break;
+	default:
+		errno = -EAFNOSUPPORT;
+		return -errno;
+	}
+
+	msg->msg_namelen = get_addrlen(srv, false);
+	return 0;
+}
+
 FIXTURE(protocol)
 {
 	struct service_fixture srv0, srv1, srv2, unspec_any0, unspec_srv0;
@@ -927,6 +950,356 @@ TEST_F(protocol, connect_unspec)
 	EXPECT_EQ(0, close(bind_fd));
 }
 
+FIXTURE(udp_send_recv)
+{
+	struct service_fixture srv_allowed;
+	struct service_fixture srv_denied;
+	struct service_fixture srv_ephemeral;
+	struct service_fixture addr_unspec0, addr_unspec1;
+	int srv_allowed_fd, srv_denied_fd, srv_ephemeral_fd, client_fd;
+	char read_buf[1];
+	struct iovec testmsg_contents;
+	struct msghdr testmsg;
+};
+
+FIXTURE_VARIANT(udp_send_recv)
+{
+	const struct protocol_variant prot;
+};
+
+FIXTURE_SETUP(udp_send_recv)
+{
+	const struct timeval read_timeout = {
+		.tv_sec = 0,
+		.tv_usec = 100000,
+	};
+	const struct protocol_variant prot_unspec = {
+		.domain = AF_UNSPEC,
+		.type = SOCK_STREAM,
+	};
+
+	disable_caps(_metadata);
+
+	ASSERT_EQ(0, set_service(&self->addr_unspec0, prot_unspec, 0));
+	ASSERT_EQ(0, set_service(&self->addr_unspec1, prot_unspec, 1));
+
+	/* Prepare one server socket to be denied */
+	ASSERT_EQ(0, set_service(&self->srv_denied, variant->prot, 0));
+	self->srv_denied_fd = socket_variant(&self->srv_denied);
+	ASSERT_LE(0, self->srv_denied_fd);
+	ASSERT_EQ(0, bind_variant(self->srv_denied_fd, &self->srv_denied));
+
+	/* Prepare one server socket on specific port to be allowed */
+	ASSERT_EQ(0, set_service(&self->srv_allowed, variant->prot, 1));
+	self->srv_allowed_fd = socket_variant(&self->srv_allowed);
+	ASSERT_LE(0, self->srv_allowed_fd);
+	ASSERT_EQ(0, bind_variant(self->srv_allowed_fd, &self->srv_allowed));
+
+	/* Prepare one server socket on ephemeral port to be allowed */
+	ASSERT_EQ(0, set_service(&self->srv_ephemeral, variant->prot, 0));
+	if (variant->prot.domain == AF_INET)
+		self->srv_ephemeral.ipv4_addr.sin_port = 0;
+	else if (variant->prot.domain == AF_INET6)
+		self->srv_ephemeral.ipv6_addr.sin6_port = 0;
+	self->srv_ephemeral_fd  = socket_variant(&self->srv_ephemeral);
+	ASSERT_LE(0, self->srv_ephemeral_fd);
+	ASSERT_EQ(0, bind_variant(self->srv_ephemeral_fd,
+				  &self->srv_ephemeral));
+	self->srv_ephemeral.port = get_binded_port(self->srv_ephemeral_fd,
+						   &variant->prot);
+	ASSERT_NE(0, self->srv_ephemeral.port);
+	if (variant->prot.domain == AF_INET)
+		self->srv_ephemeral.ipv4_addr.sin_port = htons(self->srv_ephemeral.port);
+	else if (variant->prot.domain == AF_INET6)
+		self->srv_ephemeral.ipv6_addr.sin6_port = htons(self->srv_ephemeral.port);
+
+	/* We must absolutely avoid blocking other tests indefinitely */
+	EXPECT_EQ(0, setsockopt(self->srv_allowed_fd, SOL_SOCKET,
+				SO_RCVTIMEO,
+				&read_timeout, sizeof(read_timeout)));
+	EXPECT_EQ(0, setsockopt(self->srv_denied_fd, SOL_SOCKET,
+				SO_RCVTIMEO,
+				&read_timeout, sizeof(read_timeout)));
+	EXPECT_EQ(0, setsockopt(self->srv_ephemeral_fd, SOL_SOCKET,
+				SO_RCVTIMEO,
+				&read_timeout, sizeof(read_timeout)));
+
+	self->client_fd = socket_variant(&self->srv_denied);
+	ASSERT_LE(0, self->client_fd);
+
+	self->read_buf[0] = 0;
+
+	self->testmsg_contents.iov_len = 1;
+	memset(&self->testmsg, 0, sizeof(self->testmsg));
+	self->testmsg.msg_iov = &self->testmsg_contents;
+	self->testmsg.msg_iovlen = 1;
+}
+
+FIXTURE_TEARDOWN(udp_send_recv)
+{
+	EXPECT_EQ(0, close(self->srv_allowed_fd));
+	EXPECT_EQ(0, close(self->srv_denied_fd));
+	EXPECT_EQ(0, close(self->client_fd));
+}
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(udp_send_recv, ipv4) {
+	.prot = {
+		.domain = AF_INET,
+		.type = SOCK_DGRAM,
+	},
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(udp_send_recv, ipv6) {
+	.prot = {
+		.domain = AF_INET6,
+		.type = SOCK_DGRAM,
+	},
+};
+
+TEST_F(udp_send_recv, sendmsg)
+{
+	const struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_SENDMSG_UDP,
+	};
+	const struct landlock_net_port_attr allow_one_server = {
+		.allowed_access = LANDLOCK_ACCESS_NET_SENDMSG_UDP,
+		.port = self->srv_allowed.port,
+	};
+	const int ruleset_fd = landlock_create_ruleset(
+		&ruleset_attr, sizeof(ruleset_attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
+				       LANDLOCK_RULE_NET_PORT,
+				       &allow_one_server, 0));
+
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/* Send without bind nor explicit address */
+	EXPECT_EQ(-1, write(self->client_fd, "A", 1));
+	EXPECT_EQ(EDESTADDRREQ, errno);
+
+	/* Send with an explicit denied address */
+	self->testmsg_contents.iov_base = "B";
+	EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg, &self->srv_denied));
+	EXPECT_EQ(-1, sendmsg(self->client_fd, &self->testmsg, 0));
+	EXPECT_EQ(EACCES, errno);
+	EXPECT_EQ(-1, read(self->srv_denied_fd, self->read_buf, sizeof(self->read_buf)));
+	EXPECT_EQ(EAGAIN, errno);
+
+	/* Send with an explicit allowed address */
+	self->testmsg_contents.iov_base = "C";
+	EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg, &self->srv_allowed));
+	EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0))
+	{
+		TH_LOG("sendmsg failed: %s", strerror(errno));
+	}
+	EXPECT_EQ(1, read(self->srv_allowed_fd, self->read_buf, sizeof(self->read_buf)));
+	EXPECT_EQ(self->read_buf[0], 'C');
+
+	/*
+	 * Sending to (AF_UNSPEC, port) should be equivalent to not specifying
+	 * a destination in IPv6, and equivalent to (AF_INET, port) in IPv4.
+	 */
+	if (variant->prot.domain == AF_INET) {
+		self->testmsg_contents.iov_base = "D";
+		EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg, &self->addr_unspec0));
+		EXPECT_EQ(-1, sendmsg(self->client_fd, &self->testmsg, 0));
+		EXPECT_EQ(EACCES, errno);
+
+		self->testmsg_contents.iov_base = "E";
+		EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg, &self->addr_unspec1));
+		EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0));
+		EXPECT_EQ(1, read(self->srv_allowed_fd, self->read_buf,
+				  sizeof(self->read_buf)));
+		EXPECT_EQ(self->read_buf[0], 'E');
+	} else if (variant->prot.domain == AF_INET6) {
+		EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg, &self->addr_unspec0));
+		EXPECT_EQ(-1, sendmsg(self->client_fd, &self->testmsg, 0));
+		EXPECT_EQ(EDESTADDRREQ, errno);
+	}
+
+	/* Send without an address, after connect()ing to an allowed address */
+	self->testmsg.msg_name = NULL;
+	self->testmsg.msg_namelen = 0;
+	self->testmsg_contents.iov_base = "F";
+	ASSERT_EQ(0, connect_variant(self->client_fd, &self->srv_allowed));
+	EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0))
+	{
+		TH_LOG("sendmsg failed: %s", strerror(errno));
+	}
+	EXPECT_EQ(1, read(self->srv_allowed_fd, self->read_buf,
+		  sizeof(self->read_buf)));
+	EXPECT_EQ(self->read_buf[0], 'F');
+
+	/*
+	 * Sending to AF_UNSPEC should be equivalent to not specifying an
+	 * address (in IPv6 only) and falling back to the allowed address.
+	 */
+	if (variant->prot.domain == AF_INET6) {
+		self->testmsg_contents.iov_base = "G";
+		EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg, &self->addr_unspec0));
+		EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0)) {
+			TH_LOG("sendmsg failed: %s", strerror(errno));
+		}
+		EXPECT_EQ(1, read(self->srv_allowed_fd, self->read_buf,
+			sizeof(self->read_buf)));
+		EXPECT_EQ(self->read_buf[0], 'G');
+	}
+
+	/* Send without an address, after connect()ing to a denied address */
+	self->testmsg_contents.iov_base = "H";
+	ASSERT_EQ(0, connect_variant(self->client_fd, &self->srv_denied));
+	EXPECT_EQ(-1, sendmsg(self->client_fd, &self->testmsg, 0));
+	EXPECT_EQ(EACCES, errno);
+	EXPECT_EQ(-1, read(self->srv_denied_fd, self->read_buf, sizeof(self->read_buf)));
+	EXPECT_EQ(EAGAIN, errno);
+
+	/*
+	 * Sending to AF_UNSPEC should be equivalent to not specifying an
+	 * address (in IPv6 only) and falling back to the denied address.
+	 */
+	if (variant->prot.domain == AF_INET6) {
+		self->testmsg_contents.iov_base = "I";
+		EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg, &self->addr_unspec0));
+		EXPECT_EQ(-1, sendmsg(self->client_fd, &self->testmsg, 0));
+		EXPECT_EQ(EACCES, errno);
+	}
+
+	/* Send with an explicit allowed address, should overrule connect() */
+	self->testmsg_contents.iov_base = "J";
+	ASSERT_EQ(0, connect_variant(self->client_fd, &self->srv_denied));
+	EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg, &self->srv_allowed));
+	EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0))
+	{
+		TH_LOG("sendmsg failed: %s", strerror(errno));
+	}
+	EXPECT_EQ(1, read(self->srv_allowed_fd, self->read_buf, sizeof(self->read_buf)));
+	EXPECT_EQ(self->read_buf[0], 'J');
+
+	/* Send with an explicit denied address, should overrule connect() */
+	self->testmsg_contents.iov_base = "K";
+	ASSERT_EQ(0, connect_variant(self->client_fd, &self->srv_allowed));
+	EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg, &self->srv_denied));
+	EXPECT_EQ(-1, sendmsg(self->client_fd, &self->testmsg, 0));
+	EXPECT_EQ(EACCES, errno);
+	EXPECT_EQ(-1, read(self->srv_allowed_fd,
+			   self->read_buf, sizeof(self->read_buf)));
+	EXPECT_EQ(EAGAIN, errno);
+	EXPECT_EQ(-1, read(self->srv_denied_fd, self->read_buf, sizeof(self->read_buf)));
+	EXPECT_EQ(EAGAIN, errno);
+}
+
+TEST_F(udp_send_recv, recvmsg_on_fixed_port)
+{
+	struct sockaddr_storage from = {0};
+	socklen_t from_len = sizeof(from);
+	const struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_RECVMSG_UDP,
+	};
+	const struct landlock_net_port_attr allow_one_server = {
+		.allowed_access = LANDLOCK_ACCESS_NET_RECVMSG_UDP,
+		.port = self->srv_allowed.port,
+	};
+	const int ruleset_fd = landlock_create_ruleset(
+		&ruleset_attr, sizeof(ruleset_attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
+					LANDLOCK_RULE_NET_PORT,
+					&allow_one_server, 0));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	/* Receive on an allowed port with read() */
+	EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg, &self->srv_allowed));
+	self->testmsg_contents.iov_base = "A";
+	EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0));
+	EXPECT_EQ(1, read(self->srv_allowed_fd, self->read_buf,
+			  sizeof(self->read_buf))) {
+		TH_LOG("read failed: %s", strerror(errno));
+	}
+	EXPECT_EQ(self->read_buf[0], 'A');
+
+	/* Receive on an allowed port with recv() */
+	self->testmsg_contents.iov_base = "B";
+	EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0));
+	EXPECT_EQ(1, recv(self->srv_allowed_fd,
+			  self->read_buf, sizeof(self->read_buf), 0)) {
+		TH_LOG("recv failed: %s", strerror(errno));
+	}
+	EXPECT_EQ(self->read_buf[0], 'B');
+
+	/* Receive on an allowed port with recvfrom() */
+	self->testmsg_contents.iov_base = "C";
+	EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0));
+	EXPECT_EQ(1, recvfrom(self->srv_allowed_fd,
+			      self->read_buf, sizeof(self->read_buf), 0,
+			      (struct sockaddr *)&from, &from_len)) {
+		TH_LOG("recv failed: %s", strerror(errno));
+	}
+	EXPECT_EQ(self->read_buf[0], 'C');
+
+	/* Receive on a denied port with read() */
+	EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg, &self->srv_allowed));
+	self->testmsg_contents.iov_base = "D";
+	EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0));
+	EXPECT_EQ(-1, read(self->srv_denied_fd, self->read_buf, sizeof(self->read_buf)));
+	EXPECT_EQ(EACCES, errno);
+	EXPECT_EQ(self->read_buf[0], 'C');
+
+	/* Receive on an denied port with recv() */
+	self->testmsg_contents.iov_base = "B";
+	EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0));
+	EXPECT_EQ(-1, recv(self->srv_denied_fd, self->read_buf,
+			   sizeof(self->read_buf), 0));
+	EXPECT_EQ(EACCES, errno);
+	EXPECT_EQ(self->read_buf[0], 'C');
+
+	/* Receive on an denied port with recvfrom() */
+	self->testmsg_contents.iov_base = "C";
+	EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0));
+	EXPECT_EQ(-1, recvfrom(self->srv_denied_fd, self->read_buf,
+			       sizeof(self->read_buf), 0,
+			       (struct sockaddr *)&from, &from_len));
+	EXPECT_EQ(EACCES, errno);
+	EXPECT_EQ(self->read_buf[0], 'C');
+}
+
+TEST_F(udp_send_recv, recvmsg_on_ephemeral_port)
+{
+	const struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_RECVMSG_UDP,
+	};
+	const struct landlock_net_port_attr allow_one_server = {
+		.allowed_access = LANDLOCK_ACCESS_NET_RECVMSG_UDP,
+		.port = 0,
+	};
+	const int ruleset_fd = landlock_create_ruleset(
+		&ruleset_attr, sizeof(ruleset_attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+
+	ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
+				       LANDLOCK_RULE_NET_PORT,
+				       &allow_one_server, 0));
+	enforce_ruleset(_metadata, ruleset_fd);
+	EXPECT_EQ(0, close(ruleset_fd));
+
+	EXPECT_EQ(0, get_msg_addr_variant(&self->testmsg,
+					  &self->srv_ephemeral));
+
+	self->testmsg_contents.iov_base = "A";
+	EXPECT_EQ(1, sendmsg(self->client_fd, &self->testmsg, 0));
+	EXPECT_EQ(1, read(self->srv_ephemeral_fd, self->read_buf,
+			  sizeof(self->read_buf))) {
+		TH_LOG("read failed: %s", strerror(errno));
+	}
+	EXPECT_EQ(self->read_buf[0], 'A');
+}
+
 FIXTURE(ipv4)
 {
 	struct service_fixture srv0, srv1;
