stalwartlabs/stalwart · critical

Failed to build DNSSEC resolver

Error message

Failed to build DNSSEC resolver

What it means

This panic occurs during resolver initialization when the DNSSEC-validating hickory (trust-dns) resolver cannot be built from the provided ResolverConfig and options. DnssecResolver::new calls .build().expect(...), so any builder failure aborts startup instead of returning a typed error. It means the DNSSEC resolver could not be constructed at all from the given configuration.

Source

Thrown at crates/common/src/config/smtp/resolver.rs:225

            }
        }

        // We already have a cache, so disable the built-in cache
        opts.cache_size = 0;

        // Prepare DNSSEC resolver options
        let config_dnssec = resolver_config.clone();
        let mut opts_dnssec = opts.clone();
        opts_dnssec.validate = true;

        let dnssec = DnssecResolver {
            resolver: TokioResolver::builder_with_config(
                config_dnssec,
                TokioRuntimeProvider::default(),
            )
            .with_options(opts_dnssec)
            .build()
            .expect("Failed to build DNSSEC resolver"),
        };

        Resolvers {
            #[cfg(not(feature = "test_mode"))]
            dnssec_available: ensure_dnssec(&resolver_config, &dnssec.resolver).await,
            #[cfg(feature = "test_mode")]
            dnssec_available: true,
            dns: MessageAuthenticator::new(resolver_config, opts).unwrap(),
            dnssec,
        }
    }
}

#[cfg(not(feature = "test_mode"))]
async fn ensure_dnssec(config: &ResolverConfig, resolver: &TokioResolver) -> bool {
    config.name_servers().iter().any(|name_server| {
        name_server
            .connections

View on GitHub (pinned to e962003857)

Solutions

  1. Check that the resolver nameserver IPs and ports in the SMTP resolver config are valid (dotted-quad, valid port)
  2. Remove or fix any custom DNSSEC trust-anchor/root-key settings in the configuration
  3. Test with default system resolvers to isolate whether the custom config is the cause
  4. Verify hickory-resolver version compatibility after dependency upgrades
  5. Replace .expect() with error propagation at the call site so the underlying builder error is reported instead of an opaque panic

Example fix

// before
.build()
.expect("Failed to build DNSSEC resolver"),
// after
.build()
.map_err(|e| anyhow!("Failed to build DNSSEC resolver: {e}"))?,
Defensive patterns

Strategy: validation

Validate before calling

// validate resolver config before init
fn validate_resolver_config(cfg: &ResolverConfig) -> Result<(), String> {
    for ns in cfg.name_servers() {
        // skip_addr parses the IP:port of each nameserver
        if ns.addr().is_unspecified() {
            return Err(format!("invalid nameserver address: {}", ns));
        }
    }
    Ok(())
}

Try / catch

// .expect() panics; wrap process startup to surface the message:
match resolvers_init(&config).await {
    Ok(r) => r,
    Err(e) => {
        eprintln!("resolver init failed: {e}");
        std::process::exit(1);
    }
}

Prevention

When it happens

Trigger: Calling DnssecResolver::new (as part of Resolvers init) where TokioResolver::builder_with_config(config_dnssec, TokioRuntimeProvider::default()).with_options(opts_dnssec).build() returns an error — e.g. a ResolverConfig containing unparseable nameserver IPs or invalid DNSSEC options/trust anchors.

Common situations: smtp.resolver config with malformed nameserver IPs or ports; invalid custom DNSSEC trust-anchor/root key settings; hickory-resolver version incompatibility after dependency upgrades; platform/network init failures inside hickory.

Related errors


AI-assisted analysis of stalwartlabs/stalwart@e962003857 (2026-09-06). Data as JSON: /api/errors/876b003b27526e44. Report an issue: GitHub.