ipcz: Linux multiprocess reference driver
Implements a basic multiprocess reference driver and enables its
parameterized use in all multinode ipcz tests when running on Linux.
Test nodes still run in the test process for now when this driver
is in use, but a follow-up change will move them to isolated
child processes.
Bug: 1299283
Change-Id: I310c4c1b722800090b4dffdd940f9e6b7c4661bc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3661709
Commit-Queue: Ken Rockot <rockot@google.com>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1016922}
NOKEYCHECK=True
GitOrigin-RevId: 44e9c5579242c810de4b2186c0c0ae0bfb50f12c
diff --git a/src/BUILD.gn b/src/BUILD.gn
index 71cc01e..f679514 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -146,12 +146,16 @@
public += [
"reference_drivers/file_descriptor.h",
"reference_drivers/memfd_memory.h",
+ "reference_drivers/multiprocess_reference_driver.h",
"reference_drivers/socket_transport.h",
+ "reference_drivers/wrapped_file_descriptor.h",
]
sources += [
"reference_drivers/file_descriptor.cc",
"reference_drivers/memfd_memory.cc",
+ "reference_drivers/multiprocess_reference_driver.cc",
"reference_drivers/socket_transport.cc",
+ "reference_drivers/wrapped_file_descriptor.cc",
]
}
@@ -333,6 +337,7 @@
if (is_linux) {
sources += [
"reference_drivers/memfd_memory_test.cc",
+ "reference_drivers/multiprocess_reference_driver_test.cc",
"reference_drivers/socket_transport_test.cc",
]
}
diff --git a/src/box_test.cc b/src/box_test.cc
index bd39f3d..ec814f1 100644
--- a/src/box_test.cc
+++ b/src/box_test.cc
@@ -16,61 +16,21 @@
using Blob = reference_drivers::Blob;
-class BoxTestNode : public test::TestNode {
- protected:
- // Creates a test driver Blob object with an inlined data payload and a shared
- // memory object with an embedded message.
- IpczDriverHandle CreateTestBlob(std::string_view inline_message,
- std::string_view shm_message) {
- IpczDriverHandle memory;
- EXPECT_EQ(IPCZ_RESULT_OK,
- GetDriver().AllocateSharedMemory(
- shm_message.size(), IPCZ_NO_FLAGS, nullptr, &memory));
-
- void* address;
- IpczDriverHandle mapping;
- EXPECT_EQ(IPCZ_RESULT_OK,
- GetDriver().MapSharedMemory(memory, IPCZ_NO_FLAGS, nullptr,
- &address, &mapping));
- memcpy(address, shm_message.data(), shm_message.size());
- EXPECT_EQ(IPCZ_RESULT_OK,
- GetDriver().Close(mapping, IPCZ_NO_FLAGS, nullptr));
-
- return Blob::ReleaseAsHandle(MakeRefCounted<Blob>(
- GetDriver(), inline_message, absl::MakeSpan(&memory, 1)));
- }
-
- bool BlobContentsMatch(IpczDriverHandle blob_handle,
- std::string_view expected_inline_message,
- std::string_view expected_shm_message) {
- Ref<Blob> blob = Blob::TakeFromHandle(blob_handle);
- if (expected_inline_message != blob->message()) {
- return false;
- }
-
- ABSL_ASSERT(blob->handles().size() == 1);
- ABSL_ASSERT(blob->handles()[0] != IPCZ_INVALID_DRIVER_HANDLE);
-
- void* address;
- IpczDriverHandle mapping;
- EXPECT_EQ(IPCZ_RESULT_OK,
- GetDriver().MapSharedMemory(blob->handles()[0], IPCZ_NO_FLAGS,
- nullptr, &address, &mapping));
- std::string_view shm_message(static_cast<char*>(address),
- expected_shm_message.size());
- const bool matched = (shm_message == expected_shm_message);
- EXPECT_EQ(IPCZ_RESULT_OK,
- GetDriver().Close(mapping, IPCZ_NO_FLAGS, nullptr));
- return matched;
- }
-};
-
+using BoxTestNode = test::TestNode;
using BoxTest = test::MultinodeTest<BoxTestNode>;
+IpczDriverHandle CreateTestBlob(std::string_view message) {
+ return Blob::ReleaseAsHandle(MakeRefCounted<Blob>(message));
+}
+
+std::string GetBlobContents(IpczDriverHandle handle) {
+ Ref<Blob> blob = Blob::TakeFromHandle(handle);
+ return std::string(blob->message());
+}
+
TEST_P(BoxTest, BoxAndUnbox) {
constexpr const char kMessage[] = "Hello, world?";
- IpczDriverHandle blob_handle =
- Blob::ReleaseAsHandle(MakeRefCounted<Blob>(GetDriver(), kMessage));
+ IpczDriverHandle blob_handle = CreateTestBlob(kMessage);
IpczHandle box;
EXPECT_EQ(IPCZ_RESULT_OK,
@@ -79,13 +39,11 @@
blob_handle = IPCZ_INVALID_DRIVER_HANDLE;
EXPECT_EQ(IPCZ_RESULT_OK,
ipcz().Unbox(box, IPCZ_NO_FLAGS, nullptr, &blob_handle));
-
- Ref<Blob> blob = Blob::TakeFromHandle(blob_handle);
- EXPECT_EQ(kMessage, blob->message());
+ EXPECT_EQ(kMessage, GetBlobContents(blob_handle));
}
TEST_P(BoxTest, CloseBox) {
- Ref<Blob> blob = MakeRefCounted<Blob>(GetDriver(), "meh");
+ Ref<Blob> blob = MakeRefCounted<Blob>("meh");
Ref<Blob::RefCountedFlag> destroyed = blob->destruction_flag_for_testing();
IpczDriverHandle blob_handle = Blob::ReleaseAsHandle(std::move(blob));
@@ -100,8 +58,7 @@
TEST_P(BoxTest, Peek) {
constexpr const char kMessage[] = "Hello, world?";
- IpczDriverHandle blob_handle =
- Blob::ReleaseAsHandle(MakeRefCounted<Blob>(GetDriver(), kMessage));
+ IpczDriverHandle blob_handle = CreateTestBlob(kMessage);
IpczHandle box;
EXPECT_EQ(IPCZ_RESULT_OK,
ipcz().Box(node(), blob_handle, IPCZ_NO_FLAGS, nullptr, &box));
@@ -126,7 +83,6 @@
constexpr const char kMessage1[] = "Hello, world?";
constexpr const char kMessage2[] = "Hello, world!";
-constexpr const char kMessage3[] = "Hello! World!";
MULTINODE_TEST_NODE(BoxTestNode, TransferBoxClient) {
IpczHandle b = ConnectToBroker();
@@ -134,12 +90,12 @@
std::string message;
IpczHandle box;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(b, &message, {&box, 1}));
- EXPECT_EQ(kMessage3, message);
+ EXPECT_EQ(kMessage2, message);
IpczDriverHandle blob_handle;
EXPECT_EQ(IPCZ_RESULT_OK,
ipcz().Unbox(box, IPCZ_NO_FLAGS, nullptr, &blob_handle));
- EXPECT_TRUE(BlobContentsMatch(blob_handle, kMessage1, kMessage2));
+ EXPECT_EQ(kMessage1, GetBlobContents(blob_handle));
Close(b);
}
@@ -147,12 +103,12 @@
TEST_P(BoxTest, TransferBox) {
IpczHandle c = SpawnTestNode<TransferBoxClient>();
- IpczDriverHandle blob_handle = CreateTestBlob(kMessage1, kMessage2);
+ IpczDriverHandle blob_handle = CreateTestBlob(kMessage1);
IpczHandle box;
EXPECT_EQ(IPCZ_RESULT_OK,
ipcz().Box(node(), blob_handle, IPCZ_NO_FLAGS, nullptr, &box));
- EXPECT_EQ(IPCZ_RESULT_OK, Put(c, kMessage3, {&box, 1}));
+ EXPECT_EQ(IPCZ_RESULT_OK, Put(c, kMessage2, {&box, 1}));
Close(c);
}
@@ -165,11 +121,11 @@
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(b, nullptr, {&q, 1}));
for (size_t i = 0; i < TransferBoxBetweenNonBrokersNumIterations; ++i) {
- IpczDriverHandle blob_handle = CreateTestBlob(kMessage1, kMessage2);
+ IpczDriverHandle blob_handle = CreateTestBlob(kMessage1);
IpczHandle box;
EXPECT_EQ(IPCZ_RESULT_OK,
ipcz().Box(node(), blob_handle, IPCZ_NO_FLAGS, nullptr, &box));
- EXPECT_EQ(IPCZ_RESULT_OK, Put(q, kMessage3, {&box, 1}));
+ EXPECT_EQ(IPCZ_RESULT_OK, Put(q, kMessage2, {&box, 1}));
box = IPCZ_INVALID_DRIVER_HANDLE;
std::string message;
@@ -177,7 +133,7 @@
EXPECT_EQ(kMessage1, message);
EXPECT_EQ(IPCZ_RESULT_OK,
ipcz().Unbox(box, IPCZ_NO_FLAGS, nullptr, &blob_handle));
- EXPECT_TRUE(BlobContentsMatch(blob_handle, kMessage2, kMessage3));
+ EXPECT_EQ(kMessage2, GetBlobContents(blob_handle));
}
CloseAll({q, b});
@@ -193,12 +149,12 @@
IpczDriverHandle blob_handle;
std::string message;
EXPECT_EQ(IPCZ_RESULT_OK, WaitToGet(p, &message, {&box, 1}));
- EXPECT_EQ(kMessage3, message);
+ EXPECT_EQ(kMessage2, message);
EXPECT_EQ(IPCZ_RESULT_OK,
ipcz().Unbox(box, IPCZ_NO_FLAGS, nullptr, &blob_handle));
- EXPECT_TRUE(BlobContentsMatch(blob_handle, kMessage1, kMessage2));
+ EXPECT_EQ(kMessage1, GetBlobContents(blob_handle));
- blob_handle = CreateTestBlob(kMessage2, kMessage3);
+ blob_handle = CreateTestBlob(kMessage2);
EXPECT_EQ(IPCZ_RESULT_OK,
ipcz().Box(node(), blob_handle, IPCZ_NO_FLAGS, nullptr, &box));
EXPECT_EQ(IPCZ_RESULT_OK, Put(p, kMessage1, {&box, 1}));
diff --git a/src/reference_drivers/blob.cc b/src/reference_drivers/blob.cc
index 451ad6f..bbebebe 100644
--- a/src/reference_drivers/blob.cc
+++ b/src/reference_drivers/blob.cc
@@ -14,19 +14,9 @@
Blob::RefCountedFlag::~RefCountedFlag() = default;
-Blob::Blob(const IpczDriver& driver,
- std::string_view message,
- absl::Span<IpczDriverHandle> handles)
- : driver_(driver),
- message_(message),
- handles_(std::move_iterator(handles.begin()),
- std::move_iterator(handles.end())) {}
+Blob::Blob(std::string_view message) : message_(message) {}
-Blob::~Blob() {
- for (IpczDriverHandle handle : handles_) {
- driver_.Close(handle, IPCZ_NO_FLAGS, nullptr);
- }
-}
+Blob::~Blob() = default;
IpczResult Blob::Close() {
destruction_flag_for_testing_->set(true);
diff --git a/src/reference_drivers/blob.h b/src/reference_drivers/blob.h
index ef9ad18..84be378 100644
--- a/src/reference_drivers/blob.h
+++ b/src/reference_drivers/blob.h
@@ -5,19 +5,16 @@
#ifndef IPCZ_SRC_REFERENCE_DRIVERS_BLOB_H_
#define IPCZ_SRC_REFERENCE_DRIVERS_BLOB_H_
-#include <cstdint>
+#include <string>
#include <string_view>
-#include <vector>
#include "reference_drivers/object.h"
-#include "third_party/abseil-cpp/absl/types/span.h"
#include "util/ref_counted.h"
namespace ipcz::reference_drivers {
-// A driver-managed object which packages an arbitrary collection of string data
-// and transmissible driver handles. Blobs are used to exercise driver object
-// boxing in tests.
+// A driver-managed object which packages arbitrary string data. Blobs are used
+// to exercise driver object boxing in tests.
//
// Note that unlike the transport and memory objects defined by the reference
// drivers, a blob is not a type of object known to ipcz. Instead it is used to
@@ -37,15 +34,12 @@
bool flag_ = false;
};
- Blob(const IpczDriver& driver,
- std::string_view message,
- absl::Span<IpczDriverHandle> handles = {});
+ explicit Blob(std::string_view message);
// Object:
IpczResult Close() override;
std::string& message() { return message_; }
- std::vector<IpczDriverHandle>& handles() { return handles_; }
const Ref<RefCountedFlag>& destruction_flag_for_testing() const {
return destruction_flag_for_testing_;
@@ -58,9 +52,7 @@
~Blob() override;
private:
- const IpczDriver& driver_;
std::string message_;
- std::vector<IpczDriverHandle> handles_;
const Ref<RefCountedFlag> destruction_flag_for_testing_{
MakeRefCounted<RefCountedFlag>()};
};
diff --git a/src/reference_drivers/file_descriptor.h b/src/reference_drivers/file_descriptor.h
index bd9385a..6e2233e 100644
--- a/src/reference_drivers/file_descriptor.h
+++ b/src/reference_drivers/file_descriptor.h
@@ -5,6 +5,8 @@
#ifndef IPCZ_SRC_REFERENCE_DRIVERS_FILE_DESCRIPTOR_H_
#define IPCZ_SRC_REFERENCE_DRIVERS_FILE_DESCRIPTOR_H_
+#include <utility>
+
namespace ipcz::reference_drivers {
// Implements unique ownership of a single POSIX file descriptor.
@@ -23,6 +25,8 @@
void reset();
+ [[nodiscard]] int release() { return std::exchange(fd_, -1); }
+
// Duplicates the underlying descriptor, returning a new FileDescriptor object
// to wrap it. This object must be valid before calling Clone().
FileDescriptor Clone() const;
diff --git a/src/reference_drivers/multiprocess_reference_driver.cc b/src/reference_drivers/multiprocess_reference_driver.cc
new file mode 100644
index 0000000..904cefc
--- /dev/null
+++ b/src/reference_drivers/multiprocess_reference_driver.cc
@@ -0,0 +1,434 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "reference_drivers/multiprocess_reference_driver.h"
+
+#include <cstring>
+#include <limits>
+#include <memory>
+#include <tuple>
+#include <utility>
+
+#include "ipcz/ipcz.h"
+#include "reference_drivers/blob.h"
+#include "reference_drivers/file_descriptor.h"
+#include "reference_drivers/memfd_memory.h"
+#include "reference_drivers/object.h"
+#include "reference_drivers/random.h"
+#include "reference_drivers/socket_transport.h"
+#include "reference_drivers/wrapped_file_descriptor.h"
+#include "third_party/abseil-cpp/absl/synchronization/mutex.h"
+#include "util/ref_counted.h"
+#include "util/safe_math.h"
+
+namespace ipcz::reference_drivers {
+
+namespace {
+
+// A transport implementation based on a SocketTransport.
+class MultiprocessTransport
+ : public ObjectImpl<MultiprocessTransport, Object::kTransport> {
+ public:
+ explicit MultiprocessTransport(std::unique_ptr<SocketTransport> transport)
+ : transport_(std::move(transport)) {}
+ MultiprocessTransport(const MultiprocessTransport&) = delete;
+ MultiprocessTransport& operator=(const MultiprocessTransport&) = delete;
+
+ void Activate(IpczHandle transport,
+ IpczTransportActivityHandler activity_handler) {
+ was_activated_ = true;
+ ipcz_transport_ = transport;
+ activity_handler_ = activity_handler;
+
+ absl::MutexLock lock(&transport_mutex_);
+ transport_->Activate(
+ [transport = WrapRefCounted(this)](SocketTransport::Message message) {
+ return transport->OnMessage(message);
+ },
+ [transport = WrapRefCounted(this)]() { transport->OnError(); });
+ }
+
+ void Deactivate() {
+ {
+ absl::MutexLock lock(&transport_mutex_);
+ transport_->Deactivate();
+ transport_.reset();
+ }
+
+ if (activity_handler_) {
+ activity_handler_(ipcz_transport_, nullptr, 0, nullptr, 0,
+ IPCZ_TRANSPORT_ACTIVITY_DEACTIVATED, nullptr);
+ }
+ }
+
+ IpczResult Transmit(absl::Span<const uint8_t> data,
+ absl::Span<const IpczDriverHandle> handles) {
+ std::vector<FileDescriptor> descriptors(handles.size());
+ for (size_t i = 0; i < handles.size(); ++i) {
+ ABSL_ASSERT(Object::FromHandle(handles[i])->type() ==
+ Object::kFileDescriptor);
+ descriptors[i] =
+ WrappedFileDescriptor::TakeFromHandle(handles[i])->TakeDescriptor();
+ }
+
+ {
+ absl::MutexLock lock(&transport_mutex_);
+ if (transport_) {
+ transport_->Send({data, absl::MakeSpan(descriptors)});
+ return IPCZ_RESULT_OK;
+ }
+ }
+
+ return IPCZ_RESULT_OK;
+ }
+
+ FileDescriptor TakeDescriptor() {
+ ABSL_ASSERT(!was_activated_);
+ absl::MutexLock lock(&transport_mutex_);
+ return transport_->TakeDescriptor();
+ }
+
+ private:
+ ~MultiprocessTransport() override = default;
+
+ bool OnMessage(const SocketTransport::Message& message) {
+ std::vector<IpczDriverHandle> handles(message.descriptors.size());
+ for (size_t i = 0; i < handles.size(); ++i) {
+ handles[i] =
+ Object::ReleaseAsHandle(MakeRefCounted<WrappedFileDescriptor>(
+ std::move(message.descriptors[i])));
+ }
+
+ ABSL_ASSERT(activity_handler_);
+ IpczResult result = activity_handler_(
+ ipcz_transport_, message.data.data(), message.data.size(),
+ handles.data(), handles.size(), IPCZ_NO_FLAGS, nullptr);
+ return result == IPCZ_RESULT_OK || result == IPCZ_RESULT_UNIMPLEMENTED;
+ }
+
+ void OnError() {
+ activity_handler_(ipcz_transport_, nullptr, 0, nullptr, 0,
+ IPCZ_TRANSPORT_ACTIVITY_ERROR, nullptr);
+ }
+
+ IpczHandle ipcz_transport_ = IPCZ_INVALID_HANDLE;
+ IpczTransportActivityHandler activity_handler_;
+ bool was_activated_ = false;
+
+ absl::Mutex transport_mutex_;
+ std::unique_ptr<SocketTransport> transport_ ABSL_GUARDED_BY(transport_mutex_);
+};
+
+class MultiprocessMemoryMapping
+ : public ObjectImpl<MultiprocessMemoryMapping, Object::kMapping> {
+ public:
+ explicit MultiprocessMemoryMapping(MemfdMemory::Mapping mapping)
+ : mapping_(std::move(mapping)) {}
+
+ void* address() const { return mapping_.base(); }
+
+ private:
+ ~MultiprocessMemoryMapping() override = default;
+
+ const MemfdMemory::Mapping mapping_;
+};
+
+class MultiprocessMemory
+ : public ObjectImpl<MultiprocessMemory, Object::kMemory> {
+ public:
+ explicit MultiprocessMemory(size_t num_bytes) : memory_(num_bytes) {}
+ MultiprocessMemory(FileDescriptor descriptor, size_t num_bytes)
+ : memory_(std::move(descriptor), num_bytes) {}
+
+ size_t size() const { return memory_.size(); }
+
+ Ref<MultiprocessMemory> Clone() {
+ return MakeRefCounted<MultiprocessMemory>(memory_.Clone().TakeDescriptor(),
+ memory_.size());
+ }
+
+ Ref<MultiprocessMemoryMapping> Map() {
+ return MakeRefCounted<MultiprocessMemoryMapping>(memory_.Map());
+ }
+
+ FileDescriptor TakeDescriptor() { return memory_.TakeDescriptor(); }
+
+ private:
+ ~MultiprocessMemory() override = default;
+
+ MemfdMemory memory_;
+};
+
+// Header at the start of every driver object serialized by this driver.
+struct IPCZ_ALIGN(8) SerializedObjectHeader {
+ // Enumeration indicating which type of driver object this is.
+ Object::Type type;
+
+ // For a memory object, the size of the underlying region. Ignored otherwise.
+ uint32_t memory_size;
+};
+
+IpczResult IPCZ_API Close(IpczDriverHandle handle,
+ uint32_t flags,
+ const void* options) {
+ Ref<Object> object = Object::TakeFromHandle(handle);
+ if (!object) {
+ return IPCZ_RESULT_INVALID_ARGUMENT;
+ }
+
+ object->Close();
+ return IPCZ_RESULT_OK;
+}
+
+IpczResult IPCZ_API Serialize(IpczDriverHandle handle,
+ IpczDriverHandle transport,
+ uint32_t flags,
+ const void* options,
+ void* data,
+ size_t* num_bytes,
+ IpczDriverHandle* handles,
+ size_t* num_handles) {
+ Object* object = Object::FromHandle(handle);
+ if (!object) {
+ return IPCZ_RESULT_INVALID_ARGUMENT;
+ }
+
+ // First compute the serialized dimensions.
+ size_t required_num_bytes = sizeof(SerializedObjectHeader);
+ size_t required_num_handles;
+ switch (object->type()) {
+ case Object::kTransport:
+ case Object::kMemory:
+ required_num_handles = 1;
+ break;
+
+ case Object::kBlob:
+ required_num_bytes += Blob::FromObject(object)->message().size();
+ required_num_handles = 0;
+ break;
+
+ default:
+ return IPCZ_RESULT_INVALID_ARGUMENT;
+ }
+
+ const size_t data_capacity = num_bytes ? *num_bytes : 0;
+ const size_t handle_capacity = num_handles ? *num_handles : 0;
+ if (num_bytes) {
+ *num_bytes = required_num_bytes;
+ }
+ if (num_handles) {
+ *num_handles = required_num_handles;
+ }
+ const bool need_more_space = data_capacity < required_num_bytes ||
+ handle_capacity < required_num_handles;
+ if (need_more_space) {
+ return IPCZ_RESULT_RESOURCE_EXHAUSTED;
+ }
+
+ auto& header = *reinterpret_cast<SerializedObjectHeader*>(data);
+ header.type = object->type();
+ header.memory_size = 0;
+
+ switch (object->type()) {
+ case Object::kTransport:
+ handles[0] = WrappedFileDescriptor::Create(
+ MultiprocessTransport::TakeFromObject(object)->TakeDescriptor());
+ break;
+
+ case Object::kMemory: {
+ auto memory = MultiprocessMemory::TakeFromObject(object);
+ header.memory_size = checked_cast<uint32_t>(memory->size());
+ handles[0] = WrappedFileDescriptor::Create(memory->TakeDescriptor());
+ break;
+ }
+
+ case Object::kBlob: {
+ auto blob = Blob::TakeFromObject(object);
+ memcpy(&header + 1, blob->message().data(), blob->message().size());
+ break;
+ }
+
+ default:
+ return IPCZ_RESULT_INVALID_ARGUMENT;
+ }
+
+ return IPCZ_RESULT_OK;
+}
+
+IpczResult IPCZ_API Deserialize(const void* data,
+ size_t num_bytes,
+ const IpczDriverHandle* handles,
+ size_t num_handles,
+ IpczDriverHandle transport,
+ uint32_t flags,
+ const void* options,
+ IpczDriverHandle* driver_handle) {
+ const auto& header = *static_cast<const SerializedObjectHeader*>(data);
+ if (num_bytes < sizeof(header)) {
+ return IPCZ_RESULT_INVALID_ARGUMENT;
+ }
+
+ Ref<Object> object;
+ switch (header.type) {
+ case Object::kTransport:
+ if (num_handles == 1) {
+ object = MakeRefCounted<MultiprocessTransport>(
+ std::make_unique<SocketTransport>(
+ WrappedFileDescriptor::UnwrapHandle(handles[0])));
+ }
+ break;
+
+ case Object::kMemory:
+ if (num_handles == 1) {
+ object = MakeRefCounted<MultiprocessMemory>(
+ WrappedFileDescriptor::UnwrapHandle(handles[0]),
+ header.memory_size);
+ }
+ break;
+
+ case Object::kBlob:
+ object = MakeRefCounted<Blob>(
+ std::string_view(reinterpret_cast<const char*>(&header + 1),
+ num_bytes - sizeof(header)));
+ break;
+
+ default:
+ break;
+ }
+
+ if (!object) {
+ return IPCZ_RESULT_INVALID_ARGUMENT;
+ }
+
+ *driver_handle = Object::ReleaseAsHandle(std::move(object));
+ return IPCZ_RESULT_OK;
+}
+
+IpczResult IPCZ_API CreateTransports(IpczDriverHandle transport0,
+ IpczDriverHandle transport1,
+ uint32_t flags,
+ const void* options,
+ IpczDriverHandle* new_transport0,
+ IpczDriverHandle* new_transport1) {
+ auto [first_socket, second_socket] = SocketTransport::CreatePair();
+ auto first = MakeRefCounted<MultiprocessTransport>(std::move(first_socket));
+ auto second = MakeRefCounted<MultiprocessTransport>(std::move(second_socket));
+ *new_transport0 = Object::ReleaseAsHandle(std::move(first));
+ *new_transport1 = Object::ReleaseAsHandle(std::move(second));
+ return IPCZ_RESULT_OK;
+}
+
+IpczResult IPCZ_API
+ActivateTransport(IpczDriverHandle driver_transport,
+ IpczHandle transport,
+ IpczTransportActivityHandler activity_handler,
+ uint32_t flags,
+ const void* options) {
+ MultiprocessTransport::FromHandle(driver_transport)
+ ->Activate(transport, activity_handler);
+ return IPCZ_RESULT_OK;
+}
+
+IpczResult IPCZ_API DeactivateTransport(IpczDriverHandle driver_transport,
+ uint32_t flags,
+ const void* options) {
+ MultiprocessTransport::FromHandle(driver_transport)->Deactivate();
+ return IPCZ_RESULT_OK;
+}
+
+IpczResult IPCZ_API Transmit(IpczDriverHandle driver_transport,
+ const void* data,
+ size_t num_bytes,
+ const IpczDriverHandle* handles,
+ size_t num_handles,
+ uint32_t flags,
+ const void* options) {
+ return MultiprocessTransport::FromHandle(driver_transport)
+ ->Transmit(absl::MakeSpan(static_cast<const uint8_t*>(data), num_bytes),
+ absl::MakeSpan(handles, num_handles));
+}
+
+IpczResult IPCZ_API AllocateSharedMemory(size_t num_bytes,
+ uint32_t flags,
+ const void* options,
+ IpczDriverHandle* driver_memory) {
+ auto memory =
+ MakeRefCounted<MultiprocessMemory>(static_cast<size_t>(num_bytes));
+ *driver_memory = Object::ReleaseAsHandle(std::move(memory));
+ return IPCZ_RESULT_OK;
+}
+
+IpczResult IPCZ_API DuplicateSharedMemory(IpczDriverHandle driver_memory,
+ uint32_t flags,
+ const void* options,
+ IpczDriverHandle* new_driver_memory) {
+ auto memory = MultiprocessMemory::FromHandle(driver_memory)->Clone();
+ *new_driver_memory = Object::ReleaseAsHandle(std::move(memory));
+ return IPCZ_RESULT_OK;
+}
+
+IpczResult GetSharedMemoryInfo(IpczDriverHandle driver_memory,
+ uint32_t flags,
+ const void* options,
+ IpczSharedMemoryInfo* info) {
+ Object* object = Object::FromHandle(driver_memory);
+ if (!object || object->type() != Object::kMemory || !info ||
+ info->size < sizeof(IpczSharedMemoryInfo)) {
+ return IPCZ_RESULT_INVALID_ARGUMENT;
+ }
+
+ info->region_num_bytes = static_cast<MultiprocessMemory*>(object)->size();
+ return IPCZ_RESULT_OK;
+}
+
+IpczResult IPCZ_API MapSharedMemory(IpczDriverHandle driver_memory,
+ uint32_t flags,
+ const void* options,
+ void** address,
+ IpczDriverHandle* driver_mapping) {
+ auto mapping = MultiprocessMemory::FromHandle(driver_memory)->Map();
+ *address = mapping->address();
+ *driver_mapping = Object::ReleaseAsHandle(std::move(mapping));
+ return IPCZ_RESULT_OK;
+}
+
+IpczResult IPCZ_API GenerateRandomBytes(size_t num_bytes,
+ uint32_t flags,
+ const void* options,
+ void* buffer) {
+ RandomBytes(absl::MakeSpan(static_cast<uint8_t*>(buffer), num_bytes));
+ return IPCZ_RESULT_OK;
+}
+
+} // namespace
+
+const IpczDriver kMultiprocessReferenceDriver = {
+ sizeof(kMultiprocessReferenceDriver),
+ Close,
+ Serialize,
+ Deserialize,
+ CreateTransports,
+ ActivateTransport,
+ DeactivateTransport,
+ Transmit,
+ AllocateSharedMemory,
+ GetSharedMemoryInfo,
+ DuplicateSharedMemory,
+ MapSharedMemory,
+ GenerateRandomBytes,
+};
+
+IpczDriverHandle CreateMultiprocessTransport(
+ std::unique_ptr<SocketTransport> transport) {
+ return Object::ReleaseAsHandle(
+ MakeRefCounted<MultiprocessTransport>(std::move(transport)));
+}
+
+FileDescriptor TakeMultiprocessTransportDescriptor(IpczDriverHandle transport) {
+ Ref<MultiprocessTransport> released_transport =
+ MultiprocessTransport::TakeFromHandle(transport);
+ return released_transport->TakeDescriptor();
+}
+
+} // namespace ipcz::reference_drivers
diff --git a/src/reference_drivers/multiprocess_reference_driver.h b/src/reference_drivers/multiprocess_reference_driver.h
new file mode 100644
index 0000000..a0eec29
--- /dev/null
+++ b/src/reference_drivers/multiprocess_reference_driver.h
@@ -0,0 +1,34 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IPCZ_SRC_REFERENCE_DRIVERS_MULTIPROCESS_REFERENCE_DRIVER_H_
+#define IPCZ_SRC_REFERENCE_DRIVERS_MULTIPROCESS_REFERENCE_DRIVER_H_
+
+#include <cstdint>
+#include <memory>
+
+#include "ipcz/ipcz.h"
+#include "reference_drivers/file_descriptor.h"
+#include "reference_drivers/socket_transport.h"
+
+namespace ipcz::reference_drivers {
+
+// A basic reference driver which supports multiprocess operation. This is also
+// suitable for single-process usage, but unlike kSingleProcessReferenceDriver
+// all transmissions through this driver are asynchronous.
+extern const IpczDriver kMultiprocessReferenceDriver;
+
+// Creates a new multiprocess-capable driver transport from a SocketTransport
+// endpoint and returns an IpczDriverHandle to reference it.
+IpczDriverHandle CreateMultiprocessTransport(
+ std::unique_ptr<SocketTransport> transport);
+
+// Extracts the underlying file descriptor from a socket-based multiprocess
+// driver transport. `transport` is effectively consumed and invalidated by this
+// call.
+FileDescriptor TakeMultiprocessTransportDescriptor(IpczDriverHandle transport);
+
+} // namespace ipcz::reference_drivers
+
+#endif // IPCZ_SRC_REFERENCE_DRIVERS_MULTIPROCESS_REFERENCE_DRIVER_H_
diff --git a/src/reference_drivers/multiprocess_reference_driver_test.cc b/src/reference_drivers/multiprocess_reference_driver_test.cc
new file mode 100644
index 0000000..aa966ba
--- /dev/null
+++ b/src/reference_drivers/multiprocess_reference_driver_test.cc
@@ -0,0 +1,57 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "reference_drivers/multiprocess_reference_driver.h"
+
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include "ipcz/ipcz.h"
+#include "reference_drivers/file_descriptor.h"
+#include "reference_drivers/wrapped_file_descriptor.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ipcz::reference_drivers {
+namespace {
+
+TEST(MultiprocessReferenceDriverTest, SendDeactivated) {
+ const IpczDriver& driver = kMultiprocessReferenceDriver;
+ IpczDriverHandle a, b;
+ EXPECT_EQ(IPCZ_RESULT_OK,
+ driver.CreateTransports(IPCZ_INVALID_DRIVER_HANDLE,
+ IPCZ_INVALID_DRIVER_HANDLE, IPCZ_NO_FLAGS,
+ nullptr, &a, &b));
+
+ // Activate and immediately deactivate the transport.
+ auto handler = +[](IpczHandle, const void*, size_t, const IpczDriverHandle*,
+ size_t, uint32_t, const void*) { return IPCZ_RESULT_OK; };
+ EXPECT_EQ(IPCZ_RESULT_OK,
+ driver.ActivateTransport(a, IPCZ_INVALID_HANDLE, handler,
+ IPCZ_NO_FLAGS, nullptr));
+ EXPECT_EQ(IPCZ_RESULT_OK,
+ driver.DeactivateTransport(a, IPCZ_NO_FLAGS, nullptr));
+
+ int fds[2];
+ EXPECT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds));
+
+ // This driver discards outgoing transmissions once the transport has been
+ // deactivated. The attached file descriptor should not leak as a result of
+ // this Transmit(), and if it does, the test will hang below.
+ IpczDriverHandle handle =
+ WrappedFileDescriptor::Create(FileDescriptor(fds[0]));
+ EXPECT_EQ(IPCZ_RESULT_OK,
+ driver.Transmit(a, nullptr, 0, &handle, 1, IPCZ_NO_FLAGS, nullptr));
+
+ // Wait for the other side to be closed by the driver. Since this is a
+ // blocking socket, a successful zero-length read() indicates EOF.
+ FileDescriptor fd(fds[1]);
+ uint8_t byte;
+ EXPECT_EQ(0, read(fd.get(), &byte, 1));
+
+ EXPECT_EQ(IPCZ_RESULT_OK, driver.Close(a, IPCZ_NO_FLAGS, nullptr));
+ EXPECT_EQ(IPCZ_RESULT_OK, driver.Close(b, IPCZ_NO_FLAGS, nullptr));
+}
+
+} // namespace
+} // namespace ipcz::reference_drivers
diff --git a/src/reference_drivers/object.h b/src/reference_drivers/object.h
index 101e714..56e88cd 100644
--- a/src/reference_drivers/object.h
+++ b/src/reference_drivers/object.h
@@ -7,6 +7,7 @@
#include <cstdint>
+#include "build/build_config.h"
#include "ipcz/ipcz.h"
#include "util/ref_counted.h"
@@ -24,6 +25,11 @@
// driver object de/serialization via boxing and unboxing in tests. See the
// Blob definition in src/reference_drivers/blob.h.
kBlob,
+
+#if defined(OS_LINUX)
+ // A non-standard driver object type which wraps a FileDescriptor object.
+ kFileDescriptor,
+#endif
};
explicit Object(Type type);
@@ -60,14 +66,25 @@
public:
ObjectImpl() : Object(kType) {}
- static T* FromHandle(IpczDriverHandle handle) {
- Object* object = Object::FromHandle(handle);
+ static T* FromObject(Object* object) {
if (!object || object->type() != kType) {
return nullptr;
}
return static_cast<T*>(object);
}
+ static T* FromHandle(IpczDriverHandle handle) {
+ return FromObject(Object::FromHandle(handle));
+ }
+
+ static Ref<T> TakeFromObject(Object* object) {
+ return AdoptRef(FromObject(object));
+ }
+
+ static Ref<T> TakeFromHandle(IpczDriverHandle handle) {
+ return AdoptRef(FromHandle(handle));
+ }
+
protected:
~ObjectImpl() override = default;
};
diff --git a/src/reference_drivers/wrapped_file_descriptor.cc b/src/reference_drivers/wrapped_file_descriptor.cc
new file mode 100644
index 0000000..675b874
--- /dev/null
+++ b/src/reference_drivers/wrapped_file_descriptor.cc
@@ -0,0 +1,17 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "reference_drivers/wrapped_file_descriptor.h"
+
+#include "third_party/abseil-cpp/absl/base/macros.h"
+#include "util/ref_counted.h"
+
+namespace ipcz::reference_drivers {
+
+WrappedFileDescriptor::WrappedFileDescriptor(FileDescriptor fd)
+ : fd_(std::move(fd)) {}
+
+WrappedFileDescriptor::~WrappedFileDescriptor() = default;
+
+} // namespace ipcz::reference_drivers
diff --git a/src/reference_drivers/wrapped_file_descriptor.h b/src/reference_drivers/wrapped_file_descriptor.h
new file mode 100644
index 0000000..e91154b
--- /dev/null
+++ b/src/reference_drivers/wrapped_file_descriptor.h
@@ -0,0 +1,41 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IPCZ_SRC_REFERENCE_DRIVERS_WRAPPED_FILE_DESCRIPTOR_H_
+#define IPCZ_SRC_REFERENCE_DRIVERS_WRAPPED_FILE_DESCRIPTOR_H_
+
+#include "reference_drivers/file_descriptor.h"
+#include "reference_drivers/object.h"
+
+namespace ipcz::reference_drivers {
+
+// Wraps a FileDescriptor as a driver object. The Linux multiprocess reference
+// driver uses this to facilitate serialization of more complex objects into
+// these readily transmissible objects.
+class WrappedFileDescriptor
+ : public ObjectImpl<WrappedFileDescriptor, Object::kFileDescriptor> {
+ public:
+ explicit WrappedFileDescriptor(FileDescriptor fd);
+
+ const FileDescriptor& handle() const { return fd_; }
+ FileDescriptor TakeDescriptor() { return std::move(fd_); }
+
+ static IpczDriverHandle Create(FileDescriptor fd) {
+ return ReleaseAsHandle(
+ MakeRefCounted<WrappedFileDescriptor>(std::move(fd)));
+ }
+
+ static FileDescriptor UnwrapHandle(IpczDriverHandle handle) {
+ return TakeFromHandle(handle)->TakeDescriptor();
+ }
+
+ private:
+ ~WrappedFileDescriptor() override;
+
+ FileDescriptor fd_;
+};
+
+} // namespace ipcz::reference_drivers
+
+#endif // IPCZ_SRC_REFERENCE_DRIVERS_WRAPPED_FILE_DESCRIPTOR_H_
diff --git a/src/test/multinode_test.cc b/src/test/multinode_test.cc
index 88eaf71..83eb887 100644
--- a/src/test/multinode_test.cc
+++ b/src/test/multinode_test.cc
@@ -4,15 +4,24 @@
#include "test/multinode_test.h"
+#include <map>
+#include <string>
#include <thread>
+#include "build/build_config.h"
#include "ipcz/ipcz.h"
+#include "reference_drivers/file_descriptor.h"
#include "reference_drivers/single_process_reference_driver.h"
+#include "reference_drivers/socket_transport.h"
#include "third_party/abseil-cpp/absl/base/macros.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
#include "util/log.h"
+#if BUILDFLAG(IS_LINUX)
+#include "reference_drivers/multiprocess_reference_driver.h"
+#endif
+
namespace ipcz::test {
namespace {
@@ -80,6 +89,11 @@
case DriverMode::kSync:
return reference_drivers::kSingleProcessReferenceDriver;
+#if BUILDFLAG(IS_LINUX)
+ case DriverMode::kMultiprocess:
+ return reference_drivers::kMultiprocessReferenceDriver;
+#endif
+
default:
// Other modes not yet supported.
ABSL_ASSERT(false);
@@ -164,21 +178,10 @@
TestNode::TransportPair TestNode::CreateTransports() {
TransportPair transports;
- switch (driver_mode_) {
- case DriverMode::kSync: {
- const IpczResult result =
- reference_drivers::kSingleProcessReferenceDriver.CreateTransports(
- IPCZ_INVALID_DRIVER_HANDLE, IPCZ_INVALID_DRIVER_HANDLE,
- IPCZ_NO_FLAGS, nullptr, &transports.ours, &transports.theirs);
- ABSL_ASSERT(result == IPCZ_RESULT_OK);
- break;
- }
-
- default:
- LOG(FATAL) << "DriverMode not yet supported.";
- return {};
- }
-
+ const IpczResult result = GetDriver().CreateTransports(
+ IPCZ_INVALID_DRIVER_HANDLE, IPCZ_INVALID_DRIVER_HANDLE, IPCZ_NO_FLAGS,
+ nullptr, &transports.ours, &transports.theirs);
+ ABSL_ASSERT(result == IPCZ_RESULT_OK);
return transports;
}
diff --git a/src/test/multinode_test.h b/src/test/multinode_test.h
index 99508f7..037153f 100644
--- a/src/test/multinode_test.h
+++ b/src/test/multinode_test.h
@@ -11,6 +11,7 @@
#include <type_traits>
#include <vector>
+#include "build/build_config.h"
#include "ipcz/ipcz.h"
#include "test/test_base.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -26,8 +27,8 @@
template <typename TestNodeType>
class MultinodeTest;
-// Selects which driver test nodes will use. Interconnecting nodes must always
-// use the same driver.
+// Selects which driver will be used by test nodes. Interconnecting nodes must
+// always use the same driver.
//
// Multinode tests are parameterized over these modes to provide coverage of
// various interesting constraints encountered in production. Some platforms
@@ -39,27 +40,46 @@
// all driver modes. The synchronous version is generally easier to debug in
// such cases.
enum class DriverMode {
- // Use the fully synchronous, single-process reference driver. This driver
- // does not create any background threads and all ipcz operations will
- // complete synchronously from end-to-end.
+ // Use the synchronous, single-process reference driver. This driver does not
+ // create any background threads and all ipcz operations (e.g. message
+ // delivery, portal transfer, proxy elimination, etc) complete synchronously
+ // from end-to-end. Each test node runs its test body on a dedicated thread
+ // within the test process.
kSync,
- // Use the async multiprocess driver as-is. All nodes can allocate their own
- // shared memory directly through the driver.
+ // Use the asynchronous single-process reference driver. Transport messages
+ // are received asynchronously, similar to how most production drivers are
+ // likely to operate in practice. Such asynchrony gives rise to
+ // non-determinism throughout ipcz proper and provides good coverage of
+ // potential race conditions.
+ //
+ // As with the kSync driver, each test node runs its test body on a dedicated
+ // thread within the test process.
kAsync,
- // Use the async multiprocess driver, and force non-broker nodes to delegate
- // shared memory allocation to their broker.
+ // Use the same driver as kAsync, but non-broker nodes are forced to delegate
+ // shared memory allocation to their broker. This simulates the production
+ // constraints of some sandbox environments and exercises additional
+ // asynchrony in ipcz proper.
kAsyncDelegatedAlloc,
- // Use the async multiprocess driver, and force non-broker-to-non-broker
- // transmission of driver objects to be relayed through a broker. All nodes
- // can allocate their own shared memory directly through the driver.
+ // Use the same driver as kAsync, but driver objects cannot be transmitted
+ // directly between non-brokers and must instead be relayed by a broker. This
+ // simulates the production constraints of some sandbox environments and
+ // exercises additional asynchrony in ipcz proper.
kAsyncObjectBrokering,
- // Use the async multiprocess driver, forcing shared memory AND driver
- // object relay both to be delegated to a broker.
+ // Use the same driver as kAsync, imposing the additional constraints of both
+ // kAsyncDelegatedAlloc and kAsyncObjectBrokering as described above.
kAsyncObjectBrokeringAndDelegatedAlloc,
+
+#if BUILDFLAG(IS_LINUX)
+ // Use a multiprocess-capable driver (Linux only), with each test node running
+ // in its own isolated child process.
+ //
+ // TODO: Actually run nodes in child processes.
+ kMultiprocess,
+#endif
};
namespace internal {
@@ -256,9 +276,17 @@
}; \
void node_name::NodeBody()
+#if BUILDFLAG(IS_LINUX)
+#define IPCZ_EXTRA_DRIVER_MODES , ipcz::test::DriverMode::kMultiprocess
+#else
+#define IPCZ_EXTRA_DRIVER_MODES
+#endif
+
// TODO: Add other DriverMode enumerators here as support is landed.
#define INSTANTIATE_MULTINODE_TEST_SUITE_P(suite) \
- INSTANTIATE_TEST_SUITE_P(, suite, \
- ::testing::Values(ipcz::test::DriverMode::kSync))
+ INSTANTIATE_TEST_SUITE_P( \
+ , suite, \
+ ::testing::Values( \
+ ipcz::test::DriverMode::kSync IPCZ_EXTRA_DRIVER_MODES))
#endif // IPCZ_SRC_TEST_MULTINODE_TEST_H_
diff --git a/src/test/test_transport_listener.cc b/src/test/test_transport_listener.cc
index 16ca0ba..f7eb836 100644
--- a/src/test/test_transport_listener.cc
+++ b/src/test/test_transport_listener.cc
@@ -8,6 +8,7 @@
#include "ipcz/driver_object.h"
#include "ipcz/driver_transport.h"
+#include "ipcz/message.h"
#include "ipcz/node.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/base/macros.h"
@@ -57,7 +58,13 @@
void TestTransportListener::OnError(ErrorHandler handler) {
ABSL_ASSERT(!error_handler_);
error_handler_ = std::move(handler);
- ActivateTransportIfNecessary();
+
+ // Since the caller only cares about handling errors, ensure all valid
+ // messages are cleanly discarded. This also activates the transport.
+ OnRawMessage([this](const DriverTransport::RawMessage& message) {
+ Message m;
+ return m.DeserializeUnknownType(message, *transport_);
+ });
}
void TestTransportListener::ActivateTransportIfNecessary() {