ipcz: Implement proxy bypass, part 1 of 2
This change introduces some basic utilities, messages, plumbing, and
documentation to support incremental proxy bypass along routes. There
are no functional changes here.
The TwoMutexLock helper will support two overlapping Router locks, which
are necessary for some route transformations when peers are local to the
same node. Three- and FourMutexLock usage will be limited to rare cases
where such transformations span a bridge link (i.e. MergePortals cases).
The new NodeLink messages will be used to coordinate proxy bypass among
the proxy itself and its inward and outward peers. The only relevant
logic introduced here is serialized transmission through NodeLink and
RemoteRouterLink, and basic validation and routing from NodeLink to
specific Routers.
Bug: 1299283
Change-Id: I7b3ee5c5f25707c0788b16d144616004ef1da651
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3773431
Commit-Queue: Ken Rockot <rockot@google.com>
Reviewed-by: Robert Sesek <rsesek@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1029488}
NOKEYCHECK=True
GitOrigin-RevId: 11122606968a21eb5feeb832ea34dc4bfac1177c
diff --git a/src/BUILD.gn b/src/BUILD.gn
index f434cab..8756a8d 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -194,6 +194,7 @@
public = [
"util/log.h",
+ "util/multi_mutex_lock.h",
"util/ref_counted.h",
"util/stack_trace.h",
"util/strong_alias.h",
diff --git a/src/ipcz/local_router_link.cc b/src/ipcz/local_router_link.cc
index 43bb12c..8e8ee3d 100644
--- a/src/ipcz/local_router_link.cc
+++ b/src/ipcz/local_router_link.cc
@@ -151,6 +151,37 @@
bypass_request_source;
}
+void LocalRouterLink::BypassPeer(const NodeName& bypass_target_node,
+ SublinkId bypass_target_sublink) {
+ // Not implemented, and never called on local links.
+ ABSL_ASSERT(false);
+}
+
+void LocalRouterLink::StopProxying(SequenceNumber inbound_sequence_length,
+ SequenceNumber outbound_sequence_length) {
+ // Not implemented, and never called on local links.
+ ABSL_ASSERT(false);
+}
+
+void LocalRouterLink::ProxyWillStop(SequenceNumber inbound_sequence_length) {
+ // Not implemented, and never called on local links.
+ ABSL_ASSERT(false);
+}
+
+void LocalRouterLink::BypassPeerWithLink(
+ SublinkId new_sublink,
+ FragmentRef<RouterLinkState> new_link_state,
+ SequenceNumber inbound_sequence_length) {
+ // Not implemented, and never called on local links.
+ ABSL_ASSERT(false);
+}
+
+void LocalRouterLink::StopProxyingToLocalPeer(
+ SequenceNumber outbound_sequence_length) {
+ // Not implemented, and never called on local links.
+ ABSL_ASSERT(false);
+}
+
void LocalRouterLink::Deactivate() {
state_->Deactivate(side_);
}
diff --git a/src/ipcz/local_router_link.h b/src/ipcz/local_router_link.h
index ceb2b3f..e17d5a1 100644
--- a/src/ipcz/local_router_link.h
+++ b/src/ipcz/local_router_link.h
@@ -39,6 +39,16 @@
void Unlock() override;
bool FlushOtherSideIfWaiting() override;
bool CanNodeRequestBypass(const NodeName& bypass_request_source) override;
+ void BypassPeer(const NodeName& bypass_target_node,
+ SublinkId bypass_target_sublink) override;
+ void StopProxying(SequenceNumber inbound_sequence_length,
+ SequenceNumber outbound_sequence_length) override;
+ void ProxyWillStop(SequenceNumber inbound_sequence_length) override;
+ void BypassPeerWithLink(SublinkId new_sublink,
+ FragmentRef<RouterLinkState> new_link_state,
+ SequenceNumber inbound_sequence_length) override;
+ void StopProxyingToLocalPeer(
+ SequenceNumber outbound_sequence_length) override;
void Deactivate() override;
std::string Describe() const override;
diff --git a/src/ipcz/node_link.cc b/src/ipcz/node_link.cc
index be8860f..68d7ba4 100644
--- a/src/ipcz/node_link.cc
+++ b/src/ipcz/node_link.cc
@@ -11,7 +11,9 @@
#include <utility>
#include "ipcz/box.h"
+#include "ipcz/fragment_ref.h"
#include "ipcz/ipcz.h"
+#include "ipcz/link_side.h"
#include "ipcz/link_type.h"
#include "ipcz/message.h"
#include "ipcz/node.h"
@@ -20,12 +22,29 @@
#include "ipcz/portal.h"
#include "ipcz/remote_router_link.h"
#include "ipcz/router.h"
+#include "ipcz/router_link.h"
+#include "ipcz/router_link_state.h"
+#include "ipcz/sublink_id.h"
#include "third_party/abseil-cpp/absl/base/macros.h"
#include "util/log.h"
#include "util/ref_counted.h"
namespace ipcz {
+namespace {
+
+template <typename T>
+FragmentRef<T> MaybeAdoptFragmentRef(NodeLinkMemory& memory,
+ const FragmentDescriptor& descriptor) {
+ if (descriptor.is_null() || descriptor.size() < sizeof(T)) {
+ return {};
+ }
+
+ return memory.AdoptFragmentRef<T>(memory.GetFragment(descriptor));
+}
+
+} // namespace
+
// static
Ref<NodeLink> NodeLink::Create(Ref<Node> node,
LinkSide link_side,
@@ -158,6 +177,22 @@
Transmit(reject);
}
+void NodeLink::AcceptBypassLink(
+ const NodeName& current_peer_node,
+ SublinkId current_peer_sublink,
+ SequenceNumber inbound_sequence_length_from_bypassed_link,
+ SublinkId new_sublink,
+ FragmentRef<RouterLinkState> link_state) {
+ msg::AcceptBypassLink accept;
+ accept.params().current_peer_node = current_peer_node;
+ accept.params().current_peer_sublink = current_peer_sublink;
+ accept.params().inbound_sequence_length_from_bypassed_link =
+ inbound_sequence_length_from_bypassed_link;
+ accept.params().new_sublink = new_sublink;
+ accept.params().new_link_state_fragment = link_state.release().descriptor();
+ Transmit(accept);
+}
+
void NodeLink::Deactivate() {
{
absl::MutexLock lock(&mutex_);
@@ -365,17 +400,96 @@
sublink->router_link->GetType());
}
-bool NodeLink::OnSetRouterLinkState(msg::SetRouterLinkState& set) {
- if (set.params().descriptor.is_null()) {
+bool NodeLink::OnBypassPeer(msg::BypassPeer& bypass) {
+ absl::optional<Sublink> sublink = GetSublink(bypass.params().sublink);
+ if (!sublink) {
+ return true;
+ }
+
+ // NOTE: This request is authenticated by the receiving Router, within
+ // BypassPeer().
+ return sublink->receiver->BypassPeer(*sublink->router_link,
+ bypass.params().bypass_target_node,
+ bypass.params().bypass_target_sublink);
+}
+
+bool NodeLink::OnAcceptBypassLink(msg::AcceptBypassLink& accept) {
+ Ref<NodeLink> node_link_to_peer =
+ node_->GetLink(accept.params().current_peer_node);
+ if (!node_link_to_peer) {
+ // If the link to the peer has been severed for whatever reason, the
+ // relevant route will be torn down anyway. It's safe to ignore this
+ // request in that case.
+ return true;
+ }
+
+ const Ref<Router> receiver =
+ node_link_to_peer->GetRouter(accept.params().current_peer_sublink);
+ if (!receiver) {
+ // Similar to above, if the targeted Router cannot be resolved from the
+ // given sublink, this implies that the route has already been at least
+ // partially torn down. It's safe to ignore this request.
+ return true;
+ }
+
+ auto link_state = MaybeAdoptFragmentRef<RouterLinkState>(
+ memory(), accept.params().new_link_state_fragment);
+ if (link_state.is_null()) {
+ // Bypass links must always come with a valid fragment for their
+ // RouterLinkState. If one has not been provided, that's a validation
+ // failure.
return false;
}
- if (absl::optional<Sublink> sublink = GetSublink(set.params().sublink)) {
- auto fragment = memory().GetFragment(set.params().descriptor);
- sublink->router_link->SetLinkState(
- memory().AdoptFragmentRef<RouterLinkState>(fragment));
+ return receiver->AcceptBypassLink(
+ WrapRefCounted(this), accept.params().new_sublink, std::move(link_state),
+ accept.params().inbound_sequence_length_from_bypassed_link);
+}
+
+bool NodeLink::OnStopProxying(msg::StopProxying& stop) {
+ Ref<Router> router = GetRouter(stop.params().sublink);
+ if (!router) {
+ return true;
}
- return true;
+
+ return router->StopProxying(stop.params().inbound_sequence_length,
+ stop.params().outbound_sequence_length);
+}
+
+bool NodeLink::OnProxyWillStop(msg::ProxyWillStop& will_stop) {
+ Ref<Router> router = GetRouter(will_stop.params().sublink);
+ if (!router) {
+ return true;
+ }
+
+ return router->NotifyProxyWillStop(
+ will_stop.params().inbound_sequence_length);
+}
+
+bool NodeLink::OnBypassPeerWithLink(msg::BypassPeerWithLink& bypass) {
+ Ref<Router> router = GetRouter(bypass.params().sublink);
+ if (!router) {
+ return true;
+ }
+
+ auto link_state = MaybeAdoptFragmentRef<RouterLinkState>(
+ memory(), bypass.params().new_link_state_fragment);
+ if (link_state.is_null()) {
+ return false;
+ }
+ return router->BypassPeerWithLink(*this, bypass.params().new_sublink,
+ std::move(link_state),
+ bypass.params().inbound_sequence_length);
+}
+
+bool NodeLink::OnStopProxyingToLocalPeer(msg::StopProxyingToLocalPeer& stop) {
+ Ref<Router> router = GetRouter(stop.params().sublink);
+ if (!router) {
+ return true;
+ }
+
+ return router->StopProxyingToLocalPeer(
+ stop.params().outbound_sequence_length);
}
bool NodeLink::OnFlushRouter(msg::FlushRouter& flush) {
diff --git a/src/ipcz/node_link.h b/src/ipcz/node_link.h
index a463fde..05ca96b 100644
--- a/src/ipcz/node_link.h
+++ b/src/ipcz/node_link.h
@@ -12,6 +12,7 @@
#include "ipcz/driver_memory.h"
#include "ipcz/driver_transport.h"
+#include "ipcz/fragment_ref.h"
#include "ipcz/link_side.h"
#include "ipcz/link_type.h"
#include "ipcz/node.h"
@@ -124,6 +125,22 @@
// node identified by `name`.
void RejectIntroduction(const NodeName& name);
+ // Sends a request to the remote node to establish a new RouterLink over this
+ // this NodeLink, to replace an existing RouterLink between the remote node
+ // and `current_peer_node`. `current_peer_sublink` identifies the specific
+ // RouterLink between them which is to be replaced.
+ //
+ // `inbound_sequence_length_from_bypassed_link` is the final length of the
+ // parcel sequence to be routed over the link which is being bypassed.
+ // `new_sublink` and (optionally null) `new_link_state` can be used to
+ // establish the new link over the NodeLink transmitting this message.
+ void AcceptBypassLink(
+ const NodeName& current_peer_node,
+ SublinkId current_peer_sublink,
+ SequenceNumber inbound_sequence_length_from_bypassed_link,
+ SublinkId new_sublink,
+ FragmentRef<RouterLinkState> new_link_state);
+
// Permanently deactivates this NodeLink. Once this call returns the NodeLink
// will no longer receive transport messages. It may still be used to transmit
// outgoing messages, but it cannot be reactivated. Transmissions over a
@@ -157,7 +174,12 @@
bool OnAcceptParcel(msg::AcceptParcel& accept) override;
bool OnRouteClosed(msg::RouteClosed& route_closed) override;
bool OnRouteDisconnected(msg::RouteDisconnected& route_disconnected) override;
- bool OnSetRouterLinkState(msg::SetRouterLinkState& set) override;
+ bool OnBypassPeer(msg::BypassPeer& bypass) override;
+ bool OnAcceptBypassLink(msg::AcceptBypassLink& accept) override;
+ bool OnStopProxying(msg::StopProxying& stop) override;
+ bool OnProxyWillStop(msg::ProxyWillStop& will_stop) override;
+ bool OnBypassPeerWithLink(msg::BypassPeerWithLink& bypass) override;
+ bool OnStopProxyingToLocalPeer(msg::StopProxyingToLocalPeer& stop) override;
bool OnFlushRouter(msg::FlushRouter& flush) override;
void OnTransportError() override;
diff --git a/src/ipcz/node_messages_generator.h b/src/ipcz/node_messages_generator.h
index 7151143..c0a3b97 100644
--- a/src/ipcz/node_messages_generator.h
+++ b/src/ipcz/node_messages_generator.h
@@ -169,12 +169,179 @@
IPCZ_MSG_PARAM(SublinkId, sublink)
IPCZ_MSG_END()
-// Notifies a node that the Router it has bound to `sublink` (on the
-// transmitting NodeLink) now has an allocated RouterLinkState in the fragment
-// identified by `descriptor`.
-IPCZ_MSG_BEGIN(SetRouterLinkState, IPCZ_MSG_ID(24), IPCZ_MSG_VERSION(0))
+// Informs a router that its outward peer can be bypassed. Given routers X and Y
+// on the central link, and a router Z as Y's inward peer:
+//
+// X ==== (central) ==== Y ======== Z
+//
+// Once Y successfully locks the central link, Y may send this message to Z
+// with sufficient information for Z to establish a direct link to X. Z
+// accomplishes this via an AcceptBypassLink message to X's node.
+//
+// Note that this message is only used when X and Y belong to different nodes.
+// If X and Y belong to the same node, then Y sends Z a BypassPeerWithLink
+// message instead.
+IPCZ_MSG_BEGIN(BypassPeer, IPCZ_MSG_ID(30), IPCZ_MSG_VERSION(0))
+ // Identifies the router to receive this message.
IPCZ_MSG_PARAM(SublinkId, sublink)
- IPCZ_MSG_PARAM(FragmentDescriptor, descriptor)
+
+ // Padding for NodeName alignment. Reserved for future use and must be zero.
+ IPCZ_MSG_PARAM(uint64_t, reserved0)
+
+ // The name of the node where router X lives. That is the outward peer of
+ // the recipient's outward peer.
+ IPCZ_MSG_PARAM(NodeName, bypass_target_node)
+
+ // The sublink used to route between the recipient's outward peer and that
+ // router's own outward peer; i.e., the link between X and Y.
+ IPCZ_MSG_PARAM(SublinkId, bypass_target_sublink)
+IPCZ_MSG_END()
+
+// Provides a router with a new outward link to replace its existing outward
+// link to some other node. Given routers X and Y on the central link, and a
+// router Z as Y's inward peer:
+//
+// X ==== (central) ==== Y ======== Z
+//
+// Z sends this message to X's node to establish a new direct link to X. Both
+// X's and Z's existing links to Y are left intact in a decaying state:
+//
+// - - - Y - - -
+// / \
+// X === (central) === Z
+//
+// The recipient of this message must send a StopProxying message to Y, as well
+// as a ProxyWillStop message to Z, in order for those decaying links to be
+// phased out.
+//
+// Z must send this message to X only after receiving a BypassPeer request from
+// Y. That request signifies that X's node has been adequately prepared by Y to
+// authenticate this request from Z.
+IPCZ_MSG_BEGIN(AcceptBypassLink, IPCZ_MSG_ID(31), IPCZ_MSG_VERSION(0))
+ // Identifies the node of the targeted router's own outward peer, as well as
+ // the sublink their nodes use to route between those routers. In the above
+ // scenario these fields identify the link between X and Y to be replaced, and
+ // as a consequence they uniquely identify X itself to the recipient.
+ IPCZ_MSG_PARAM(NodeName, current_peer_node)
+ IPCZ_MSG_PARAM(SublinkId, current_peer_sublink)
+
+ // The length of the parcel sequence routed from Z to Y before Z adopted X as
+ // its new outward peer, which is implicitly also the final length of the
+ // parcel sequence to be routed from Y to X before that link is dropped.
+ IPCZ_MSG_PARAM(SequenceNumber, inbound_sequence_length_from_bypassed_link)
+
+ // A new sublink which can now be used to route messages between X and Z on
+ // the NodeLink transmitting this AcceptBypassLink message.
+ IPCZ_MSG_PARAM(SublinkId, new_sublink)
+
+ // The shared memory location of the new link's RouterLinkState. This may be
+ // null if one could not be allocated ahead of time, in which case one will be
+ // allocated and shared later.
+ IPCZ_MSG_PARAM(FragmentDescriptor, new_link_state_fragment)
+IPCZ_MSG_END()
+
+// Informs a router about how many more parcels it can expect to receive from
+// its inward and outward peers before it can stop proxying between them and
+// cease to exist. Given routers X, Y, and Z in a configuration resulting from
+// a BypassPeer from Y to Z, followed by an AcceptBypassLink from Z to X:
+//
+// - - - Y - - -
+// / \
+// X === (central) === Z
+//
+// This message is sent from X to Y to provide the final length of the sequence
+// of parcels routed through Y in either direction, now that X and Z have
+// established a direct connection.
+IPCZ_MSG_BEGIN(StopProxying, IPCZ_MSG_ID(32), IPCZ_MSG_VERSION(0))
+ // Identifies the router to receive this message.
+ IPCZ_MSG_PARAM(SublinkId, sublink)
+
+ // The final sequence length of inbound parcels the router can expect from its
+ // outward peer. In the scenario above, this is the sequence of parcels routed
+ // from X to Y.
+ IPCZ_MSG_PARAM(SequenceNumber, inbound_sequence_length)
+
+ // The final sequence length of outbound parcels the router can expect from
+ // its inward peer. In the scenario above, this is the sequence of parcels
+ // routed from Z to Y.
+ IPCZ_MSG_PARAM(SequenceNumber, outbound_sequence_length)
+IPCZ_MSG_END()
+
+// Informs a router about how many more parcels it can expect to receive from
+// its outward peer. Given routers X, Y, and Z in a configuration resulting from
+// a BypassPeer from Y to Z, followed by an AcceptBypassLink from Z to X:
+//
+// - - - Y - - -
+// / \
+// X === (central) === Z
+//
+// This message is sent from X to Z to provide the final length of the sequence
+// of parcels routed from X to Y (and therefore from Y to Z), now that X and Z
+// have established a direct connection.
+IPCZ_MSG_BEGIN(ProxyWillStop, IPCZ_MSG_ID(33), IPCZ_MSG_VERSION(0))
+ // Identifies the router to receive this message.
+ IPCZ_MSG_PARAM(SublinkId, sublink)
+
+ // The final sequence length of inbound parcels the router can expect from its
+ // outward peer.
+ IPCZ_MSG_PARAM(SequenceNumber, inbound_sequence_length)
+IPCZ_MSG_END()
+
+// Informs a router that its outward peer can be bypassed, and provides a new
+// link with which to execute that bypass. Given the following arrangement where
+// X, Y, and Z are routers; AND X and Y live on the same node as each other:
+//
+// X ==== (central) ==== Y ======== Z
+//
+// Y sends this to Z to establish a new link (over the same NodeLink) directly
+// between Z and X. Both X's and Z's existing links to Y are left intact in a
+// decaying state:
+//
+// - - - Y - - -
+// / \
+// X === (central) === Z
+//
+// Note that unlike with BypassPeer/AcceptBypassLink, there is no need to
+// authenticate this request, as it's only swapping one sublink out for another
+// along the same NodeLink.
+IPCZ_MSG_BEGIN(BypassPeerWithLink, IPCZ_MSG_ID(34), IPCZ_MSG_VERSION(0))
+ // Identifies the router to receive this message.
+ IPCZ_MSG_PARAM(SublinkId, sublink)
+
+ // A new sublink which can now be used to route messages between X and Z on
+ // the NodeLink transmitting this BypassPeerWithLink message.
+ IPCZ_MSG_PARAM(SublinkId, new_sublink)
+
+ // The shared memory location of the new link's RouterLinkState. This may be
+ // null if one could not be allocated ahead of time, in which case one will be
+ // allocated and shared later.
+ IPCZ_MSG_PARAM(FragmentDescriptor, new_link_state_fragment)
+
+ // The final length of the sequence of inbound parcels the recipient Z can
+ // expect to receive from Y. Parcels beyond this point come directly from X
+ // over the newly established link.
+ IPCZ_MSG_PARAM(SequenceNumber, inbound_sequence_length)
+IPCZ_MSG_END()
+
+// Provides a router with the final length of the sequence of outbound parcels
+// that will be routed to it via its decaying inward link. Given the following
+// arrangement where X, Y, and Z are routers; X and Y live on the same node
+// as each other: and Y has already sent a BypassPeerWithLink message to Z:
+//
+// - - - Y - - -
+// / \
+// X === (central) === Z
+//
+// This message is sent from Z back to Y, informing Y of the last parcel it can
+// expect to receive from Z, now that X and Z are connected directly.
+IPCZ_MSG_BEGIN(StopProxyingToLocalPeer, IPCZ_MSG_ID(35), IPCZ_MSG_VERSION(0))
+ // Identifies the router to receive this message.
+ IPCZ_MSG_PARAM(SublinkId, sublink)
+
+ // The final length of the sequence of outbound parcels the recipient Y can
+ // expect to receive from the sender Z. Beyond this point, parcels are no
+ // longer routed through Y in that direction.
+ IPCZ_MSG_PARAM(SequenceNumber, outbound_sequence_length)
IPCZ_MSG_END()
// Hints to the target router that it should flush its state. Generally sent to
diff --git a/src/ipcz/remote_router_link.cc b/src/ipcz/remote_router_link.cc
index 98dab6b..0c2be7f 100644
--- a/src/ipcz/remote_router_link.cc
+++ b/src/ipcz/remote_router_link.cc
@@ -26,7 +26,10 @@
sublink_(sublink),
type_(type),
side_(side) {
- ABSL_ASSERT(type.is_central() || link_state.is_null());
+ // Central links must be constructed with a valid RouterLinkState fragment.
+ // Other links must not.
+ ABSL_ASSERT(type.is_central() == !link_state.is_null());
+
if (type.is_central()) {
SetLinkState(std::move(link_state));
}
@@ -47,32 +50,17 @@
void RemoteRouterLink::SetLinkState(FragmentRef<RouterLinkState> state) {
ABSL_ASSERT(type_.is_central());
- if (state.is_null()) {
- // By convention, if a central link has no RouterLinkState at construction
- // time, side A is responsible for allocating a new one and sharing it with
- // side B eventually. Side B lives with a null RouterLinkState until then.
- if (side_.is_side_a()) {
- AllocateAndShareLinkState();
- }
- return;
- }
+ ABSL_ASSERT(!state.is_null());
if (state.is_pending()) {
- // By convention, side A should never be given a pending RouterLinkState
- // fragment.
- ABSL_ASSERT(side_.is_side_b());
-
- // Side B on the other hand may obtain a RouterLinkState fragment which it
- // can't address yet, and in this case, we wait for the fragment's buffer to
- // be mapped locally.
+ // Wait for the fragment's buffer to be mapped locally.
Ref<NodeLinkMemory> memory = WrapRefCounted(&node_link()->memory());
FragmentDescriptor descriptor = state.fragment().descriptor();
memory->WaitForBufferAsync(
descriptor.buffer_id(),
[self = WrapRefCounted(this), memory, descriptor] {
- auto fragment = memory->GetFragment(descriptor);
- self->SetLinkState(
- memory->AdoptFragmentRef<RouterLinkState>(fragment));
+ self->SetLinkState(memory->AdoptFragmentRef<RouterLinkState>(
+ memory->GetFragment(descriptor)));
});
return;
}
@@ -283,30 +271,60 @@
node_link()->RemoveRemoteRouterLink(sublink_);
}
+void RemoteRouterLink::BypassPeer(const NodeName& bypass_target_node,
+ SublinkId bypass_target_sublink) {
+ msg::BypassPeer bypass;
+ bypass.params().sublink = sublink_;
+ bypass.params().reserved0 = 0;
+ bypass.params().bypass_target_node = bypass_target_node;
+ bypass.params().bypass_target_sublink = bypass_target_sublink;
+ node_link()->Transmit(bypass);
+}
+
+void RemoteRouterLink::StopProxying(SequenceNumber inbound_sequence_length,
+ SequenceNumber outbound_sequence_length) {
+ msg::StopProxying stop;
+ stop.params().sublink = sublink_;
+ stop.params().inbound_sequence_length = inbound_sequence_length;
+ stop.params().outbound_sequence_length = outbound_sequence_length;
+ node_link()->Transmit(stop);
+}
+
+void RemoteRouterLink::ProxyWillStop(SequenceNumber inbound_sequence_length) {
+ msg::ProxyWillStop will_stop;
+ will_stop.params().sublink = sublink_;
+ will_stop.params().inbound_sequence_length = inbound_sequence_length;
+ node_link()->Transmit(will_stop);
+}
+
+void RemoteRouterLink::BypassPeerWithLink(
+ SublinkId new_sublink,
+ FragmentRef<RouterLinkState> new_link_state,
+ SequenceNumber inbound_sequence_length) {
+ msg::BypassPeerWithLink bypass;
+ bypass.params().sublink = sublink_;
+ bypass.params().new_sublink = new_sublink;
+ bypass.params().new_link_state_fragment =
+ new_link_state.release().descriptor();
+ bypass.params().inbound_sequence_length = inbound_sequence_length;
+ node_link()->Transmit(bypass);
+}
+
+void RemoteRouterLink::StopProxyingToLocalPeer(
+ SequenceNumber outbound_sequence_length) {
+ msg::StopProxyingToLocalPeer stop;
+ stop.params().sublink = sublink_;
+ stop.params().outbound_sequence_length = outbound_sequence_length;
+ node_link()->Transmit(stop);
+}
+
std::string RemoteRouterLink::Describe() const {
std::stringstream ss;
- ss << type_.ToString() << " link on "
+ ss << type_.ToString() << " link from "
<< node_link_->local_node_name().ToString() << " to "
<< node_link_->remote_node_name().ToString() << " via sublink "
<< sublink_;
return ss.str();
}
-void RemoteRouterLink::AllocateAndShareLinkState() {
- node_link()->memory().AllocateRouterLinkState(
- [self = WrapRefCounted(this)](FragmentRef<RouterLinkState> state) {
- if (state.is_null()) {
- DLOG(ERROR) << "Unable to allocate RouterLinkState.";
- return;
- }
- ABSL_ASSERT(state.is_addressable());
- self->SetLinkState(state);
-
- msg::SetRouterLinkState set;
- set.params().sublink = self->sublink();
- set.params().descriptor = state.release().descriptor();
- self->node_link()->Transmit(set);
- });
-}
-
} // namespace ipcz
diff --git a/src/ipcz/remote_router_link.h b/src/ipcz/remote_router_link.h
index e84e092..859dd54 100644
--- a/src/ipcz/remote_router_link.h
+++ b/src/ipcz/remote_router_link.h
@@ -38,7 +38,7 @@
// this RemoteRouterLink falls (side A or B), and `type` indicates what type
// of link it is -- which for remote links must be either kCentral,
// kPeripheralInward, or kPeripheralOutward. If the link is kCentral, a
- // non-null `link_state` may be provided to use as the link's RouterLinkState.
+ // non-null `link_state` must be provided for the link's RouterLinkState.
static Ref<RemoteRouterLink> Create(Ref<NodeLink> node_link,
SublinkId sublink,
FragmentRef<RouterLinkState> link_state,
@@ -48,23 +48,6 @@
const Ref<NodeLink>& node_link() const { return node_link_; }
SublinkId sublink() const { return sublink_; }
- // Sets this link's RouterLinkState.
- //
- // If `state` is null and this link is on side B, this call is a no-op. If
- // `state` is null and this link is on side A, this call will kick off an
- // asynchronous allocation of a new RouterLinkState. When that completes, the
- // new state will be adopted by side A and shared with side B.
- //
- // If `state` references a pending fragment and this link is on side A, the
- // call is a no-op. If `state` references a pending fragment and this link
- // is on side B, this operation will be automatically deferred until the
- // NodeLink acquires a mapping of the buffer referenced by `state` and the
- // fragment can be resolved to an addressable one.
- //
- // Finally, if `state` references a valid, addressable fragment, it is
- // adopted as-is.
- void SetLinkState(FragmentRef<RouterLinkState> state);
-
// RouterLink:
LinkType GetType() const override;
RouterLinkState* GetLinkState() const override;
@@ -78,6 +61,16 @@
void Unlock() override;
bool FlushOtherSideIfWaiting() override;
bool CanNodeRequestBypass(const NodeName& bypass_request_source) override;
+ void BypassPeer(const NodeName& bypass_target_node,
+ SublinkId bypass_request_sublink) override;
+ void StopProxying(SequenceNumber inbound_sequence_length,
+ SequenceNumber outbound_sequence_length) override;
+ void ProxyWillStop(SequenceNumber inbound_sequence_length) override;
+ void BypassPeerWithLink(SublinkId new_sublink,
+ FragmentRef<RouterLinkState> new_link_state,
+ SequenceNumber inbound_sequence_length) override;
+ void StopProxyingToLocalPeer(
+ SequenceNumber outbound_sequence_length) override;
void Deactivate() override;
std::string Describe() const override;
@@ -90,7 +83,9 @@
~RemoteRouterLink() override;
- void AllocateAndShareLinkState();
+ // Sets this link's RouterLinkState. `state` must be pending or addressable
+ // and this must be a central link.
+ void SetLinkState(FragmentRef<RouterLinkState> state);
const Ref<NodeLink> node_link_;
const SublinkId sublink_;
@@ -103,15 +98,10 @@
std::atomic<bool> side_is_stable_{false};
// A reference to the shared memory Fragment containing the RouterLinkState
- // shared by both ends of this RouterLink. Always null for non-central links,
- // and may be null for a central links if its RouterLinkState has not yet been
- // allocated or shared.
- //
- // Must be set at most once and is only retained by this object to keep the
- // fragment allocated. Access is unguarded and is restricted to
- // SetLinkState(), and only allowed while `link_state_` below is still null.
- // Any other access is unsafe. Use GetLinkState() to get a usable reference to
- // the RouterLinkState instance.
+ // shared by both ends of this RouterLink. Only used by central links. Once
+ // this is set to a non-null fragment and that fragment is addressable by this
+ // link's node, `link_state_` is also updated to cache a pointer to this
+ // fragment's mapped memory.
FragmentRef<RouterLinkState> link_state_fragment_;
// Cached address of the shared RouterLinkState referenced by
diff --git a/src/ipcz/router.cc b/src/ipcz/router.cc
index 62e5c49..779e6fc 100644
--- a/src/ipcz/router.cc
+++ b/src/ipcz/router.cc
@@ -496,6 +496,46 @@
Flush();
}
+bool Router::BypassPeer(RemoteRouterLink& requestor,
+ const NodeName& bypass_target_node,
+ SublinkId bypass_target_sublink) {
+ // TODO: Implement this.
+ return true;
+}
+
+bool Router::AcceptBypassLink(
+ Ref<NodeLink> new_node_link,
+ SublinkId new_sublink,
+ FragmentRef<RouterLinkState> new_link_state,
+ SequenceNumber inbound_sequence_length_from_bypassed_link) {
+ // TODO: Implement this.
+ return true;
+}
+
+bool Router::StopProxying(SequenceNumber inbound_sequence_length,
+ SequenceNumber outbound_sequence_length) {
+ // TODO: Implement this.
+ return true;
+}
+
+bool Router::NotifyProxyWillStop(SequenceNumber inbound_sequence_length) {
+ // TODO: Implement this.
+ return true;
+}
+
+bool Router::BypassPeerWithLink(NodeLink& from_node_link,
+ SublinkId new_sublink,
+ FragmentRef<RouterLinkState> new_link_state,
+ SequenceNumber inbound_sequence_length) {
+ // TODO: Implement this.
+ return true;
+}
+
+bool Router::StopProxyingToLocalPeer(SequenceNumber outbound_sequence_length) {
+ // TODO: Implement this.
+ return true;
+}
+
void Router::NotifyLinkDisconnected(RemoteRouterLink& link) {
{
absl::MutexLock lock(&mutex_);
diff --git a/src/ipcz/router.h b/src/ipcz/router.h
index 54b9037..847622a 100644
--- a/src/ipcz/router.h
+++ b/src/ipcz/router.h
@@ -8,12 +8,14 @@
#include <cstdint>
#include <utility>
+#include "ipcz/fragment_ref.h"
#include "ipcz/ipcz.h"
#include "ipcz/parcel_queue.h"
#include "ipcz/route_edge.h"
#include "ipcz/router_descriptor.h"
#include "ipcz/router_link.h"
#include "ipcz/sequence_number.h"
+#include "ipcz/sublink_id.h"
#include "ipcz/trap_set.h"
#include "third_party/abseil-cpp/absl/synchronization/mutex.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
@@ -23,6 +25,7 @@
class NodeLink;
class RemoteRouterLink;
+struct RouterLinkState;
// The Router is the main primitive responsible for routing parcels between ipcz
// portals. This class is thread-safe.
@@ -60,8 +63,7 @@
// Router.
void QueryStatus(IpczPortalStatus& status);
- // Returns true iff this Router's outward link is a LocalRouterLink between
- // `this` and `other`.
+ // Returns true iff this is a LocalRouterLink whose peer router is `router`.
bool HasLocalPeer(Router& router);
// Attempts to send an outbound parcel originating from this Router. Called
@@ -139,9 +141,86 @@
void BeginProxyingToNewRouter(NodeLink& to_node_link,
const RouterDescriptor& descriptor);
- // Notifies this router that the given RemoteRouterLink has been disconnected
- // due to an underlying NodeLink disconnection. This is only called for
- // RemoteRouterLinks which are associated with this Router.
+ // Notifies this router that it should reach out to its outward peer's own
+ // outward peer in order to establish a direct link. `requestor` is the link
+ // over which this request arrived, and it must be this router's current
+ // outward peer in order for the request to be valid.
+ //
+ // Note that the requestor and its own outward peer must exist on different
+ // nodes in order for this method to be called. `bypass_target_node`
+ // identifies the node where that router lives, and `bypass_target_sublink`
+ // identifies the Sublink used to route between that router and the requestor;
+ // i.e., it identifies the link to be bypassed.
+ //
+ // If the requestor's own outward peer lives on a different node from this
+ // router, this router proceeds with the bypass by allocating a new link
+ // between itself and the requestor's outward peer and sharing it with that
+ // router's node via an AcceptBypassLink message, which will ultimately invoke
+ // AcceptBypassLink() on the targeted router.
+ //
+ // If the requestor's outward peer lives on the same node as this router,
+ // bypass is completed immediately by establishing a new LocalRotuerLink
+ // between the two routers. In this case a StopProxying message is sent back
+ // to the requestor in order to finalize the bypass.
+ bool BypassPeer(RemoteRouterLink& requestor,
+ const NodeName& bypass_target_node,
+ SublinkId bypass_target_sublink);
+
+ // Begins decaying this router's outward link and replaces it with a new link
+ // over `new_node_link` via `new_sublink`, and using (optional)
+ // `new_link_state` for its shared state.
+ //
+ // `inbound_sequence_length_from_bypassed_link` conveys the final length of
+ // sequence of inbound parcels to expect over the decaying link from the peer.
+ // See comments on the BypassPeer definition in node_messages_generator.h.
+ bool AcceptBypassLink(
+ Ref<NodeLink> new_node_link,
+ SublinkId new_sublink,
+ FragmentRef<RouterLinkState> new_link_state,
+ SequenceNumber inbound_sequence_length_from_bypassed_link);
+
+ // Configures the final inbound and outbound sequence lengths of this router's
+ // decaying links. Once these lengths are set and sequences have progressed
+ // to the specified length in each direction, those decaying links -- and
+ // eventually the router itself -- are dropped.
+ bool StopProxying(SequenceNumber inbound_sequence_length,
+ SequenceNumber outbound_sequence_length);
+
+ // Configures the final length of the inbound parcel sequence coming from the
+ // this router's decaying outward link. Once this length is set and the
+ // decaying link has forwarded the full sequence of parcels up to this limit,
+ // the decaying link can be dropped.
+ bool NotifyProxyWillStop(SequenceNumber inbound_sequence_length);
+
+ // Begins decaying this router's outward link and replaces it with a new link
+ // using `new_sublink` over `from_node_link`, the node issuing this request.
+ // `new_link_state` if non-null specifies the shared memory location of the
+ // RouterLinkState for this link.
+ //
+ // `inbound_sequence_length` conveys the final length of the sequence of
+ // inbound parcels to expect over the decaying link.
+ bool BypassPeerWithLink(NodeLink& from_node_link,
+ SublinkId new_sublink,
+ FragmentRef<RouterLinkState> new_link_state,
+ SequenceNumber inbound_sequence_length);
+
+ // Configures the final sequence length of outbound parcels to expect on this
+ // proxying Router's decaying inward link. Once this is set and the decaying
+ // link has received the full sequence of parcels, the link can be dropped.
+ bool StopProxyingToLocalPeer(SequenceNumber outbound_sequence_length);
+
+ // Notifies this Router that one of its links has been disconnected from a
+ // remote node. The link is identified by a combination of a specific NodeLink
+ // and SublinkId.
+ //
+ // Note that this is invoked if ANY RemoteRouterLink bound to this router is
+ // disconnected at its underlying NodeLink, and the result is aggressive
+ // teardown of the route in both directions across any remaining (i.e. primary
+ // and/or decaying) links.
+ //
+ // For a proxying router which is generally only kept alive by the links
+ // which are bound to it, this call will typically be followed by imminent
+ // destruction of this Router once the caller releases its own reference.
void NotifyLinkDisconnected(RemoteRouterLink& link);
// Flushes any inbound or outbound parcels, as well as any route closure
@@ -160,6 +239,15 @@
private:
~Router() override;
+ // Attempts to initiate bypass of this router by its peers, and ultimately to
+ // remove this router from its route.
+ //
+ // Called during a Flush() if this is a proxying router which just dropped its
+ // last decaying link, or if Flush() was called with kForceProxyBypassAttempt,
+ // indicating that some significant state has changed on the route which might
+ // unblock our bypass.
+ bool MaybeStartSelfBypass();
+
absl::Mutex mutex_;
// The current computed portal status to be reflected by a portal controlling
diff --git a/src/ipcz/router_link.h b/src/ipcz/router_link.h
index b08942f..59e13ee 100644
--- a/src/ipcz/router_link.h
+++ b/src/ipcz/router_link.h
@@ -5,6 +5,7 @@
#ifndef IPCZ_SRC_IPCZ_ROUTER_LINK_H_
#define IPCZ_SRC_IPCZ_ROUTER_LINK_H_
+#include "ipcz/fragment_ref.h"
#include "ipcz/link_type.h"
#include "ipcz/node_name.h"
#include "ipcz/sequence_number.h"
@@ -15,6 +16,7 @@
class NodeLink;
class Parcel;
+class RemoteRouterLink;
class Router;
struct RouterLinkState;
@@ -37,7 +39,8 @@
// returns null.
virtual RouterLinkState* GetLinkState() const = 0;
- // Returns true iff this is a LocalRouterLink whose peer router is `router`.
+ // Returns true if this is a LocalRouterLink and the Router on the other side
+ // of the link is `router`.
virtual bool HasLocalPeer(const Router& router) = 0;
// Passes a parcel to the Router on the other side of this link to be queued
@@ -96,6 +99,49 @@
// matches the NodeName it stored in this link's shared state at that time.
virtual bool CanNodeRequestBypass(const NodeName& bypass_request_source) = 0;
+ // Requests that the router on the other side of this link bypass the router
+ // on this side. `bypass_target_node` is the name node where the router's
+ // outward peer lives, and `bypass_target_sublink` identifies the link between
+ // that router and the router on the other side of this link.
+ virtual void BypassPeer(const NodeName& bypass_target_node,
+ SublinkId bypass_target_sublink) = 0;
+
+ // Informs the router on the other side of this link about when it can drop
+ // its inward and outward links. Specifically,`inbound_sequence_length` is the
+ // final length of the parcel sequence the router must expect to receive from
+ // its outward peer and forward to its inbound peer; while
+ // `outbound_sequence_length` is the final length of the parcel sequence the
+ // router must expect to receive from its inward peer and forward to its
+ // outward peer.
+ virtual void StopProxying(SequenceNumber inbound_sequence_length,
+ SequenceNumber outbound_sequence_length) = 0;
+
+ // Informs the router on the other side of this link that the router it most
+ // recently bypassed will stop sending it parcels once the router's inbound
+ // sequence length reaches `inbound_sequence_length`, at which point the
+ // router's link to the proxy can be dropped.
+ virtual void ProxyWillStop(SequenceNumber inbound_sequence_length) = 0;
+
+ // Informs the router on the other side of this link that its outward peer
+ // (and the router on this side of this link) can be bypassed, and provides a
+ // new link (over the same NodeLink) to adopt for that bypass operation.
+ // `new_link_state` is a freshly allocated RouterLinkState fragment for the
+ // new link, and `inbound_sequence_length` is the current inbound sequence
+ // length of the router on this side of the link.
+ virtual void BypassPeerWithLink(SublinkId new_sublink,
+ FragmentRef<RouterLinkState> new_link_state,
+ SequenceNumber inbound_sequence_length) = 0;
+
+ // Informs the router on the other side of this link that its inward peer
+ // (i.e. the router on this side of the link) has bypassed it in favor of a
+ // direct link to proxy's own local outward peer. This is essentially a reply
+ // to a BypassPeerWithLink() call from the other side of this link.
+ // `outbound_sequence_length` is the current outbound sequence length of the
+ // router on this side of the link at the moment it switches to the new link
+ // for its outward transmissions.
+ virtual void StopProxyingToLocalPeer(
+ SequenceNumber outbound_sequence_length) = 0;
+
// Deactivates this RouterLink to sever any binding it may have to a specific
// Router. Note that deactivation is not necessarily synchronous, so some
// in-progress calls into a Router may still complete on behalf of this
diff --git a/src/ipcz/router_link_test.cc b/src/ipcz/router_link_test.cc
index f89f1d4..77336c9 100644
--- a/src/ipcz/router_link_test.cc
+++ b/src/ipcz/router_link_test.cc
@@ -350,38 +350,6 @@
CloseRoutes(router_pairs);
}
-TEST_F(RemoteRouterLinkTest, NewLinkWithNullState) {
- // Occupy all fragments in the primary buffer so they aren't usable.
- std::vector<FragmentRef<RouterLinkState>> unused_fragments =
- nodes().AllocateAllRouterLinkStates();
-
- constexpr size_t kNumIterations = 15000;
- std::vector<Router::Pair> router_pairs =
- CreateTestRouterPairs(kNumIterations);
- std::vector<RouterLink::Pair> links;
- for (size_t i = 0; i < kNumIterations; ++i) {
- auto [a, b] = router_pairs[i];
- auto [a_link, b_link] = nodes().LinkRemoteRouters(a, nullptr, b, nullptr);
- a_link->MarkSideStable();
- b_link->MarkSideStable();
- EXPECT_FALSE(a_link->TryLockForClosure());
- EXPECT_FALSE(b_link->TryLockForClosure());
- links.emplace_back(std::move(a_link), std::move(b_link));
- }
-
- // Since we're using the synchronous driver, by the time transport activation
- // completes, side A of each link must have already dynamically allocated a
- // RouterLinkState and shared it with side B.
- nodes().ActivateTransports();
- for (const auto& [a_link, b_link] : links) {
- EXPECT_TRUE(a_link->TryLockForClosure());
- a_link->Unlock();
- EXPECT_TRUE(b_link->TryLockForClosure());
- }
-
- CloseRoutes(router_pairs);
-}
-
INSTANTIATE_TEST_SUITE_P(,
RouterLinkTest,
::testing::Values(RouterLinkTestMode::kLocal,
diff --git a/src/util/multi_mutex_lock.h b/src/util/multi_mutex_lock.h
new file mode 100644
index 0000000..2633dad
--- /dev/null
+++ b/src/util/multi_mutex_lock.h
@@ -0,0 +1,65 @@
+// 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_UTIL_MULTI_MUTEX_LOCK_H_
+#define IPCZ_SRC_UTIL_MULTI_MUTEX_LOCK_H_
+
+#include <algorithm>
+#include <array>
+#include <cstddef>
+
+#include "third_party/abseil-cpp/absl/synchronization/mutex.h"
+
+namespace ipcz {
+
+// MultiMutexLock is a scoped mutex locker capable of locking between two and
+// four mutexes simultaneously. Locks are always acquired in a globally
+// consistent order based on the address of each Mutex.
+template <size_t N>
+class ABSL_SCOPED_LOCKABLE MultiMutexLock {
+ public:
+ MultiMutexLock(absl::Mutex* a, absl::Mutex* b)
+ ABSL_EXCLUSIVE_LOCK_FUNCTION(*a, *b)
+ : mutexes_({a, b}) {
+ SortAndLock();
+ }
+
+ MultiMutexLock(absl::Mutex* a, absl::Mutex* b, absl::Mutex* c)
+ ABSL_EXCLUSIVE_LOCK_FUNCTION(*a, *b, *c)
+ : mutexes_({a, b, c}) {
+ SortAndLock();
+ }
+
+ MultiMutexLock(absl::Mutex* a, absl::Mutex* b, absl::Mutex* c, absl::Mutex* d)
+ ABSL_EXCLUSIVE_LOCK_FUNCTION(*a, *b, *c, *d)
+ : mutexes_({a, b, c, d}) {
+ SortAndLock();
+ }
+
+ ~MultiMutexLock() ABSL_UNLOCK_FUNCTION() {
+ for (absl::Mutex* mutex : mutexes_) {
+ mutex->Unlock();
+ }
+ }
+
+ private:
+ void SortAndLock() ABSL_NO_THREAD_SAFETY_ANALYSIS {
+ std::sort(mutexes_.begin(), mutexes_.end());
+ for (absl::Mutex* mutex : mutexes_) {
+ mutex->Lock();
+ }
+ }
+
+ std::array<absl::Mutex*, N> mutexes_;
+};
+
+// Helpful deduction guides so instantiations can omit a template argument.
+MultiMutexLock(absl::Mutex*, absl::Mutex*)->MultiMutexLock<2>;
+MultiMutexLock(absl::Mutex*, absl::Mutex*, absl::Mutex*)->MultiMutexLock<3>;
+MultiMutexLock(absl::Mutex*, absl::Mutex*, absl::Mutex*, absl::Mutex*)
+ ->MultiMutexLock<4>;
+
+} // namespace ipcz
+
+#endif // IPCZ_SRC_UTIL_MULTI_MUTEX_LOCK_H_