From a087c489a06c110db10c3e5585301c24b015044e Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Tue, 14 Apr 2026 14:26:32 +0200 Subject: [PATCH] Make IP check public --- src/config.rs | 13 +++++++++++-- src/utils.rs | 37 ++++++++++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/config.rs b/src/config.rs index c5e26f1..fbb784e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -20,7 +20,7 @@ use crate::{ http_signatures::sign_request, protocol::verification::verify_domains_match, traits::{Activity, Actor}, - utils::is_invalid_ip, + utils::validate_ip, }; use async_trait::async_trait; use bytes::Bytes; @@ -183,7 +183,7 @@ impl FederationConfig { } let allow_local = std::env::var("DANGER_FEDERATION_ALLOW_LOCAL_IP").is_ok(); - if !allow_local && is_invalid_ip(domain).await? { + if !allow_local && self.is_valid_ip(&url).await.is_err() { return Err(Error::DomainResolveError(domain.to_string())); } } @@ -222,6 +222,15 @@ impl FederationConfig { pub fn domain(&self) -> &str { &self.domain } + + /// Resolve domain of the url and throw error if it points to local/private IP. + pub async fn is_valid_ip(&self, url: &Url) -> Result<(), Error> { + if self.debug { + return Ok(()); + } + + validate_ip(url).await + } } impl FederationConfigBuilder { diff --git a/src/utils.rs b/src/utils.rs index 2bc923b..9e8fb6c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,16 +2,33 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use crate::error::Error; use tokio::net::lookup_host; +use url::{Host, Url}; -// Resolve domain and see if it points to private IP // TODO: Use is_global() once stabilized // https://doc.rust-lang.org/std/net/enum.IpAddr.html#method.is_global -pub(crate) async fn is_invalid_ip(domain: &str) -> Result { - let mut ips = lookup_host((domain, 80)).await?; - Ok(ips.any(|addr| match addr.ip() { +pub(crate) async fn validate_ip(url: &Url) -> Result<(), Error> { + let mut ip = vec![]; + let host = url + .host() + .ok_or(Error::UrlVerificationError("Url must have a domain"))?; + match host { + Host::Domain(domain) => ip.extend( + lookup_host((domain.to_owned(), 80)) + .await? + .map(|s| s.ip().to_canonical()), + ), + Host::Ipv4(ipv4) => ip.push(ipv4.into()), + Host::Ipv6(ipv6) => ip.push(ipv6.into()), + }; + + let invalid_ip = ip.into_iter().any(|addr| match addr { IpAddr::V4(addr) => v4_is_invalid(addr), IpAddr::V6(addr) => v6_is_invalid(addr), - })) + }); + if invalid_ip { + return Err(Error::DomainResolveError(host.to_string())); + } + Ok(()) } fn v4_is_invalid(v4: Ipv4Addr) -> bool { @@ -48,8 +65,14 @@ mod test { #[tokio::test] async fn test_is_valid_ip() -> Result<(), Error> { - assert!(!is_invalid_ip("example.com").await?); - assert!(is_invalid_ip("localhost").await?); + assert!(validate_ip(&Url::parse("http://example.com")?) + .await + .is_ok()); + assert!(validate_ip(&Url::parse("http://172.66.147.243")?) + .await + .is_ok()); + assert!(validate_ip(&Url::parse("http://localhost")?).await.is_err()); + assert!(validate_ip(&Url::parse("http://127.0.0.1")?).await.is_err()); Ok(()) } }