2 Commits
2.0.1 ... 2.0.2

Author SHA1 Message Date
Timothy Miller
560a3b7b28 Bump version to 2.0.2 2026-03-13 00:10:31 -04:00
Timothy Miller
1b3928865b Use literal IP trace URLs as primary
Primary trace endpoints now use literal IPs per address family to
guarantee correct address family selection. Fallback uses
api.cloudflare.com to work around WARP/Zero Trust interception. Rename
constants and update tests accordingly.
2026-03-13 00:04:08 -04:00
3 changed files with 25 additions and 23 deletions

2
Cargo.lock generated
View File

@@ -103,7 +103,7 @@ dependencies = [
[[package]] [[package]]
name = "cloudflare-ddns" name = "cloudflare-ddns"
version = "2.0.1" version = "2.0.2"
dependencies = [ dependencies = [
"chrono", "chrono",
"idna", "idna",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "cloudflare-ddns" name = "cloudflare-ddns"
version = "2.0.1" version = "2.0.2"
edition = "2021" edition = "2021"
description = "Access your home network remotely via a custom domain name without a static IP" description = "Access your home network remotely via a custom domain name without a static IP"
license = "GPL-3.0" license = "GPL-3.0"

View File

@@ -145,12 +145,15 @@ impl ProviderType {
// --- Cloudflare Trace --- // --- Cloudflare Trace ---
/// Primary trace URL uses a hostname so DNS resolves normally, avoiding the /// Primary trace URLs use literal IPs to guarantee the correct address family.
/// problem where WARP/Zero Trust intercepts requests to literal 1.1.1.1. /// api.cloudflare.com is dual-stack, so on dual-stack hosts (e.g. Docker
const CF_TRACE_PRIMARY: &str = "https://api.cloudflare.com/cdn-cgi/trace"; /// --net=host with IPv6) the connection may go via IPv6 even when detecting
/// Fallback URLs use literal IPs for when api.cloudflare.com is unreachable. /// IPv4, causing the trace endpoint to return the wrong address family.
const CF_TRACE_V4_FALLBACK: &str = "https://1.0.0.1/cdn-cgi/trace"; const CF_TRACE_V4_PRIMARY: &str = "https://1.0.0.1/cdn-cgi/trace";
const CF_TRACE_V6_FALLBACK: &str = "https://[2606:4700:4700::1001]/cdn-cgi/trace"; const CF_TRACE_V6_PRIMARY: &str = "https://[2606:4700:4700::1001]/cdn-cgi/trace";
/// Fallback uses a hostname, which works when literal IPs are intercepted
/// (e.g. Cloudflare WARP/Zero Trust).
const CF_TRACE_FALLBACK: &str = "https://api.cloudflare.com/cdn-cgi/trace";
pub fn parse_trace_ip(body: &str) -> Option<String> { pub fn parse_trace_ip(body: &str) -> Option<String> {
for line in body.lines() { for line in body.lines() {
@@ -211,13 +214,13 @@ async fn detect_cloudflare_trace(
return Vec::new(); return Vec::new();
} }
let fallback = match ip_type { let primary = match ip_type {
IpType::V4 => CF_TRACE_V4_FALLBACK, IpType::V4 => CF_TRACE_V4_PRIMARY,
IpType::V6 => CF_TRACE_V6_FALLBACK, IpType::V6 => CF_TRACE_V6_PRIMARY,
}; };
// Try primary (api.cloudflare.com — resolves via DNS, avoids literal-IP interception) // Try primary (literal IP — guarantees correct address family)
if let Some(ip) = fetch_trace_ip(&client, CF_TRACE_PRIMARY, timeout).await { if let Some(ip) = fetch_trace_ip(&client, primary, timeout).await {
if validate_detected_ip(&ip, ip_type, ppfmt) { if validate_detected_ip(&ip, ip_type, ppfmt) {
return vec![ip]; return vec![ip];
} }
@@ -227,8 +230,8 @@ async fn detect_cloudflare_trace(
&format!("{} not detected via primary, trying fallback", ip_type.describe()), &format!("{} not detected via primary, trying fallback", ip_type.describe()),
); );
// Try fallback (literal IP — useful when DNS is broken) // Try fallback (hostname-based — works when literal IPs are intercepted by WARP/Zero Trust)
if let Some(ip) = fetch_trace_ip(&client, fallback, timeout).await { if let Some(ip) = fetch_trace_ip(&client, CF_TRACE_FALLBACK, timeout).await {
if validate_detected_ip(&ip, ip_type, ppfmt) { if validate_detected_ip(&ip, ip_type, ppfmt) {
return vec![ip]; return vec![ip];
} }
@@ -918,14 +921,13 @@ mod tests {
// ---- trace URL constants ---- // ---- trace URL constants ----
#[test] #[test]
fn test_trace_primary_uses_hostname_not_ip() { fn test_trace_urls() {
// Primary must use a hostname (api.cloudflare.com) so DNS resolves normally // Primary URLs use literal IPs to guarantee correct address family.
// and WARP/Zero Trust doesn't intercept the request. assert!(CF_TRACE_V4_PRIMARY.contains("1.0.0.1"));
assert_eq!(CF_TRACE_PRIMARY, "https://api.cloudflare.com/cdn-cgi/trace"); assert!(CF_TRACE_V6_PRIMARY.contains("2606:4700:4700::1001"));
assert!(CF_TRACE_PRIMARY.contains("api.cloudflare.com")); // Fallback uses a hostname for when literal IPs are intercepted (WARP/Zero Trust).
// Fallbacks use literal IPs for when DNS is broken. assert_eq!(CF_TRACE_FALLBACK, "https://api.cloudflare.com/cdn-cgi/trace");
assert!(CF_TRACE_V4_FALLBACK.contains("1.0.0.1")); assert!(CF_TRACE_FALLBACK.contains("api.cloudflare.com"));
assert!(CF_TRACE_V6_FALLBACK.contains("2606:4700:4700::1001"));
} }
// ---- build_split_client ---- // ---- build_split_client ----