summaryrefslogtreecommitdiffstats
path: root/src/swarm/topology.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/swarm/topology.rs')
-rw-r--r--src/swarm/topology.rs32
1 files changed, 32 insertions, 0 deletions
diff --git a/src/swarm/topology.rs b/src/swarm/topology.rs
index 1d98637..3731036 100644
--- a/src/swarm/topology.rs
+++ b/src/swarm/topology.rs
@@ -47,6 +47,10 @@ pub(crate) struct NeighbourView {
/// Peers that proved they are of our swarm but aren't in our member list (yet): their
/// registration hasn't reached us. They are neighbours too (`refresh`).
unlisted: BTreeSet<String>,
+ /// Members the member list gives no address for on our transports, that reach us
+ /// anyway: they only connect out (no listen address). They are neighbours too while
+ /// they say they sync with us (`refresh`).
+ outbound: BTreeSet<String>,
neighbours: BTreeSet<String>,
/// Every reachable member is a neighbour.
full_mesh: bool,
@@ -227,11 +231,36 @@ pub(crate) async fn refresh(state: &AppState) -> Option<NeighbourChange> {
unlisted.insert(peer);
}
}
+ // A member that lists no address on our transports, but reached us anyway, only
+ // connects out: it isn't in the graph on our side (it's on none of our transports, as
+ // far as the list goes), while on its side we are, so it would sync with us while we
+ // never sent it our changes (only answers to its). It picks its neighbours; those it
+ // picked keep it, as it says (STATUS).
+ let mut outbound = BTreeSet::new();
+ for member in &current {
+ if member == state.local_id()
+ || unreachable.contains(member)
+ || transports
+ .get(member)
+ .is_some_and(|on| !on.is_disjoint(&local_transports))
+ {
+ continue;
+ }
+ if let Some(peer_state) = state.get_peer_state(member).await {
+ let peer_state = peer_state.lock().await;
+ if matches!(peer_state.standing, crate::state::Standing::Member { .. })
+ && peer_state.syncs_with_us != Some(false)
+ {
+ outbound.insert(member.clone());
+ }
+ }
+ }
let previous = {
let view = state.neighbours.read_or_recover();
if view.as_ref().is_some_and(|view| {
view.members == current
&& view.unlisted == unlisted
+ && view.outbound == outbound
&& view.k == k
&& view.unreachable == unreachable
&& view.stable == stable
@@ -256,6 +285,7 @@ pub(crate) async fn refresh(state: &AppState) -> Option<NeighbourChange> {
&|m| unreachable.contains(m),
&|m| stable.contains(m),
);
+ computed.extend(outbound.iter().cloned());
let listed_neighbours = computed.len();
computed.extend(unlisted.iter().cloned());
let change = NeighbourChange {
@@ -277,6 +307,7 @@ pub(crate) async fn refresh(state: &AppState) -> Option<NeighbourChange> {
k,
neighbours = computed.len(),
unlisted = unlisted.len(),
+ outbound = outbound.len(),
added = change.added.len(),
removed = change.removed.len(),
"Neighbours computed from the member list"
@@ -294,6 +325,7 @@ pub(crate) async fn refresh(state: &AppState) -> Option<NeighbourChange> {
transports,
local_transports,
unlisted,
+ outbound,
neighbours: computed,
});
Some(change)