Remove co-dependencies for different modules found in `crates` and migrate the development history to a new repository. The docs were stale, and largely not getting used, so simply deleted for now. When code stabilizes more, they will become useful again. Co-authored-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com> Co-committed-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
86 lines
2.7 KiB
Rust
86 lines
2.7 KiB
Rust
//! Relay candidacy evaluation.
|
|
//!
|
|
//! Determines whether this node is eligible to run an embedded relay server
|
|
//! by checking a set of extensible rules (public IP, port availability, etc.).
|
|
|
|
use std::net::{IpAddr, SocketAddr, UdpSocket};
|
|
|
|
// ─── Candidacy ──────────────────────────────────────────────────────────
|
|
|
|
/// Result of evaluating a single candidacy rule.
|
|
pub enum CandidacyResult {
|
|
Eligible,
|
|
Ineligible(String),
|
|
}
|
|
|
|
/// Extensible rule for relay candidacy evaluation.
|
|
pub trait CandidacyRule: Send {
|
|
fn evaluate(&self) -> CandidacyResult;
|
|
}
|
|
|
|
/// Checks whether the node's outbound IP is public (non-RFC1918, non-loopback).
|
|
pub struct PublicIpRule;
|
|
|
|
impl PublicIpRule {
|
|
pub fn outbound_ip() -> Option<IpAddr> {
|
|
let sock = UdpSocket::bind("0.0.0.0:0").ok()?;
|
|
sock.connect("192.0.2.1:80").ok()?; // RFC 5737 TEST-NET-1 (non-routable)
|
|
Some(sock.local_addr().ok()?.ip())
|
|
}
|
|
|
|
fn is_public(ip: &IpAddr) -> bool {
|
|
match ip {
|
|
IpAddr::V4(v4) => {
|
|
!v4.is_loopback()
|
|
&& !v4.is_private()
|
|
&& !v4.is_link_local()
|
|
&& !v4.is_unspecified()
|
|
}
|
|
IpAddr::V6(v6) => !v6.is_loopback() && !v6.is_unspecified(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl CandidacyRule for PublicIpRule {
|
|
fn evaluate(&self) -> CandidacyResult {
|
|
match Self::outbound_ip() {
|
|
Some(ip) if Self::is_public(&ip) => CandidacyResult::Eligible,
|
|
Some(ip) => CandidacyResult::Ineligible(format!("outbound IP {ip} is private")),
|
|
None => CandidacyResult::Ineligible("could not determine outbound IP".into()),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Checks whether the desired relay port is available for binding.
|
|
pub struct PortBindRule {
|
|
addr: SocketAddr,
|
|
}
|
|
|
|
impl PortBindRule {
|
|
pub fn new(addr: SocketAddr) -> Self {
|
|
Self { addr }
|
|
}
|
|
}
|
|
|
|
impl CandidacyRule for PortBindRule {
|
|
fn evaluate(&self) -> CandidacyResult {
|
|
match std::net::TcpListener::bind(self.addr) {
|
|
Ok(_) => CandidacyResult::Eligible,
|
|
Err(e) => CandidacyResult::Ineligible(format!(
|
|
"cannot bind {}: {e}",
|
|
self.addr
|
|
)),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Evaluate all candidacy rules. Returns `Ok(())` if all pass, or
|
|
/// `Err(reason)` with the first failure reason.
|
|
pub fn evaluate_candidacy(rules: &[Box<dyn CandidacyRule>]) -> Result<(), String> {
|
|
for rule in rules {
|
|
if let CandidacyResult::Ineligible(reason) = rule.evaluate() {
|
|
return Err(reason);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|