Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

- To prevent false positives, non-public email addresses (e.g. `user@localhost`) are no longer scrubbed by default. ([#5737](https://github.com/getsentry/relay/pull/5737))

**Features**:

- Separate NXDOMAIN DNS cache. ([#5750](https://github.com/getsentry/relay/pull/5750))

**Internal**:

- Calculate and track accepted bytes per individual trace metric item via `TraceMetricByte` data category. ([#5744](https://github.com/getsentry/relay/pull/5744))
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4776,6 +4776,7 @@ dependencies = [
"flate2",
"futures",
"hashbrown 0.15.4",
"hickory-resolver",
"http",
"http-body-util",
"hyper",
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ globset = "0.4.16"
hash32 = "1.0.0"
hashbrown = "0.15.4"
hex = "0.4.3"
hickory-resolver = "0.25.2"
hmac = "0.12.1"
hostname = "0.4.1"
http = "1.3.1"
Expand Down
11 changes: 11 additions & 0 deletions relay-config/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,10 @@ pub struct Http {
/// the resolved entries. This helps to limit the amount of requests made to the upstream DNS
/// server (important for K8s infrastructure).
pub dns_cache: bool,
/// When `dns_cache` is enabled, controls whether NXDOMAIN responses are cached.
/// Set to `false` to always re-query for failed lookups.
/// Only has an effect when `dns_cache` is `true`.
pub dns_cache_nxdomain: bool,
}

impl Default for Http {
Expand All @@ -904,6 +908,7 @@ impl Default for Http {
global_metrics: false,
forward: true,
dns_cache: true,
dns_cache_nxdomain: true,
}
}
}
Expand Down Expand Up @@ -2225,6 +2230,12 @@ impl Config {
self.values.http.dns_cache
}

/// Returns `true` if relay should cache negative DNS responses (NXDOMAIN).
/// Only meaningful when `http_dns_cache()` returns `true`.
pub fn http_dns_cache_nxdomain(&self) -> bool {
self.values.http.dns_cache_nxdomain
}

/// Returns the expiry timeout for cached projects.
pub fn project_cache_expiry(&self) -> Duration {
Duration::from_secs(self.values.cache.project_expiry.into())
Expand Down
1 change: 1 addition & 0 deletions relay-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ either = { workspace = true }
flate2 = { workspace = true }
futures = { workspace = true, features = ["async-await"] }
hashbrown = { workspace = true }
hickory-resolver = { workspace = true }
http = { workspace = true }
http-body-util = { workspace = true }
hyper = { workspace = true }
Expand Down
51 changes: 46 additions & 5 deletions relay-server/src/services/upstream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@ use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::net::SocketAddr;
use std::time::Duration;

use axum::response::IntoResponse;
use bytes::Bytes;
use hickory_resolver::config::LookupIpStrategy;
use hickory_resolver::system_conf::read_system_conf;
use hickory_resolver::TokioResolver;
use hickory_resolver::name_server::TokioConnectionProvider;
use itertools::Itertools;
use relay_auth::{
RegisterChallenge, RegisterRequest, RegisterResponse, Registration, SecretKey, Signature,
Expand All @@ -29,6 +34,7 @@ use relay_system::{
AsyncResponse, FromMessage, Interface, MessageResponse, NoResponse, Sender, Service,
};
pub use reqwest::Method;
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
use reqwest::header;
use serde::Serialize;
use serde::de::DeserializeOwned;
Expand Down Expand Up @@ -841,6 +847,24 @@ impl UpstreamQuery for RegisterResponse {
}
}

/// A custom DNS resolver backed by hickory, allowing fine-grained control over
/// resolver options (e.g. NXDOMAIN TTL) that reqwest's `.hickory_dns()` does not expose.
struct HickoryResolver(TokioResolver);

impl Resolve for HickoryResolver {
fn resolve(&self, name: Name) -> Resolving {
let resolver = self.0.clone(); // cheap, TokioResolver is Arc-backed
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The error handling for read_system_conf() uses .unwrap_or_default(), which falls back to the default resolver, contradicting the explicit goal of avoiding it.
Severity: MEDIUM

Suggested Fix

Instead of using .unwrap_or_default(), handle the potential error from read_system_conf() explicitly. Either log an error when it fails, propagate the error up the call stack, or use a fallback strategy that aligns with the intent to use system resolvers.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: relay-server/src/services/upstream.rs#L856

Potential issue: The code uses `read_system_conf().unwrap_or_default()`. If
`read_system_conf()` fails to read the system's DNS configuration, for instance in
certain containerized or isolated environments, the code will silently fall back to
`ResolverConfig::default()`. This behavior contradicts the stated intent in the commit
history, which was to explicitly avoid using the default resolver. This can lead to the
Relay silently using public DNS servers instead of the intended system resolvers,
causing DNS resolution issues that are difficult to debug.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I know, it's not good, but this is the default behvaiour of reqwest and it was doing the same thing, so I thought this should not get changed in this PR.

Box::pin(async move {
let addrs = resolver
.lookup_ip(name.as_str())
.await?
.into_iter()
.map(|ip| SocketAddr::new(ip, 0));
Ok(Box::new(addrs) as Addrs)
})
}
}

/// A shared, asynchronous client to build and execute requests.
///
/// The main way to send a request through this client is [`send`](Self::send).
Expand All @@ -856,16 +880,33 @@ struct SharedClient {
impl SharedClient {
/// Creates a new `SharedClient` instance.
pub fn build(config: Arc<Config>) -> Self {
let reqwest = reqwest::ClientBuilder::new()
let mut builder = reqwest::ClientBuilder::new()
.connect_timeout(config.http_connection_timeout())
.timeout(config.http_timeout())
// In the forward endpoint, this means that content negotiation is done twice, and the
// response body is first decompressed by the client, then re-compressed by the server.
.gzip(true)
.hickory_dns(config.http_dns_cache())
.build()
.unwrap();
.gzip(true);

if config.http_dns_cache() {
let (system_config, mut system_opts) = read_system_conf().unwrap_or_default();
// Match reqwest's built-in hickory behaviour which uses Ipv4AndIpv6 (parallel,
// "happy eyeballs") instead of the hickory default Ipv4thenIpv6 (sequential).
system_opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
if !config.http_dns_cache_nxdomain() {
system_opts.negative_max_ttl = Some(Duration::ZERO);
}
let resolver = TokioResolver::builder_with_config(system_config, TokioConnectionProvider::default())
.with_options(system_opts)
.build();
builder = builder.dns_resolver(Arc::new(HickoryResolver(resolver)));
Comment thread
aminvakil marked this conversation as resolved.
} else {
// Explicitly disable hickory so reqwest falls back to the system resolver.
// Without this, reqwest defaults to hickory when the feature is compiled in,
// ignoring the user's opt-out.
builder = builder.hickory_dns(false);
}
Comment thread
aminvakil marked this conversation as resolved.

let reqwest = builder.build().unwrap();
Self { config, reqwest }
}

Expand Down