uniform node ids

This commit is contained in:
Evan
2026-03-10 18:26:06 +00:00
parent 8371ec0c07
commit 7a71973d9c
+171 -72
View File
@@ -26,28 +26,111 @@ pub mod error {
}
}
pub mod if_watcher {
use std::{collections::HashSet, net::Ipv6Addr, path::PathBuf};
#[cfg(target_os = "linux")]
use std::path::PathBuf;
use std::{
collections::HashMap,
net::{IpAddr, Ipv6Addr},
};
use futures_lite::StreamExt;
use ipnet::Ipv6Net;
use n0_watcher::Watcher;
use netwatch::interfaces::IpNet;
use netwatch::interfaces::{Interface, IpNet};
use tokio::sync::mpsc;
use crate::{
BabbleError, Result,
babel::Babble,
ip_manager::add_ip,
{BabbleError, Result},
ip_manager::{add_ip, remove_ip},
};
pub const PREFIX: Ipv6Net = Ipv6Net::new_assert(
Ipv6Addr::new(0xfde0, 0x20c6, 0x1fa7, 0xffff, 0, 0, 0, 0),
64,
);
pub const PREFIX: Ipv6Net =
Ipv6Net::new_assert(Ipv6Addr::new(0xfde0, 0x20c6, 0x1fa7, 0, 0, 0, 0, 0), 48);
trait IfaceExt {
fn has_link_local_v6(&self) -> bool;
fn is_real_interface(&self) -> bool;
fn will_babel(&self) -> bool;
fn get_v6_in(&self, prefix: Ipv6Net) -> Option<Ipv6Addr>;
}
impl IfaceExt for Interface {
fn will_babel(&self) -> bool {
self.has_link_local_v6() && self.is_real_interface() && self.is_up()
}
fn has_link_local_v6(&self) -> bool {
let mut has = false;
for addr in self.addrs() {
let IpAddr::V6(a) = addr.addr() else {
continue;
};
if a.is_unicast_link_local() {
has = true;
break;
}
}
has
}
fn is_real_interface(&self) -> bool {
#[cfg(target_os = "macos")]
if !self.name().starts_with("en") {
tracing::debug!("skipping non 'en' interface {}", self.name());
return false;
}
#[cfg(target_os = "linux")]
{
if !PathBuf::from(format!("/sys/class/net/{}/device", self.name())).exists() {
tracing::debug!(
"skipping interface {} as it doesn't correspond to a physical link",
self.name()
);
return false;
}
let dev_type_path = PathBuf::from(format!("/sys/class/net/{}/type", self.name()));
if !dev_type_path.exists() {
tracing::debug!(
"skipping interface {} with no type file at {:?}",
self.name(),
dev_type_path.to_str()
);
return false;
}
let Ok(dev_type) = std::fs::read_to_string(dev_type_path) else {
return false;
};
if dev_type.trim() != "1" {
tracing::debug!(
"skipping interface {} with type {:?}",
self.name(),
dev_type
);
return false;
}
}
true
}
fn get_v6_in(&self, prefix: Ipv6Net) -> Option<Ipv6Addr> {
for addr in self.addrs() {
if let IpNet::V6(v6) = addr
&& prefix.contains(&v6.addr())
{
return Some(v6.addr());
}
}
None
}
}
#[tracing::instrument(skip(send))]
pub async fn watch(send: mpsc::Sender<Babble>) -> Result<()> {
let mut ready_ifaces = HashSet::new();
let mut ready_ifaces = HashMap::new();
let ip_node_id: u128 = (rand::random::<u64>() as u128) << 16;
let my_range: Ipv6Net = Ipv6Net::new_assert(
Ipv6Addr::from_bits(PREFIX.addr().to_bits() | ip_node_id),
112,
);
let mut iface_num: u16 = 0;
tracing::info!("starting interface monitor");
let mon = netwatch::netmon::Monitor::new()
@@ -57,75 +140,53 @@ pub mod if_watcher {
while let Some(s) = mon_stream.next().await {
let mut not_seen = ready_ifaces.clone();
for iface in s.interfaces.values().filter(|iface| iface.is_up()) {
if not_seen.contains(iface.name()) {
assert!(not_seen.remove(iface.name()));
for iface in s.interfaces.values().filter(|iface| iface.will_babel()) {
if not_seen.contains_key(iface.name()) {
assert!(not_seen.remove(iface.name()).is_some());
continue;
}
#[cfg(target_os = "macos")]
if !iface.name().starts_with("en") {
tracing::debug!("skipping non 'en' interface {}", iface.name());
continue;
}
#[cfg(target_os = "linux")]
{
if !PathBuf::from(format!("/sys/class/net/{}/device", iface.name())).exists() {
tracing::debug!(
"skipping interface {} as it doesn't correspond to a physical link",
iface.name()
);
continue;
}
let dev_type_path =
PathBuf::from(format!("/sys/class/net/{}/type", iface.name()));
if !dev_type_path.exists() {
tracing::debug!(
"skipping interface {} with no type file at {:?}",
iface.name(),
dev_type_path.to_str()
);
continue;
}
let dev_type = tokio::fs::read_to_string(dev_type_path).await?;
if dev_type.trim() != "1" {
tracing::debug!(
"skipping interface {} with type {:?}",
iface.name(),
dev_type
);
continue;
}
}
let mut found_addr = false;
for addr in iface.addrs() {
if let IpNet::V6(v6) = addr
&& PREFIX.contains(&v6.addr())
{
found_addr = true;
break;
}
}
if !found_addr {
let lower: u64 = rand::random();
let upper = 0xfde0_20c6_1fa7_ffff_u64;
let addr = Ipv6Addr::from_bits((u128::from(upper) << 64) | u128::from(lower));
tracing::info!("adding new ip {} to {}", addr, iface.name());
add_ip(addr, iface)?;
let my_ip = iface.get_v6_in(my_range);
let mut has_removed = false;
for addr in iface.addrs() {
if let IpNet::V6(v6) = addr
&& PREFIX.contains(&v6)
&& !my_range.contains(&v6)
{
tracing::info!("removing stale ip {v6} from {}", iface.name());
// don't really care if this fails
_ = remove_ip(v6.addr(), iface).await;
has_removed = true;
}
}
if has_removed {
continue;
}
if my_ip.is_none() {
let addr = Ipv6Addr::from_bits(my_range.addr().to_bits() | (iface_num as u128));
iface_num += 1;
assert!(iface_num < u16::MAX, "Really? u16::MAX interfaces?");
tracing::info!("adding new ip {addr} to {}", iface.name());
add_ip(addr, iface).await?;
}
tracing::info!("telling babeld to watch {}", iface.name());
let Ok(()) = send.send(Babble::AddIface(iface.name().to_owned())).await else {
return Ok(());
};
ready_ifaces.insert(iface.name().to_owned());
ready_ifaces.insert(iface.name().to_owned(), iface.clone());
}
for iface in not_seen {
tracing::info!("telling babeld to stop watching {iface}");
let Ok(()) = send.send(Babble::RemoveIface(iface.clone())).await else {
for (name, iface) in not_seen.iter() {
tracing::info!("telling babeld to stop watching {name}");
if let Some(v6) = iface.get_v6_in(PREFIX) {
tracing::info!("removing stale ip {v6} from {name}");
// don't really care if this fails
_ = remove_ip(v6, iface).await;
}
assert!(ready_ifaces.remove(name).is_some());
let Ok(()) = send.send(Babble::RemoveIface(name.to_owned())).await else {
return Ok(());
};
assert!(ready_ifaces.remove(&iface));
}
}
tracing::info!("stopping interface monitor");
@@ -373,23 +434,43 @@ pub mod babel {
}
pub(crate) mod ip_manager {
pub(crate) use sys::add_ip;
pub(crate) use sys::remove_ip;
#[cfg(target_os = "linux")]
mod sys {
use netwatch::interfaces::Interface;
use crate::{BabbleError, Result};
use std::{net::Ipv6Addr, process::Command};
use std::net::Ipv6Addr;
use tokio::process::Command;
#[tracing::instrument]
pub fn add_ip(v6: Ipv6Addr, iface: &Interface) -> Result<()> {
pub async fn add_ip(v6: Ipv6Addr, iface: &Interface) -> Result<()> {
let out = Command::new("ip")
.arg("addr")
.arg("add")
.arg(format!("{v6}/128"))
.arg("dev")
.arg(iface.name())
.output()?;
.output()
.await?;
if out.status.success() {
Ok(())
} else {
Err(BabbleError::FailedToSetIp)
}
}
#[tracing::instrument]
pub async fn remove_ip(v6: Ipv6Addr, iface: &Interface) -> Result<()> {
let out = Command::new("ip")
.arg("addr")
.arg("del")
.arg(format!("{v6}/128"))
.arg("dev")
.arg(iface.name())
.output()
.await?;
if out.status.success() {
Ok(())
} else {
@@ -404,16 +485,34 @@ pub(crate) mod ip_manager {
use crate::BabbleError;
use crate::Result;
use std::{net::Ipv6Addr, process::Command};
use std::net::Ipv6Addr;
use tokio::process::Command;
#[tracing::instrument]
pub fn add_ip(v6: Ipv6Addr, iface: &Interface) -> Result<()> {
pub async fn add_ip(v6: Ipv6Addr, iface: &Interface) -> Result<()> {
let out = Command::new("ifconfig")
.arg(iface.name())
.arg("inet6")
.arg(format!("{v6}/128"))
.arg("add")
.output()?;
.output()
.await?;
if out.status.success() {
Ok(())
} else {
Err(BabbleError::FailedToSetIp)
}
}
#[tracing::instrument]
pub async fn remove_ip(v6: Ipv6Addr, iface: &Interface) -> Result<()> {
let out = Command::new("ifconfig")
.arg(iface.name())
.arg("inet6")
.arg(format!("{v6}/128"))
.arg("delete")
.output()
.await?;
if out.status.success() {
Ok(())
} else {