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.

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.

Appendix: 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)