Skip to content

Conversation

@lidel
Copy link
Member

@lidel lidel commented Jan 12, 2026

p2p-forge hostnames encode IP addresses directly (e.g., 1-2-3-4.peerID.libp2p.direct -> 1.2.3.4), so DNS queries are wasteful. kubo now parses these IPs in-memory.

cc @aschmahmann as this aims to address #11136

This PR

Notes on DNS wiring

Using DNS.Resolvers for go-libp2p too

iiuc the dependency injection chain is:

  1. core/node/dns.go:13 - DNSResolver() creates *madns.Resolver with p2p-forge support
  2. core/node/groups.go:357,375 - fx.Provide(DNSResolver) makes it available in both Online and Offline modes
  3. core/node/groups.go:32 - BaseLibP2P includes fx.Provide(libp2p.MultiaddrResolver)
  4. core/node/libp2p/dns.go:9-11 - MultiaddrResolver takes *madns.Resolver as a dependency and wires it into go-libp2p:
    func MultiaddrResolver(rslv *madns.Resolver) (opts Libp2pOpts, err error) {
        opts.Opts = append(opts.Opts, libp2p.MultiaddrResolver(swarm.ResolverFromMaDNS{Resolver: rslv}))
        return opts, nil
    }

The fx dependency injection automatically connects these - when MultiaddrResolver needs a *madns.Resolver, fx provides the one from DNSResolver() which now includes the p2p-forge local resolution.

This means all /dns4/*.libp2p.direct/... and /dns6/*.libp2p.direct/... multiaddrs resolved by go-libp2p's swarm will use the new local resolver, avoiding network I/O. This includes peer addresses discovered via DHT, delegated routing, or any other source.

Regression tests

I confirmed this with extra manual test:

$ ipfs config DNS.Resolvers '{"." : "https://invalid.broken.resolver.test/dns-query"}'
$ ipfs swarm connect /dnsaddr/bootstrap.libp2p.io

Error: Post "https://invalid.broken.resolver.test/dns-query": dial tcp: lookup
invalid.broken.resolver.test on 127.0.0.1:53: no such host

The error proves DNS.Resolvers IS being used for /dnsaddr multiaddr resolution too.

Added e2e test to test/cli to ensure we have regression test that detects if this wiring is ever broken due to future go-libp2p update or our own refactors etc.

lidel added 2 commits January 12, 2026 17:57
p2p-forge hostnames encode IP addresses directly (e.g., 1-2-3-4.peerID.libp2p.direct -> 1.2.3.4),
so DNS queries are wasteful. kubo now parses these IPs in-memory.

- applies to both default libp2p.direct and custom AutoTLS.DomainSuffix
- TXT queries still delegate to network for ACME DNS-01 compatibility

Fixes #11136
regression test for #9199

confirms that DNS.Resolvers config is used when resolving /dnsaddr
multiaddrs during swarm connect, not just for DNSLink resolution
@lidel lidel changed the title feat(dns): resolve libp2p.direct addresses locally without network I/O feat(dns): skip DNS lookups for AutoTLS hostnames Jan 12, 2026
@lidel lidel marked this pull request as ready for review January 12, 2026 18:05
@lidel lidel requested a review from a team as a code owner January 12, 2026 18:05
@lidel lidel requested a review from aschmahmann January 12, 2026 18:05
@lidel lidel added the status/ready Ready to be worked label Jan 12, 2026
Comment on lines 70 to 75
// split subdomain into parts: should be [ip-prefix, peerID] or just [peerID]
parts := strings.Split(subdomain, ".")
if len(parts) < 2 {
// peerID only, no IP component - return empty (NODATA equivalent)
return nil, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

We can do this, but should we? e.g. under normal circumstances this shouldn't happen and in theory in the future .libp2p.direct could point at a given IP address.

Maybe a fallback here is better? Generally speaking shouldn't we use a fallback everywhere we can't give a definitive answer?

Copy link
Member Author

Choose a reason for hiding this comment

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

You are right, we don't want to hard-fail here, as that would close the door for future features.

Changed all error paths to fall back to network DNS.

return nil, fmt.Errorf("hostname %q does not match any p2p-forge suffix", hostname)
}

// split subdomain into parts: should be [ip-prefix, peerID] or just [peerID]
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't we do a "is this a valid peerID" check like happens at libp2p.direct?

Copy link
Member Author

Choose a reason for hiding this comment

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

Added peer.Decode() validation with fallback on failure

Comment on lines +40 to +42
for _, domain := range forgeDomains {
opts = append(opts, madns.WithDomainResolver(domain+".", forgeResolver))
}
Copy link
Contributor

Choose a reason for hiding this comment

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

How is this meant to interact with say the user hard-coding a resolver in the config for *.libp2p.direct?

Copy link
Member Author

Choose a reason for hiding this comment

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

It overrides it. Documented in DNS.Resolvers section of config.md.

core/node/dns.go Outdated

// Build list of p2p-forge domains to resolve locally without network I/O.
// AutoTLS hostnames encode IP addresses directly (e.g., 1-2-3-4.peerID.libp2p.direct),
// so DNS lookups are wasteful. We always resolve these in-memory.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a reason we'd want to be able to turn this feature off? I can't think of a good reason other than debugging right now so I suspect not but wanted to ask in case.

Copy link
Member Author

Choose a reason for hiding this comment

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

Unlikely, but could be useful for testing/debugging.
Added AutoTLS.SkipDNSLookup config flag (enabled by default).

// It verifies that DNS.Resolvers config is used when resolving /dnsaddr,
// /dns, /dns4, /dns6 multiaddrs during peer connections, not just for
// DNSLink resolution.
func TestDNSResolversApplyToMultiaddr(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we also add a test that even when we override the default resolver, libp2p.direct still uses the custom resolver or just extra noise because we have sufficient tests around the configurability elsewhere?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think its good idea: added test verifying local resolution with broken DNS.Resolvers

lidel added 2 commits January 19, 2026 22:49
Changes based on PR review comments:

- #11140 (comment)
  use fallback to network DNS instead of returning errors when local
  parsing fails, ensuring forward compatibility with future DNS records

- #11140 (comment)
  add peerID validation using peer.Decode(), matching libp2p.direct
  server behavior, with fallback on invalid peerID

- #11140 (comment)
  document interaction with DNS.Resolvers in config.md

- #11140 (comment)
  add AutoTLS.SkipDNSLookup config flag to disable local resolution
  (useful for debugging or custom DNS override scenarios)

- #11140 (comment)
  add E2E test verifying libp2p.direct resolves locally even when
  DNS.Resolvers points to a broken server

additional improvements:
- use madns.BasicResolver interface instead of custom basicResolver
- add compile-time interface checks for p2pForgeResolver and madns.Resolver
- refactor tests: merge IPv4/IPv6, add helpers, use config.DefaultDomainSuffix
- improve changelog to explain public good benefit (reducing DNS load)
@lidel lidel requested a review from aschmahmann January 19, 2026 22:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status/ready Ready to be worked

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Do not require network i/o for *.libp2p.direct domain resolution DNS.Resolvers should be used for DNS multiaddrs: /dnsaddr /dns /dns4 /dns6

3 participants