feat(http): expose narrow benchmarking fake-ip predicate

This commit is contained in:
elky
2026-09-04 16:13:19 +08:00
parent 2d17d4b73f
commit c142d39951
2 changed files with 35 additions and 4 deletions
+33 -2
View File
@@ -89,6 +89,24 @@ pub fn is_private_or_reserved_ip(ip: IpAddr) -> bool {
}
}
/// Return whether an address belongs to RFC 2544's IPv4 benchmarking range.
///
/// Local DNS interception tools (for example, Surge in Fake-IP mode) commonly
/// synthesize answers from `198.18.0.0/15`. The range is intentionally still
/// classified as reserved by [`is_private_or_reserved_ip`]; callers may use
/// this predicate only when they have independently established that the
/// hostname is a trusted, fixed destination. Keeping the predicates separate
/// prevents a compatibility exception from weakening the generic SSRF guard.
pub fn is_ipv4_benchmarking_fake_ip(ip: IpAddr) -> bool {
match ip {
IpAddr::V4(ip) => {
let octets = ip.octets();
octets[0] == 198 && (18..=19).contains(&octets[1])
}
IpAddr::V6(_) => false,
}
}
/// Return true only when a URL names loopback without relying on DNS.
///
/// This is intentionally stricter than accepting names that currently resolve
@@ -110,8 +128,8 @@ pub fn is_https_or_loopback_http_url(url: &Url) -> bool {
#[cfg(test)]
mod tests {
use super::{
connection_declared_header_names, is_https_or_loopback_http_url, is_private_or_reserved_ip,
url_has_literal_loopback_host,
connection_declared_header_names, is_https_or_loopback_http_url,
is_ipv4_benchmarking_fake_ip, is_private_or_reserved_ip, url_has_literal_loopback_host,
};
#[test]
@@ -151,6 +169,19 @@ mod tests {
));
}
#[test]
fn benchmarking_fake_ip_predicate_is_narrow_and_does_not_change_private_policy() {
for address in ["198.18.0.1", "198.19.255.254"] {
let ip = address.parse().expect("benchmarking address");
assert!(is_ipv4_benchmarking_fake_ip(ip));
assert!(is_private_or_reserved_ip(ip));
}
for address in ["198.17.255.254", "198.20.0.1", "2001:db8::1"] {
let ip = address.parse().expect("non-benchmarking address");
assert!(!is_ipv4_benchmarking_fake_ip(ip));
}
}
#[test]
fn sensitive_http_transport_allows_https_or_literal_loopback_only() {
for allowed in [
+2 -2
View File
@@ -9,8 +9,8 @@ pub use client::{apply_http_client_config, build_http_client, build_http_client_
pub use config::{HttpClientConfig, HttpRetryConfig};
pub use dns::{lookup_host_with_limits, DEFAULT_DNS_LOOKUP_TIMEOUT, MAX_DNS_RESOLVED_ADDRESSES};
pub use header_security::{
connection_declared_header_names, is_https_or_loopback_http_url, is_private_or_reserved_ip,
url_has_literal_loopback_host,
connection_declared_header_names, is_https_or_loopback_http_url, is_ipv4_benchmarking_fake_ip,
is_private_or_reserved_ip, url_has_literal_loopback_host,
};
pub use response_body::{read_response_bytes_with_limit, ResponseBodyReadError};
pub use retry::jittered_delay_for_retry;