@ -14,7 +14,10 @@ use reqwest::{
} ;
use url ::Host ;
use crate ::{ CONFIG , util ::is_global } ;
use crate ::{
CONFIG ,
util ::{ get_env_bool , is_global } ,
} ;
pub fn make_http_request ( method : reqwest ::Method , url : & str ) -> Result < reqwest ::RequestBuilder , crate ::Error > {
static INSTANCE : LazyLock < Client > =
@ -59,6 +62,14 @@ pub fn get_reqwest_client_builder(enforce_block: bool) -> ClientBuilder {
. timeout ( Duration ::from_secs ( 10 ) )
}
fn dns_prefer_ipv6 ( ) -> bool {
// CONFIG may require DNS to initialize, so avoid forcing it during bootstrap.
match LazyLock ::get ( & CONFIG ) {
Some ( config ) = > config . dns_prefer_ipv6 ( ) ,
None = > get_env_bool ( "DNS_PREFER_IPV6" ) . unwrap_or ( false ) ,
}
}
fn should_block_ip ( ip : IpAddr ) -> bool {
if ! CONFIG . http_request_block_non_global_ips ( ) {
return false ;
@ -258,12 +269,8 @@ impl CustomDnsResolver {
fn new ( ) -> Arc < Self > {
TokioResolver ::builder ( TokioRuntimeProvider ::default ( ) )
. and_then ( | mut builder | {
// Hickory's default since v0.26 is `Ipv6AndIpv4`, which sorts IPv6 first
// This might cause issues on IPv4 only systems or containers
// Unless someone enabled DNS_PREFER_IPV6, use Ipv4AndIpv6, which returns IPv4 first which was our previous default
if ! CONFIG . dns_prefer_ipv6 ( ) {
builder . options_mut ( ) . ip_strategy = hickory_resolver ::config ::LookupIpStrategy ::Ipv4AndIpv6 ;
}
// Query both families; the preferred order is applied per lookup below.
builder . options_mut ( ) . ip_strategy = hickory_resolver ::config ::LookupIpStrategy ::Ipv4AndIpv6 ;
builder . build ( )
} )
. inspect_err ( | e | warn ! ( "Error creating Hickory resolver, falling back to default: {e:?}" ) )
@ -289,6 +296,14 @@ impl CustomDnsResolver {
}
}
fn sort_addresses ( addresses : & mut [ SocketAddr ] , prefer_ipv6 : bool ) {
if prefer_ipv6 {
addresses . sort_by_key ( SocketAddr ::is_ipv4 ) ;
} else {
addresses . sort_by_key ( SocketAddr ::is_ipv6 ) ;
}
}
fn pre_resolve ( name : & str , enforce_block : bool ) -> Result < ( ) , CustomHttpClientError > {
let Ok ( host ) = get_valid_host ( name ) else {
return Err ( CustomHttpClientError ::Invalid {
@ -320,7 +335,9 @@ impl Resolve for CustomDns {
let this = Arc ::clone ( & self . resolver ) ;
Box ::pin ( async move {
let name = name . as_str ( ) ;
let results = this . resolve_domain ( name , enforce_block ) . await ? ;
let mut results = this . resolve_domain ( name , enforce_block ) . await ? ;
// Recheck after bootstrap so long-lived clients adopt the loaded config.
sort_addresses ( & mut results , dns_prefer_ipv6 ( ) ) ;
if results . is_empty ( ) {
warn ! ( "Unable to resolve {name} to any valid IP address" ) ;
}
@ -391,7 +408,7 @@ pub(crate) mod aws {
mod tests {
use super ::* ;
use crate ::util ::is_global_hardcoded ;
use std ::net ::Ipv4Addr ;
use std ::net ::{ Ipv4Addr , Ipv6Addr } ;
use url ::Host ;
// ===
@ -404,6 +421,26 @@ mod tests {
}
}
#[ test ]
fn dns_setup_does_not_initialize_config ( ) {
assert ! ( LazyLock ::get ( & CONFIG ) . is_none ( ) ) ;
drop ( CustomDns ::instance ( false ) ) ;
assert ! ( LazyLock ::get ( & CONFIG ) . is_none ( ) ) ;
}
#[ test ]
fn dns_preference_orders_addresses ( ) {
let ipv4 = SocketAddr ::new ( IpAddr ::V4 ( Ipv4Addr ::LOCALHOST ) , 0 ) ;
let ipv6 = SocketAddr ::new ( IpAddr ::V6 ( Ipv6Addr ::LOCALHOST ) , 0 ) ;
let mut addresses = [ ipv6 , ipv4 ] ;
sort_addresses ( & mut addresses , false ) ;
assert_eq ! ( addresses , [ ipv4 , ipv6 ] ) ;
sort_addresses ( & mut addresses , true ) ;
assert_eq ! ( addresses , [ ipv6 , ipv4 ] ) ;
}
#[ test ]
fn dotted_decimal_loopback_normalizes ( ) {
let ip = parse_to_ip ( "127.0.0.1" ) . unwrap ( ) ;