use std::sync::Arc;
use std::sync::atomic::{AtomicI64, Ordering};
use iroh_relay::protos::relay::Status;
use tracing_subscriber::Layer;
use tracing_subscriber::layer::Context;
#[derive(Debug, Default)]
pub struct IdentityConflict {
last_seen: AtomicI64,
}
impl IdentityConflict {
pub fn last_seen_epoch(&self) -> Option<i64> {
match self.last_seen.load(Ordering::Relaxed) {
0 => None,
t => Some(t),
}
}
pub fn observe(&self, epoch: i64) {
self.last_seen.store(epoch, Ordering::Relaxed);
}
}
fn needle() -> String {
Status::SameEndpointIdConnected.to_string()
}
pub struct IdentityConflictLayer {
state: Arc<IdentityConflict>,
needle: String,
}
impl IdentityConflictLayer {
pub fn new(state: Arc<IdentityConflict>) -> Self {
Self {
state,
needle: needle(),
}
}
}
struct MessageVisitor<'a> {
needle: &'a str,
hit: bool,
}
impl tracing::field::Visit for MessageVisitor<'_> {
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
if field.name() == "message" && format!("{value:?}").contains(self.needle) {
self.hit = true;
}
}
fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
if field.name() == "message" && value.contains(self.needle) {
self.hit = true;
}
}
}
impl<S: tracing::Subscriber> Layer<S> for IdentityConflictLayer {
fn on_event(&self, event: &tracing::Event<'_>, _ctx: Context<'_, S>) {
if *event.metadata().level() > tracing::Level::WARN {
return;
}
let mut v = MessageVisitor {
needle: &self.needle,
hit: false,
};
event.record(&mut v);
if v.hit {
self.state.observe(crate::util::epoch_now_i64());
tracing::error!(
"another endpoint is presenting THIS node's identity — two nodes booted from \
copies of one mesh root cannot both use the relay, and peers will appear \
unreachable. Stop the duplicate, or give it its own identity."
);
}
}
}
pub fn install_for_daemon(state: Arc<IdentityConflict>) {
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::layer::SubscriberExt;
let layer = IdentityConflictLayer::new(state).with_filter(LevelFilter::WARN);
if tracing::subscriber::set_global_default(tracing_subscriber::registry().with(layer)).is_err()
{
tracing::debug!(
"a tracing subscriber is already installed, so the duplicate-identity detector was \
NOT registered (#134)"
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use tracing_subscriber::prelude::*;
#[test]
fn the_needle_is_derived_from_iroh_and_is_specific() {
let n = needle();
assert!(
n.len() > 20,
"too short to be a safe substring match — it would match unrelated warnings: {n:?}"
);
assert!(
n.to_lowercase().contains("endpoint id"),
"the needle must still be about endpoint identity; if iroh made this generic the \
layer is no longer a diagnostic: {n:?}"
);
assert_ne!(
n,
Status::Healthy.to_string(),
"the needle must not collapse onto the healthy status"
);
}
#[test]
fn the_layer_records_only_the_duplicate_identity_warning() {
let state = Arc::new(IdentityConflict::default());
assert_eq!(state.last_seen_epoch(), None, "nothing observed yet");
let sub = tracing_subscriber::registry().with(IdentityConflictLayer::new(state.clone()));
tracing::subscriber::with_default(sub, || {
tracing::warn!("Relay server reports problem: {}", Status::Healthy);
tracing::warn!("peer unreachable");
tracing::error!("something else broke entirely");
assert_eq!(
state.last_seen_epoch(),
None,
"an unrelated warning must never read as a duplicate identity"
);
tracing::warn!(
"Relay server reports problem: {}",
Status::SameEndpointIdConnected
);
assert!(
state.last_seen_epoch().is_some(),
"the duplicate-identity report must be recorded"
);
});
}
#[test]
fn the_stamp_is_the_most_recent_observation() {
let s = IdentityConflict::default();
s.observe(100);
assert_eq!(s.last_seen_epoch(), Some(100));
s.observe(200);
assert_eq!(s.last_seen_epoch(), Some(200), "later replaces earlier");
}
}