feat(distributed): serve children in the search tree

The client read the distributed network but never carried it: a peer
dialling with a D connection was logged and dropped, and AcceptChildren
was hardcoded false, so this client took from the tree without ever
relaying a search for anyone else.

With accept_children set, peers hang from us: each is told where our
branch sits when it connects and again when the branch moves, every
search that reaches us is passed down, and a child that goes away loses
its slot. Ten at a time, matching Nicotine+, since each costs a socket
and a copy of the network's whole search stream — which is also why it is
opt-in, through the new accept_children config key (ignored without a
listener, since a child would have nothing to dial).
This commit is contained in:
Michel de Graaf
2026-09-09 16:47:55 +00:00
parent f5a97744f8
commit 460031b532
12 changed files with 648 additions and 37 deletions
+33 -3
View File
@@ -191,6 +191,11 @@ pub struct ClientSettings {
/// Directories whose files are shared with (uploaded to) other peers.
/// Empty means nothing is shared.
pub shared_directories: Vec<String>,
/// Whether to serve children in the distributed search network: peers
/// hang from us with a `D` connection and we pass every search we receive
/// down to them. Off by default — it costs a socket and the network's
/// whole search stream per child.
pub accept_children: bool,
/// The version reported to the server on login. Defaults to the
/// soulseek-rs major version with minor version 1 ("unidentified");
/// clients built on this library should reserve their own minor
@@ -223,6 +228,7 @@ impl Default for ClientSettings {
enable_listen: true,
listen_port: DEFAULT_LISTEN_PORT,
shared_directories: Vec::new(),
accept_children: false,
version: ClientVersion::default(),
}
}
@@ -343,6 +349,12 @@ pub enum ClientOperation {
PossibleParents(Vec<(String, String, u16)>),
/// The server told us to drop our parent.
ResetDistributed,
/// A peer dialled us with a `D` connection, asking to hang from us in the
/// distributed search network.
ChildConnected {
username: String,
stream: std::net::TcpStream,
},
/// Recommendations from the server: either from our own interests
/// (`global` false, code 54) or server-wide (`global` true, code 56).
Recommendations {
@@ -405,6 +417,8 @@ pub struct ClientContext {
operations: Option<Sender<ClientOperation>>,
/// Our place in the distributed search network.
leaf: distributed::Leaf,
/// The children hanging from us, when the client serves any.
pub(crate) children: children::Children,
searches: HashMap<String, Search>,
private_messages: Vec<UserMessage>,
/// Correlation tokens for server-brokered (firewalled) connections, mapping
@@ -565,6 +579,7 @@ impl ClientContext {
server_sender: None,
operations: None,
leaf: distributed::Leaf::new(""),
children: children::Children::default(),
searches: HashMap::new(),
private_messages: Vec::new(),
pending_connect_tokens: HashMap::new(),
@@ -1180,14 +1195,14 @@ impl Client {
#[must_use]
pub fn with_settings(settings: ClientSettings) -> Self {
logger::init();
let mut context = ClientContext::for_user(&settings.username);
context.children = children::Children::new(settings.accept_children);
Self {
enable_listen: settings.enable_listen,
listen_port: settings.listen_port,
bound_port: None,
address: settings.server_address,
context: Arc::new(RwLock::new(ClientContext::for_user(
&settings.username,
))),
context: Arc::new(RwLock::new(context)),
username: settings.username,
password: settings.password,
version: settings.version,
@@ -1197,6 +1212,20 @@ impl Client {
}
}
/// The children currently hanging from us in the distributed search
/// network, by username. Empty unless the client was built with
/// [`ClientSettings::accept_children`].
#[must_use]
pub fn children(&self) -> Vec<String> {
match self.context.read_safe() {
Ok(ctx) => ctx.children.usernames(),
Err(e) => {
error!("[client] children: {}", e);
Vec::new()
}
}
}
/// The username we log in as, for attributing our own messages.
#[must_use]
pub fn username(&self) -> &str {
@@ -1334,6 +1363,7 @@ impl Client {
}
}
mod children;
mod connection;
mod distributed;
mod downloads;