Files
tsunagi/crates/tsunagi-core/src/agent/events.rs
T
Aleksandr BogomiakovandClaude 97377e3703 Relay across any mix of links, one online answer for every client, first claim owns a hostname
A tunnel no longer depends on the kind of link under it. The relay hub keeps
one forwarding table per network instead of one per protocol, the envelope
names the end-to-end protocol, and a neighbour reached over several kinds of
transport is sent to over the best open one. A member in the middle needs no
protocol in common with either end. Which protocol a pair runs is settled by
the lower endpoint id's announced order, so both ends agree without asking.
A direct link that comes up later is shorter than the path through others, so
traffic moves to it with the same logical link.

The agent now answers "is it online" once: a live control session or any data
path. Members and peers report how they are reached (direct over a named
transport, relayed through a named member, or no data path yet), and `tsng
status` and the tray both read that instead of a session list and a WireGuard
handshake respectively.

A hostname belongs to whoever claimed it first. A claim carries the time its
author first claimed the name, signed with the record, and replicas order by
(time, id) so every member names the same holder. A later claimant keeps
working by address, is not found by the name, and is told so in its status,
`tsng status`, the tray and a HostnameTaken event.

Wire change: control and data ALPN 5, control PROTOCOL_VERSION 5, envelope
version 2, signing domain v3, state schema 7, local control protocol 18.
Upgrade every member together.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-05 11:39:33 +01:00

155 lines
4.8 KiB
Rust

//! Events published by a running agent.
//!
//! Events are delivered through a bounded [`tokio::sync::broadcast`] channel.
//! A slow subscriber is lagged, never allowed to stall the runtime.
//!
//! Nothing here ever carries a secret, a derived key or a handshake proof.
use std::time::Duration;
use iroh::EndpointId;
use crate::identity::NetworkId;
use crate::net::TransportKind;
use crate::proto::ControlMessage;
use crate::proto::handshake::Role;
/// Something that happened inside the agent.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum Event {
/// A network was activated locally.
NetworkActivated {
/// The network.
network: NetworkId,
},
/// A network was deactivated locally.
///
/// This is a local deactivation only. It is not a signed revocation of
/// membership, and it says nothing about the network's other participants.
NetworkDeactivated {
/// The network.
network: NetworkId,
},
/// A peer completed the handshake and has an authenticated session.
PeerConnected {
/// The network the session belongs to.
network: NetworkId,
/// Authenticated peer endpoint id.
peer: EndpointId,
/// Which side this agent played.
role: Role,
/// How the connection currently reaches the peer.
transport: TransportKind,
/// RTT of the selected path, if iroh reported one.
rtt: Option<Duration>,
},
/// A peer's session ended.
PeerDisconnected {
/// The network the session belonged to.
network: NetworkId,
/// The peer.
peer: EndpointId,
/// Why the session ended. Free of secrets.
reason: String,
},
/// A control message arrived on an authenticated session.
MessageReceived {
/// The network.
network: NetworkId,
/// The peer that sent it.
peer: EndpointId,
/// The message.
message: ControlMessage,
},
/// An outbound dial failed.
///
/// A dead candidate produces these and nothing else; other peers keep
/// connecting normally.
DialFailed {
/// The network.
network: NetworkId,
/// The candidate that could not be reached.
peer: EndpointId,
/// Why it failed.
reason: String,
},
/// A handshake was rejected.
///
/// The network is `None` when the failure happened before the peer's
/// requested network could be resolved.
HandshakeRejected {
/// The network, when known.
network: Option<NetworkId>,
/// The peer, when known.
peer: Option<EndpointId>,
/// Why it was rejected.
reason: String,
},
/// A message or session was rejected for violating the protocol.
ProtocolViolation {
/// The network, when known.
network: Option<NetworkId>,
/// The peer, when known.
peer: Option<EndpointId>,
/// What was wrong.
reason: String,
},
/// The disposable cache was discarded and recreated at startup.
CacheReset {
/// Why it was discarded. Free of secrets.
reason: String,
},
/// A data plane link to a peer is up.
///
/// The data plane is a separate connection from the control plane; this
/// says nothing about the control session, and vice versa.
DataLinkUp {
/// The network.
network: NetworkId,
/// The peer.
peer: EndpointId,
/// Plugin protocol the link carries.
protocol: String,
/// What the transport reports about the path in use.
path: String,
/// Largest datagram the link can carry.
max_datagram: usize,
},
/// A data plane link went away or could not be opened.
///
/// Never fatal: the control plane keeps running and the link is retried.
DataLinkDown {
/// The network.
network: NetworkId,
/// The peer.
peer: EndpointId,
/// Plugin protocol the link would have carried.
protocol: String,
/// Why it is not up.
reason: String,
},
/// The name this agent claimed was claimed first by another member.
///
/// Nothing stops working: this agent keeps its address and every
/// connection. It just is not found by that name until it is renamed or
/// the member that holds the name lets it go.
HostnameTaken {
/// The network.
network: NetworkId,
/// The name that is taken.
name: String,
/// The member that holds it.
holder: EndpointId,
},
/// An IP plugin reported an error. Never fatal.
PluginError {
/// The network the call was scoped to.
network: NetworkId,
/// Plugin protocol id.
protocol: String,
/// The reported error.
reason: String,
},
}