feat(push): allow listed hosts to resolve to private addresses (#3228)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-05 20:11:08 +02:00
1 parent d456048e69
commit 2006fc0d8d
8 files changed
+103 -22

No files matched your search

+34 -7
View File
@@ -2,7 +2,7 @@
use crate::config::{ApnsConfig, FcmConfig, ProviderEnvironment};
use crate::metrics::Metrics;
use crate::resolver::PublicOnlyResolver;
use crate::resolver::{BLOCKED_ADDRESS_ERROR, PublicOnlyResolver};
use crate::tokens::{TokenCache, TokenError};
use reqwest::header::{AUTHORIZATION, CONTENT_TYPE};
use reqwest::redirect::Policy;
@@ -30,10 +30,10 @@ pub fn http_client() -> reqwest::Result<reqwest::Client> {
.build()
}
pub fn web_push_http_client() -> reqwest::Result<reqwest::Client> {
pub fn web_push_http_client(private_hosts: &[String]) -> reqwest::Result<reqwest::Client> {
reqwest::Client::builder()
.redirect(Policy::none())
.dns_resolver(PublicOnlyResolver)
.dns_resolver(PublicOnlyResolver::new(private_hosts))
.connect_timeout(CONNECT_TIMEOUT)
.timeout(HTTP_TIMEOUT)
.build()
@@ -65,6 +65,7 @@ pub enum VendorOutcome {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Unreachable {
BlockedAddress,
Dns,
Transport,
}
@@ -72,17 +73,20 @@ pub enum Unreachable {
impl Unreachable {
pub fn label(self) -> &'static str {
match self {
Self::BlockedAddress => "blocked_address",
Self::Dns => "dns",
Self::Transport => "transport",
}
}
pub fn is_permanent(self) -> bool {
matches!(self, Self::Dns)
matches!(self, Self::BlockedAddress | Self::Dns)
}
pub fn of(error: &reqwest::Error) -> Self {
if names_no_host(error) {
if source_mentions(error, BLOCKED_ADDRESS_ERROR) {
Self::BlockedAddress
} else if source_mentions(error, DNS_ERROR_MARKER) {
Self::Dns
} else {
Self::Transport
@@ -90,10 +94,10 @@ impl Unreachable {
}
}
fn names_no_host(error: &reqwest::Error) -> bool {
fn source_mentions(error: &reqwest::Error, marker: &str) -> bool {
let mut current = std::error::Error::source(error);
while let Some(error) = current {
if error.to_string().contains(DNS_ERROR_MARKER) {
if error.to_string().contains(marker) {
return true;
}
current = error.source();
@@ -320,6 +324,29 @@ mod tests {
);
}
#[tokio::test]
async fn a_host_in_blocked_address_space_is_named_as_such() {
let error = web_push_http_client(&[])
.expect("the http client builds")
.post("https://localhost/push")
.send()
.await
.expect_err("the request cannot complete");
assert_eq!(Unreachable::of(&error), Unreachable::BlockedAddress);
assert!(Unreachable::of(&error).is_permanent());
}
#[tokio::test]
async fn a_listed_private_host_gets_through_to_connect() {
let error = web_push_http_client(&["localhost".to_owned()])
.expect("the http client builds")
.post("https://localhost:1/push")
.send()
.await
.expect_err("the request cannot complete");
assert_eq!(Unreachable::of(&error), Unreachable::Transport);
}
#[tokio::test]
async fn a_refused_connection_stays_retryable() {
let error = error_for("http://127.0.0.1:1/").await;