blob: c343789b32884c8937609aa6926b463e7cc2f7d0 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <functional>
#include <memory>
#include <utility>
#include <vector>
#include "ipcz/api_object.h"
#include "ipcz/driver_memory.h"
#include "ipcz/ipcz.h"
#include "ipcz/link_side.h"
#include "ipcz/node_messages.h"
#include "ipcz/node_name.h"
#include "ipcz/node_type.h"
#include "ipcz/operation_context.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_map.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_set.h"
#include "third_party/abseil-cpp/absl/synchronization/mutex.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/abseil-cpp/absl/types/span.h"
namespace ipcz {
class NodeLink;
class NodeLinkMemory;
// A Node controls creation and interconnection of a collection of routers which
// can establish links to and from other routers in other nodes. Every node is
// assigned a globally unique name by a trusted broker node, and nodes may be
// introduced to each other exclusively through such brokers.
class Node : public APIObjectImpl<Node, APIObject::kNode> {
using Type = NodeType;
// State regarding a connection to a single remote node.
struct Connection {
// The NodeLink used to communicate with the remote node.
Ref<NodeLink> link;
// The NodeLink used to communicate with the broker of the remote node's
// network. If the remote node belongs to the same network as the local
// node, then this is the same link the local node's `broker_link_`. If the
// local node *is* the broker for the remote node on `link`, then this link
// is null.
Ref<NodeLink> broker;
// Constructs a new node of the given `type`, using `driver` to support IPC.
// Note that `driver` must outlive the Node. `driver_node` is an arbitrary
// driver-specific handle that may be used for additional context when
// interfacing with the driver regarding this node.
Node(Type type,
const IpczDriver& driver,
IpczDriverHandle driver_node,
const IpczCreateNodeOptions* options = nullptr);
Type type() const { return type_; }
const IpczDriver& driver() const { return driver_; }
IpczDriverHandle driver_node() const { return driver_node_; }
const IpczCreateNodeOptions& options() const { return options_; }
// APIObject:
IpczResult Close() override;
// Connects to another node using `driver_transport` for I/O to and from the
// other node. `initial_portals` is a collection of new portals who may
// immediately begin to route parcels over a link to the new node, assuming
// the link is established successfully.
IpczResult ConnectNode(IpczDriverHandle driver_transport,
IpczConnectNodeFlags flags,
absl::Span<IpczHandle> initial_portals);
// Retrieves the name assigned to this node, if any.
NodeName GetAssignedName();
// Gets a reference to the node's broker link, if it has one.
Ref<NodeLink> GetBrokerLink();
// Sets this node's assigned name as given by a broker. NodeConnector is
// responsible for calling on non-broker Nodes this after receiving the
// expected handshake from a broker. Must not be called on broker nodes, as
// they assign their own name at construction time.
void SetAssignedName(const NodeName& name);
// Registers a new connection for the given `remote_node_name`.
bool AddConnection(const NodeName& remote_node_name, Connection connection);
// Returns a copy of the Connection to the remote node named by `name`, or
// null if this node has no connection to that node.
absl::optional<Node::Connection> GetConnection(const NodeName& name);
// Returns a reference to the NodeLink used by this Node to communicate with
// the remote node identified by `name`; or null if this node has no NodeLink
// connected to that node. This is shorthand for GetConnection() in the common
// case where the caller only wants the underlying NodeLink.
Ref<NodeLink> GetLink(const NodeName& name);
// Generates a new random NodeName using this node's driver as a source of
// randomness.
NodeName GenerateRandomName() const;
// Sets a NodeLink to use for asynchronous shared memory allocation requests.
// This is configured when the ConnectNode() API is called with
// IPCZ_CONNECT_NODE_TO_ALLOCATION_DELEGATE. Typically this is combined with
// IPCZ_CONNECT_NODE_TO_BROKER when connecting from a sandboxed process which
// cannot allocate its own shared memory regions.
void SetAllocationDelegate(Ref<NodeLink> link);
// Requests allocation of a new shared memory object of the given size.
// `callback` is invoked with the new object when allocation is complete.
// This operation is asynchronous if allocation is delegated to another node,
// but if this node can allocate directly through the driver, `callback` is
// invoked with the result before this method returns.
using AllocateSharedMemoryCallback = std::function<void(DriverMemory)>;
void AllocateSharedMemory(size_t size, AllocateSharedMemoryCallback callback);
// Asynchronously attempts to establish a new NodeLink directly to the named
// node, invoking `callback` when complete. On success, this node will retain
// a new NodeLink to the named node, and `callback` will be invoked with a
// reference to that link. Otherwise `callback` will be invoked with a null
// reference.
// If the calling node already has a link to the named node, `callback` may
// be invoked synchronously with a link to that node before this method
// returns.
using EstablishLinkCallback = std::function<void(NodeLink*)>;
void EstablishLink(const NodeName& name, EstablishLinkCallback callback);
// Handles an incoming introduction request. Must only be called on a broker
// node. If this broker has a NodeLink to the node named by `for_node`, it
// will introduce that node and the remote node on `from_node_link`.
void HandleIntroductionRequest(NodeLink& from_node_link,
const NodeName& for_node);
// Accepts an introduction received from the broker. `transport` and `memory`
// can be used to establish a new NodeLink to the remote node, whose name is
// `name`. The NodeLink must assume a role as the given `side` of the link.
void AcceptIntroduction(NodeLink& from_node_link,
const NodeName& name,
LinkSide side,
Node::Type remote_node_type,
uint32_t remote_protocol_version,
Ref<DriverTransport> transport,
Ref<NodeLinkMemory> memory);
// Handles a rejected introduction for the node named `name` from the
// identified broker. This is called on a node that previously requested an
// introduction if the broker is unable or unwilling to satisfy the request.
void NotifyIntroductionFailed(NodeLink& from_broker, const NodeName& name);
// Relays a message to its destination on behalf of `from_node`.
bool RelayMessage(const NodeName& from_node, msg::RelayMessage& relay);
// Attempts to dispatch a relayed message from the broker as if it came from
// the relay source directly.
bool AcceptRelayedMessage(msg::AcceptRelayedMessage& accept);
// Drops this node's connection to the named node, if one exists.
void DropConnection(const OperationContext& context, const NodeName& name);
// Asynchronously waits for this Node to acquire a broker link and then
// invokes `callback` with it. If this node already has a broker link then the
// callback is invoked immediately, before this method returns.
using BrokerLinkCallback = std::function<void(Ref<NodeLink>)>;
void WaitForBrokerLinkAsync(BrokerLinkCallback callback);
// Processes a request for an indirect cross-network node introduction. The
// request was sent by `from_node_link` (a link to another broker) and is
// asking us to introduce `our_node` within our network to `their_node` in the
// the requestor's network.
bool HandleIndirectIntroductionRequest(NodeLink& from_node_link,
const NodeName& our_node,
const NodeName& their_node);
class PendingIntroduction;
~Node() override;
// Deactivates all NodeLinks and their underlying driver transports in
// preparation for this node's imminent destruction.
void ShutDown();
// Resolves all pending introduction requests with a null link, implying
// failure.
void CancelAllIntroductions();
// Creates a new transport and link memory and sends introduction messages to
// introduce the remote node on `first` to the remote node on `second`.
void IntroduceRemoteNodes(NodeLink& first, NodeLink& second);
const Type type_;
const IpczDriver& driver_;
const IpczDriverHandle driver_node_;
const IpczCreateNodeOptions options_;
absl::Mutex mutex_;
// The name assigned to this node by the first broker it connected to, or
// self-assigned if this is a broker node. Once assigned, this name remains
// constant through the lifetime of the node.
NodeName assigned_name_ ABSL_GUARDED_BY(mutex_);
// A link to the first broker this node connected to. If this link is broken,
// the node will lose all its other links too. This is always null on broker
// nodes, though brokers may keep track of links to other brokers within
// `other_brokers_`.
Ref<NodeLink> broker_link_ ABSL_GUARDED_BY(mutex_);
// A link over which all internal shared memory allocation is delegated. If
// null, this Node will always attempt to allocate shared memory directly
// through its ipcz driver.
Ref<NodeLink> allocation_delegate_link_ ABSL_GUARDED_BY(mutex_);
// Lookup table of broker-assigned node names and links to those nodes. All of
// these links and their associated names are received by the `broker_link_`
// if this is a non-broker node. If this is a broker node, these links are
// either assigned by this node itself, or received from other brokers in the
// system.
using ConnectionMap = absl::flat_hash_map<NodeName, Connection>;
ConnectionMap connections_ ABSL_GUARDED_BY(mutex_);
// A map of other nodes to which this node is waiting for an introduction,
// either from its own broker or (if we are a broker) all the other known
// brokers we're connected to.
using PendingIntroductionMap =
absl::flat_hash_map<NodeName, std::unique_ptr<PendingIntroduction>>;
PendingIntroductionMap pending_introductions_ ABSL_GUARDED_BY(mutex_);
// Nodes may race to request introductions to each other from the same broker.
// This can lead to redundant introductions being sent which the requesting
// nodes should be able to ignore. However, the following could occur on a
// broker which is processing a request from node A on Thread 1 while also
// processing a request from node B on thread 2:
// Thread 1 Thread 2 Time
// --- --- |
// A requests intro to B B requests intro to A v
// Send B intro X to A
// Send A intro Y to B
// Send A intro X to B
// Send B intro Y to A
// Each unique intro shares either end of a transport with its recipients,
// so both A and B must accept the same introduction (either X or Y). In this
// scenario however, A will first receive and accept intro X, and will ignore
// intro Y as redundant. But B will receive intro Y first and ignore intro X
// as redundant. This is bad.
// The set of `in_progress_introductions_` allows this (broker) node to guard
// against such interleaved introductions. Immediately before sending an intro
// to both recipients, a key identifying them is placed into the set. This key
// is removed immediately after both introductions are sent. If another thread
// is asked to introduce the same two nodes while the key is still present, it
// will ignore the request and send nothing.
using IntroductionKey = std::pair<NodeName, NodeName>;
absl::flat_hash_set<IntroductionKey> in_progress_introductions_
// Set of callbacks waiting to be invoked as soon as this Node acquires a
// broker link.
std::vector<BrokerLinkCallback> broker_link_callbacks_
// Mapping of links to other known brokers in the system. This is the subset
// of `links_` which corresponds to remote broker nodes NOT in this node's own
// network. This map can only be non-empty on broker nodes.
absl::flat_hash_map<NodeName, Ref<NodeLink>> other_brokers_
} // namespace ipcz