| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/at_exit.h" |
| #include "base/check.h" |
| #include "base/command_line.h" |
| #include "base/files/scoped_file.h" |
| #include "base/logging.h" |
| #include "base/message_loop/message_pump_type.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "base/task/single_thread_task_executor.h" |
| #include "mojo/core/embedder/embedder.h" |
| #include "mojo/core/embedder/scoped_ipc_support.h" |
| #include "mojo/core/ipcz_api.h" |
| #include "mojo/core/ipcz_driver/transport.h" |
| #include "mojo/core/scoped_ipcz_handle.h" |
| #include "mojo/proxy/node_proxy.h" |
| #include "mojo/proxy/portal_proxy.h" |
| #include "mojo/proxy/switches.h" |
| #include "mojo/public/cpp/platform/platform_channel_endpoint.h" |
| #include "mojo/public/cpp/platform/platform_handle.h" |
| #include "mojo/public/cpp/system/invitation.h" |
| #include "third_party/ipcz/include/ipcz/ipcz.h" |
| |
| namespace mojo_proxy { |
| |
| void RunProxy(int argc, char** argv) { |
| CHECK(base::CommandLine::Init(argc, argv)); |
| base::AtExitManager at_exit; |
| logging::InitLogging({}); |
| logging::SetLogItems(true, true, true, true); |
| |
| base::SingleThreadTaskExecutor io_task_executor(base::MessagePumpType::IO); |
| |
| // We initialize Mojo with ipcz disabled, since pre-ipcz Mojo Core only works |
| // as a process-wide singleton. This means that all Mojo C APIs in this |
| // process are wired to the old Mojo implementation and are therefore usable |
| // to interface (exclusively) with the proxy's legacy client. |
| // |
| // We always operate as a broker on the legacy side based on the assumption |
| // that all legacy clients are non-brokers. We're the only node the legacy |
| // client communicates with. |
| mojo::core::Configuration mojo_config; |
| mojo_config.is_broker_process = true; |
| mojo_config.disable_ipcz = true; |
| mojo::core::Init(mojo_config); |
| at_exit.RegisterTask(base::BindOnce(&mojo::core::ShutDown)); |
| auto ipc_support = std::make_unique<mojo::core::ScopedIPCSupport>( |
| io_task_executor.task_runner(), |
| mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN); |
| |
| // Also initialize the global MojoIpcz node, but don't re-initialize Mojo |
| // Core. Mojo C APIs therefore still point to the old Mojo implementation, and |
| // any interaction with the MojoIpcz side of the proxy must be done direct |
| // calls into either ipcz or the MojoIpcz driver. |
| // |
| // On the ipcz side we're a non-broker, based on the assumption that either |
| // our ipcz client is a broker or (if --inherit-ipcz-broker is given) we can |
| // inherit a broker from them. |
| mojo::core::IpczNodeOptions ipcz_options{ |
| .is_broker = false, |
| .use_local_shared_memory_allocation = true, |
| }; |
| CHECK(mojo::core::InitializeIpczNodeForProcess(ipcz_options)); |
| const IpczHandle ipcz_node = mojo::core::GetIpczNode(); |
| |
| int fd; |
| const auto& command_line = *base::CommandLine::ForCurrentProcess(); |
| CHECK(base::StringToInt( |
| command_line.GetSwitchValueASCII(switches::kLegacyClientFd), &fd)); |
| mojo::PlatformChannelEndpoint legacy_endpoint{ |
| mojo::PlatformHandle{base::ScopedFD{fd}}}; |
| CHECK(base::StringToInt( |
| command_line.GetSwitchValueASCII(switches::kHostIpczTransportFd), &fd)); |
| mojo::PlatformChannelEndpoint ipcz_endpoint{ |
| mojo::PlatformHandle{base::ScopedFD{fd}}}; |
| |
| // Some Mojo clients use free-form strings for attachment names, and some use |
| // 64-bit integral, zero-based values. In general only the latter cases attach |
| // multiple pipes to a single invitation. The chosen scheme influences how |
| // MojoIpcz (and therefore how this proxy) maps attachment names from Mojo |
| // APIs to an index into the portal array filled im by ipcz ConnectNode(). |
| std::vector<std::string> attachment_names; |
| if (command_line.HasSwitch(switches::kAttachmentName)) { |
| attachment_names.push_back( |
| command_line.GetSwitchValueASCII(switches::kAttachmentName)); |
| } else if (command_line.HasSwitch(switches::kNumAttachments)) { |
| uint64_t num_unnamed_attachments; |
| CHECK(base::StringToUint64( |
| command_line.GetSwitchValueASCII(switches::kNumAttachments), |
| &num_unnamed_attachments)); |
| for (uint64_t i = 0; i < num_unnamed_attachments; ++i) { |
| attachment_names.emplace_back(reinterpret_cast<const char*>(&i), |
| sizeof(i)); |
| } |
| } |
| |
| // Create an appropriate ipcz transport to connect back to the host. |
| using Transport = mojo::core::ipcz_driver::Transport; |
| const bool inherit_ipcz_broker = |
| command_line.HasSwitch(switches::kInheritIpczBroker); |
| const Transport::EndpointType ipcz_client_type = |
| inherit_ipcz_broker ? Transport::kNonBroker : Transport::kBroker; |
| auto ipcz_transport = Transport::Create( |
| {.source = Transport::kNonBroker, .destination = ipcz_client_type}, |
| std::move(ipcz_endpoint), base::Process{}); |
| |
| // Portal 0 is reserved (see below). The portals corresponding to invitation |
| // attachments span indices [1, N]. |
| const IpczAPI& ipcz = mojo::core::GetIpczAPI(); |
| std::vector<IpczHandle> initial_portals(attachment_names.size() + 1); |
| const IpczConnectNodeFlags connect_flags = |
| inherit_ipcz_broker ? IPCZ_CONNECT_NODE_INHERIT_BROKER |
| : IPCZ_CONNECT_NODE_TO_BROKER; |
| const IpczResult connect_result = ipcz.ConnectNode( |
| ipcz_node, Transport::ReleaseAsHandle(std::move(ipcz_transport)), |
| initial_portals.size(), connect_flags, nullptr, initial_portals.data()); |
| CHECK_EQ(IPCZ_RESULT_OK, connect_result); |
| |
| // Portal 0 is bound on the other end to an internal shared memory allocation |
| // service by MojoIpcz. We don't need it. |
| ipcz.Close(initial_portals[0], IPCZ_NO_FLAGS, nullptr); |
| |
| // Seed the server with proxies between each of the attached pipes on the |
| // legacy invitation and their corresponding initial portals from the host |
| // connection. |
| base::RunLoop run_loop; |
| NodeProxy proxy(ipcz, /*dead_callback=*/run_loop.QuitClosure()); |
| mojo::OutgoingInvitation invitation; |
| for (size_t i = 0; i < attachment_names.size(); ++i) { |
| proxy.AddPortalProxy(mojo::core::ScopedIpczHandle(initial_portals[i + 1]), |
| invitation.AttachMessagePipe(attachment_names[i])); |
| } |
| |
| // After sending the legacy invitation, we wait until all proxies are dead. |
| mojo::OutgoingInvitation::Send(std::move(invitation), |
| base::kNullProcessHandle, |
| std::move(legacy_endpoint)); |
| run_loop.Run(); |
| |
| mojo::core::DestroyIpczNodeForProcess(); |
| ipc_support.reset(); |
| } |
| |
| } // namespace mojo_proxy |
| |
| int main(int argc, char** argv) { |
| mojo_proxy::RunProxy(argc, argv); |
| return 0; |
| } |