mirror of
https://github.com/timothymiller/cloudflare-ddns.git
synced 2026-03-21 22:48:57 -03:00
Compare commits
1 Commits
e7772c0fe0
...
2.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93d351d997 |
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -103,7 +103,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cloudflare-ddns"
|
name = "cloudflare-ddns"
|
||||||
version = "2.0.0"
|
version = "2.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"idna",
|
"idna",
|
||||||
@@ -1135,9 +1135,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.26.0"
|
version = "3.27.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0"
|
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cloudflare-ddns"
|
name = "cloudflare-ddns"
|
||||||
version = "2.0.0"
|
version = "2.0.1"
|
||||||
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"
|
||||||
@@ -24,5 +24,5 @@ strip = true
|
|||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.26.0"
|
tempfile = "3.27.0"
|
||||||
wiremock = "0.6"
|
wiremock = "0.6"
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ fn read_providers_from_env(ppfmt: &PP) -> Result<HashMap<IpType, ProviderType>,
|
|||||||
let ip4_provider = match ip4_str {
|
let ip4_provider = match ip4_str {
|
||||||
Some(s) => ProviderType::parse(&s)
|
Some(s) => ProviderType::parse(&s)
|
||||||
.map_err(|e| format!("Invalid IP4_PROVIDER: {e}"))?,
|
.map_err(|e| format!("Invalid IP4_PROVIDER: {e}"))?,
|
||||||
None => ProviderType::Ipify,
|
None => ProviderType::CloudflareTrace { url: None },
|
||||||
};
|
};
|
||||||
|
|
||||||
let ip6_provider = match ip6_str {
|
let ip6_provider = match ip6_str {
|
||||||
@@ -1429,12 +1429,12 @@ mod tests {
|
|||||||
let pp = PP::new(false, true);
|
let pp = PP::new(false, true);
|
||||||
let providers = read_providers_from_env(&pp).unwrap();
|
let providers = read_providers_from_env(&pp).unwrap();
|
||||||
drop(g);
|
drop(g);
|
||||||
// V4 defaults to Ipify, V6 defaults to CloudflareTrace.
|
// Both V4 and V6 default to CloudflareTrace.
|
||||||
assert!(providers.contains_key(&IpType::V4));
|
assert!(providers.contains_key(&IpType::V4));
|
||||||
assert!(providers.contains_key(&IpType::V6));
|
assert!(providers.contains_key(&IpType::V6));
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
providers[&IpType::V4],
|
providers[&IpType::V4],
|
||||||
ProviderType::Ipify
|
ProviderType::CloudflareTrace { url: None }
|
||||||
));
|
));
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
providers[&IpType::V6],
|
providers[&IpType::V6],
|
||||||
|
|||||||
191
src/provider.rs
191
src/provider.rs
@@ -145,9 +145,11 @@ impl ProviderType {
|
|||||||
|
|
||||||
// --- Cloudflare Trace ---
|
// --- Cloudflare Trace ---
|
||||||
|
|
||||||
const CF_TRACE_V4_PRIMARY: &str = "https://1.1.1.1/cdn-cgi/trace";
|
/// Primary trace URL uses a hostname so DNS resolves normally, avoiding the
|
||||||
|
/// problem where WARP/Zero Trust intercepts requests to literal 1.1.1.1.
|
||||||
|
const CF_TRACE_PRIMARY: &str = "https://api.cloudflare.com/cdn-cgi/trace";
|
||||||
|
/// Fallback URLs use literal IPs for when api.cloudflare.com is unreachable.
|
||||||
const CF_TRACE_V4_FALLBACK: &str = "https://1.0.0.1/cdn-cgi/trace";
|
const CF_TRACE_V4_FALLBACK: &str = "https://1.0.0.1/cdn-cgi/trace";
|
||||||
const CF_TRACE_V6_PRIMARY: &str = "https://[2606:4700:4700::1111]/cdn-cgi/trace";
|
|
||||||
const CF_TRACE_V6_FALLBACK: &str = "https://[2606:4700:4700::1001]/cdn-cgi/trace";
|
const CF_TRACE_V6_FALLBACK: &str = "https://[2606:4700:4700::1001]/cdn-cgi/trace";
|
||||||
|
|
||||||
pub fn parse_trace_ip(body: &str) -> Option<String> {
|
pub fn parse_trace_ip(body: &str) -> Option<String> {
|
||||||
@@ -171,16 +173,34 @@ async fn fetch_trace_ip(client: &Client, url: &str, timeout: Duration) -> Option
|
|||||||
ip_str.parse::<IpAddr>().ok()
|
ip_str.parse::<IpAddr>().ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build an HTTP client that only connects via the given IP family.
|
||||||
|
/// Binding to 0.0.0.0 forces IPv4-only; binding to [::] forces IPv6-only.
|
||||||
|
/// This ensures the trace endpoint sees the correct address family.
|
||||||
|
fn build_split_client(ip_type: IpType, timeout: Duration) -> Client {
|
||||||
|
let local_addr: IpAddr = match ip_type {
|
||||||
|
IpType::V4 => Ipv4Addr::UNSPECIFIED.into(),
|
||||||
|
IpType::V6 => Ipv6Addr::UNSPECIFIED.into(),
|
||||||
|
};
|
||||||
|
Client::builder()
|
||||||
|
.local_address(local_addr)
|
||||||
|
.timeout(timeout)
|
||||||
|
.build()
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
async fn detect_cloudflare_trace(
|
async fn detect_cloudflare_trace(
|
||||||
client: &Client,
|
_client: &Client,
|
||||||
ip_type: IpType,
|
ip_type: IpType,
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
custom_url: Option<&str>,
|
custom_url: Option<&str>,
|
||||||
ppfmt: &PP,
|
ppfmt: &PP,
|
||||||
) -> Vec<IpAddr> {
|
) -> Vec<IpAddr> {
|
||||||
|
// Use an IP-family-specific client so the trace endpoint sees the right address family.
|
||||||
|
let client = build_split_client(ip_type, timeout);
|
||||||
|
|
||||||
if let Some(url) = custom_url {
|
if let Some(url) = custom_url {
|
||||||
if let Some(ip) = fetch_trace_ip(client, url, timeout).await {
|
if let Some(ip) = fetch_trace_ip(&client, url, timeout).await {
|
||||||
if matches_ip_type(&ip, ip_type) {
|
if validate_detected_ip(&ip, ip_type, ppfmt) {
|
||||||
return vec![ip];
|
return vec![ip];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -191,14 +211,14 @@ async fn detect_cloudflare_trace(
|
|||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
let (primary, fallback) = match ip_type {
|
let fallback = match ip_type {
|
||||||
IpType::V4 => (CF_TRACE_V4_PRIMARY, CF_TRACE_V4_FALLBACK),
|
IpType::V4 => CF_TRACE_V4_FALLBACK,
|
||||||
IpType::V6 => (CF_TRACE_V6_PRIMARY, CF_TRACE_V6_FALLBACK),
|
IpType::V6 => CF_TRACE_V6_FALLBACK,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Try primary
|
// Try primary (api.cloudflare.com — resolves via DNS, avoids literal-IP interception)
|
||||||
if let Some(ip) = fetch_trace_ip(client, primary, timeout).await {
|
if let Some(ip) = fetch_trace_ip(&client, CF_TRACE_PRIMARY, timeout).await {
|
||||||
if matches_ip_type(&ip, ip_type) {
|
if validate_detected_ip(&ip, ip_type, ppfmt) {
|
||||||
return vec![ip];
|
return vec![ip];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,9 +227,9 @@ 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
|
// Try fallback (literal IP — useful when DNS is broken)
|
||||||
if let Some(ip) = fetch_trace_ip(client, fallback, timeout).await {
|
if let Some(ip) = fetch_trace_ip(&client, fallback, timeout).await {
|
||||||
if matches_ip_type(&ip, ip_type) {
|
if validate_detected_ip(&ip, ip_type, ppfmt) {
|
||||||
return vec![ip];
|
return vec![ip];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -249,7 +269,7 @@ async fn detect_cloudflare_doh(
|
|||||||
if let Ok(body) = r.bytes().await {
|
if let Ok(body) = r.bytes().await {
|
||||||
if let Some(ip_str) = parse_dns_txt_response(&body) {
|
if let Some(ip_str) = parse_dns_txt_response(&body) {
|
||||||
if let Ok(ip) = ip_str.parse::<IpAddr>() {
|
if let Ok(ip) = ip_str.parse::<IpAddr>() {
|
||||||
if matches_ip_type(&ip, ip_type) {
|
if validate_detected_ip(&ip, ip_type, ppfmt) {
|
||||||
return vec![ip];
|
return vec![ip];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -379,7 +399,7 @@ async fn detect_ipify(
|
|||||||
if let Ok(body) = resp.text().await {
|
if let Ok(body) = resp.text().await {
|
||||||
let ip_str = body.trim();
|
let ip_str = body.trim();
|
||||||
if let Ok(ip) = ip_str.parse::<IpAddr>() {
|
if let Ok(ip) = ip_str.parse::<IpAddr>() {
|
||||||
if matches_ip_type(&ip, ip_type) {
|
if validate_detected_ip(&ip, ip_type, ppfmt) {
|
||||||
return vec![ip];
|
return vec![ip];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -491,7 +511,7 @@ async fn detect_custom_url(
|
|||||||
if let Ok(body) = resp.text().await {
|
if let Ok(body) = resp.text().await {
|
||||||
let ip_str = body.trim();
|
let ip_str = body.trim();
|
||||||
if let Ok(ip) = ip_str.parse::<IpAddr>() {
|
if let Ok(ip) = ip_str.parse::<IpAddr>() {
|
||||||
if matches_ip_type(&ip, ip_type) {
|
if validate_detected_ip(&ip, ip_type, ppfmt) {
|
||||||
return vec![ip];
|
return vec![ip];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -516,6 +536,34 @@ fn matches_ip_type(ip: &IpAddr, ip_type: IpType) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validate a detected IP: must match the requested address family and be a
|
||||||
|
/// global unicast address. Mirrors the checks in favonia/cloudflare-ddns's
|
||||||
|
/// NormalizeDetectedIPs — rejects loopback, link-local, multicast,
|
||||||
|
/// unspecified, and non-global addresses.
|
||||||
|
fn validate_detected_ip(ip: &IpAddr, ip_type: IpType, ppfmt: &PP) -> bool {
|
||||||
|
if !matches_ip_type(ip, ip_type) {
|
||||||
|
ppfmt.warningf(
|
||||||
|
pp::EMOJI_WARNING,
|
||||||
|
&format!(
|
||||||
|
"Detected IP {} does not match expected type {}",
|
||||||
|
ip, ip_type.describe()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if !ip.is_global_() {
|
||||||
|
ppfmt.warningf(
|
||||||
|
pp::EMOJI_WARNING,
|
||||||
|
&format!(
|
||||||
|
"Detected {} address {} is not a global unicast address",
|
||||||
|
ip_type.describe(), ip
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn filter_ips_by_type(ips: &[IpAddr], ip_type: IpType) -> Vec<IpAddr> {
|
fn filter_ips_by_type(ips: &[IpAddr], ip_type: IpType) -> Vec<IpAddr> {
|
||||||
ips.iter()
|
ips.iter()
|
||||||
.copied()
|
.copied()
|
||||||
@@ -867,6 +915,35 @@ mod tests {
|
|||||||
assert_eq!(result_ok[0], "93.184.216.34".parse::<IpAddr>().unwrap());
|
assert_eq!(result_ok[0], "93.184.216.34".parse::<IpAddr>().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- trace URL constants ----
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trace_primary_uses_hostname_not_ip() {
|
||||||
|
// Primary must use a hostname (api.cloudflare.com) so DNS resolves normally
|
||||||
|
// and WARP/Zero Trust doesn't intercept the request.
|
||||||
|
assert_eq!(CF_TRACE_PRIMARY, "https://api.cloudflare.com/cdn-cgi/trace");
|
||||||
|
assert!(CF_TRACE_PRIMARY.contains("api.cloudflare.com"));
|
||||||
|
// Fallbacks use literal IPs for when DNS is broken.
|
||||||
|
assert!(CF_TRACE_V4_FALLBACK.contains("1.0.0.1"));
|
||||||
|
assert!(CF_TRACE_V6_FALLBACK.contains("2606:4700:4700::1001"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- build_split_client ----
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_build_split_client_v4() {
|
||||||
|
let client = build_split_client(IpType::V4, Duration::from_secs(5));
|
||||||
|
// Client should build successfully — we can't inspect local_address,
|
||||||
|
// but we verify it doesn't panic.
|
||||||
|
drop(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_build_split_client_v6() {
|
||||||
|
let client = build_split_client(IpType::V6, Duration::from_secs(5));
|
||||||
|
drop(client);
|
||||||
|
}
|
||||||
|
|
||||||
// ---- detect_ipify with wiremock ----
|
// ---- detect_ipify with wiremock ----
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -875,7 +952,7 @@ mod tests {
|
|||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/"))
|
.and(path("/"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_string("198.51.100.1\n"))
|
.respond_with(ResponseTemplate::new(200).set_body_string("93.184.216.34\n"))
|
||||||
.mount(&server)
|
.mount(&server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -887,7 +964,7 @@ mod tests {
|
|||||||
// which uses the same logic
|
// which uses the same logic
|
||||||
let result = detect_custom_url(&client, &server.uri(), IpType::V4, timeout, &ppfmt).await;
|
let result = detect_custom_url(&client, &server.uri(), IpType::V4, timeout, &ppfmt).await;
|
||||||
assert_eq!(result.len(), 1);
|
assert_eq!(result.len(), 1);
|
||||||
assert_eq!(result[0], "198.51.100.1".parse::<IpAddr>().unwrap());
|
assert_eq!(result[0], "93.184.216.34".parse::<IpAddr>().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -897,7 +974,7 @@ mod tests {
|
|||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/"))
|
.and(path("/"))
|
||||||
.respond_with(
|
.respond_with(
|
||||||
ResponseTemplate::new(200).set_body_string("2001:db8::1\n"),
|
ResponseTemplate::new(200).set_body_string("2606:4700:4700::1111\n"),
|
||||||
)
|
)
|
||||||
.mount(&server)
|
.mount(&server)
|
||||||
.await;
|
.await;
|
||||||
@@ -908,7 +985,7 @@ mod tests {
|
|||||||
|
|
||||||
let result = detect_custom_url(&client, &server.uri(), IpType::V6, timeout, &ppfmt).await;
|
let result = detect_custom_url(&client, &server.uri(), IpType::V6, timeout, &ppfmt).await;
|
||||||
assert_eq!(result.len(), 1);
|
assert_eq!(result.len(), 1);
|
||||||
assert_eq!(result[0], "2001:db8::1".parse::<IpAddr>().unwrap());
|
assert_eq!(result[0], "2606:4700:4700::1111".parse::<IpAddr>().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- detect_custom_url with wiremock ----
|
// ---- detect_custom_url with wiremock ----
|
||||||
@@ -919,7 +996,7 @@ mod tests {
|
|||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/my-ip"))
|
.and(path("/my-ip"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_string("10.0.0.1"))
|
.respond_with(ResponseTemplate::new(200).set_body_string("93.184.216.34"))
|
||||||
.mount(&server)
|
.mount(&server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -928,16 +1005,79 @@ mod tests {
|
|||||||
let timeout = Duration::from_secs(5);
|
let timeout = Duration::from_secs(5);
|
||||||
let url = format!("{}/my-ip", server.uri());
|
let url = format!("{}/my-ip", server.uri());
|
||||||
|
|
||||||
// 10.0.0.1 is a valid IPv4, should match V4
|
|
||||||
let result = detect_custom_url(&client, &url, IpType::V4, timeout, &ppfmt).await;
|
let result = detect_custom_url(&client, &url, IpType::V4, timeout, &ppfmt).await;
|
||||||
assert_eq!(result.len(), 1);
|
assert_eq!(result.len(), 1);
|
||||||
assert_eq!(result[0], "10.0.0.1".parse::<IpAddr>().unwrap());
|
assert_eq!(result[0], "93.184.216.34".parse::<IpAddr>().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_detect_custom_url_wrong_ip_type() {
|
async fn test_detect_custom_url_wrong_ip_type() {
|
||||||
let server = MockServer::start().await;
|
let server = MockServer::start().await;
|
||||||
|
|
||||||
|
Mock::given(method("GET"))
|
||||||
|
.and(path("/my-ip"))
|
||||||
|
.respond_with(ResponseTemplate::new(200).set_body_string("93.184.216.34"))
|
||||||
|
.mount(&server)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let client = Client::new();
|
||||||
|
let ppfmt = PP::default_pp();
|
||||||
|
let timeout = Duration::from_secs(5);
|
||||||
|
let url = format!("{}/my-ip", server.uri());
|
||||||
|
|
||||||
|
// 93.184.216.34 is IPv4 but we ask for V6 -> empty
|
||||||
|
let result = detect_custom_url(&client, &url, IpType::V6, timeout, &ppfmt).await;
|
||||||
|
assert!(result.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- validate_detected_ip ----
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validate_detected_ip_accepts_global() {
|
||||||
|
let ppfmt = PP::default_pp();
|
||||||
|
assert!(validate_detected_ip(&"93.184.216.34".parse().unwrap(), IpType::V4, &ppfmt));
|
||||||
|
assert!(validate_detected_ip(&"2606:4700:4700::1111".parse().unwrap(), IpType::V6, &ppfmt));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validate_detected_ip_rejects_wrong_family() {
|
||||||
|
let ppfmt = PP::default_pp();
|
||||||
|
assert!(!validate_detected_ip(&"93.184.216.34".parse().unwrap(), IpType::V6, &ppfmt));
|
||||||
|
assert!(!validate_detected_ip(&"2606:4700:4700::1111".parse().unwrap(), IpType::V4, &ppfmt));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validate_detected_ip_rejects_private() {
|
||||||
|
let ppfmt = PP::default_pp();
|
||||||
|
assert!(!validate_detected_ip(&"10.0.0.1".parse().unwrap(), IpType::V4, &ppfmt));
|
||||||
|
assert!(!validate_detected_ip(&"192.168.1.1".parse().unwrap(), IpType::V4, &ppfmt));
|
||||||
|
assert!(!validate_detected_ip(&"172.16.0.1".parse().unwrap(), IpType::V4, &ppfmt));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validate_detected_ip_rejects_loopback() {
|
||||||
|
let ppfmt = PP::default_pp();
|
||||||
|
assert!(!validate_detected_ip(&"127.0.0.1".parse().unwrap(), IpType::V4, &ppfmt));
|
||||||
|
assert!(!validate_detected_ip(&"::1".parse().unwrap(), IpType::V6, &ppfmt));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validate_detected_ip_rejects_link_local() {
|
||||||
|
let ppfmt = PP::default_pp();
|
||||||
|
assert!(!validate_detected_ip(&"169.254.0.1".parse().unwrap(), IpType::V4, &ppfmt));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validate_detected_ip_rejects_documentation() {
|
||||||
|
let ppfmt = PP::default_pp();
|
||||||
|
assert!(!validate_detected_ip(&"198.51.100.1".parse().unwrap(), IpType::V4, &ppfmt));
|
||||||
|
assert!(!validate_detected_ip(&"203.0.113.1".parse().unwrap(), IpType::V4, &ppfmt));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_detect_custom_url_rejects_private_ip() {
|
||||||
|
let server = MockServer::start().await;
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/my-ip"))
|
.and(path("/my-ip"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_string("10.0.0.1"))
|
.respond_with(ResponseTemplate::new(200).set_body_string("10.0.0.1"))
|
||||||
@@ -949,8 +1089,7 @@ mod tests {
|
|||||||
let timeout = Duration::from_secs(5);
|
let timeout = Duration::from_secs(5);
|
||||||
let url = format!("{}/my-ip", server.uri());
|
let url = format!("{}/my-ip", server.uri());
|
||||||
|
|
||||||
// 10.0.0.1 is IPv4 but we ask for V6 -> empty
|
let result = detect_custom_url(&client, &url, IpType::V4, timeout, &ppfmt).await;
|
||||||
let result = detect_custom_url(&client, &url, IpType::V6, timeout, &ppfmt).await;
|
|
||||||
assert!(result.is_empty());
|
assert!(result.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -191,11 +191,11 @@ async fn update_legacy(config: &AppConfig, _ppfmt: &PP) -> bool {
|
|||||||
client,
|
client,
|
||||||
cf_api_base: "https://api.cloudflare.com/client/v4".to_string(),
|
cf_api_base: "https://api.cloudflare.com/client/v4".to_string(),
|
||||||
ipv4_urls: vec![
|
ipv4_urls: vec![
|
||||||
"https://1.1.1.1/cdn-cgi/trace".to_string(),
|
"https://api.cloudflare.com/cdn-cgi/trace".to_string(),
|
||||||
"https://1.0.0.1/cdn-cgi/trace".to_string(),
|
"https://1.0.0.1/cdn-cgi/trace".to_string(),
|
||||||
],
|
],
|
||||||
ipv6_urls: vec![
|
ipv6_urls: vec![
|
||||||
"https://[2606:4700:4700::1111]/cdn-cgi/trace".to_string(),
|
"https://api.cloudflare.com/cdn-cgi/trace".to_string(),
|
||||||
"https://[2606:4700:4700::1001]/cdn-cgi/trace".to_string(),
|
"https://[2606:4700:4700::1001]/cdn-cgi/trace".to_string(),
|
||||||
],
|
],
|
||||||
dry_run: config.dry_run,
|
dry_run: config.dry_run,
|
||||||
|
|||||||
Reference in New Issue
Block a user