Summary
When Routing.Type=none is set (to fully disable routing), the bootstrap process still runs because IpfsNode.Routing is set to routinghelpers.Null{} (not nil). This causes the node to periodically dial backup bootstrap peers persisted from previous runs, triggering external DNS resolution and failed connection attempts every 30 seconds.
Version
Configuration
{
"Routing": {
"Type": "none"
},
"Bootstrap": null,
"Routing.DelegatedRouters": null,
"Ipns.DelegatedPublishers": null,
"Discovery": {
"MDNS": { "Enabled": false }
}
}
The node was previously run with default routing (Routing.Type=auto), which persisted bootstrap peers and delegated routing peers to the datastore's backup bootstrap peer list (TempBootstrapPeersKey = /local/temp_bootstrap_peers) and the libp2p peerstore address book.
Observed behavior
With Routing.Type=none and Bootstrap=null, the daemon logs show repeated DNS resolution failures every ~30s (matching DefaultBootstrapConfig.Period):
WARN swarm2 swarm/swarm_dial.go:448 Failed to resolve addr {"addr": "/dnsaddr/va1.bootstrap.libp2p.io", "err": "server misbehaving"}
WARN swarm2 swarm/swarm_dial.go:448 Failed to resolve addr {"addr": "/dns4/ny5.bootstrap.libp2p.io/tcp/443/tls/sni/ny5.bootstrap.libp2p.io/ws", "err": "operation was canceled"}
WARN swarm2 swarm/swarm_dial.go:448 Failed to resolve addr {"addr": "/dns4/miracle-matter-mercy-habit.2n6.me/tcp/443/tls/sni/miracle-matter-mercy-habit.2n6.me/ws", "err": "server misbehaving"}
WARN swarm2 swarm/swarm_dial.go:448 Failed to resolve addr {"addr": "/dns4/urge-gym-circle-excuse.2n6.me/tcp/443/tls/sni/urge-gym-circle-excuse.2n6.me/ws", "err": "server misbehaving"}
The addresses being dialed include:
*.bootstrap.libp2p.io — DHT bootstrap nodes
*.2n6.me — delegated routing endpoints (from previous runs)
ipfs-amino-kubo.probelab.io — Amino DHT nodes
ipfs swarm peers shows zero connected peers, confirming the node is isolated as intended. But the bootstrap process keeps trying to reach external peers.
Root cause
In core/core.go, IpfsNode.Bootstrap():
func (n *IpfsNode) Bootstrap(cfg bootstrap.BootstrapConfig) error {
if n.Routing == nil { // Null{} is NOT nil, so this check passes
return nil
}
// ... bootstrap proceeds
n.Bootstrapper, err = bootstrap.Bootstrap(n.Identity, n.PeerHost, n.Routing, cfg)
return err
}
When Routing.Type=none, constructNilRouting in core/node/libp2p/routingopt.go returns routinghelpers.Null{}:
func constructNilRouting(_ RoutingOptionArgs) (routing.Routing, error) {
return routinghelpers.Null{}, nil
}
routinghelpers.Null{} is a non-nil interface value, so the n.Routing == nil check does not short-circuit. The bootstrap process starts.
Then in bootstrap.Bootstrap() (boxo/bootstrap/bootstrap.go), since Bootstrap=null means cfg.BootstrapPeers() returns empty, but cfg.loadBackupBootstrapPeers(ctx) loads persisted peers from the datastore (key TempBootstrapPeersKey), the bootstrapRound function dials these stale backup peers:
func bootstrapRound(ctx context.Context, host host.Host, cfg BootstrapConfig) error {
connected := host.Network().Peers()
if len(connected) >= cfg.MinPeerThreshold { // 0 < 4, so we proceed
return nil
}
peers := cfg.BootstrapPeers() // empty (Bootstrap=null)
if len(peers) > 0 { ... } // skipped
// Falls back to backup peers loaded from datastore
tempBootstrapPeers := cfg.loadBackupBootstrapPeers(ctx)
if len(tempBootstrapPeers) > 0 {
numToDial -= int(peersConnect(ctx, host, tempBootstrapPeers, numToDial, false))
// dials stale external peers -> DNS resolution -> failures
}
return ErrNotEnoughBootstrapPeers
}
This repeats every 30 seconds (DefaultBootstrapConfig.Period).
Expected behavior
Routing.Type=none should fully disable the bootstrap process. The node should not attempt to dial any external peers, resolve external DNS, or load backup bootstrap peers. The intent of Routing.Type=none is to run a fully offline/local-only node.
Proposed fix
In core/core.go, IpfsNode.Bootstrap(), add a check for routinghelpers.Null:
func (n *IpfsNode) Bootstrap(cfg bootstrap.BootstrapConfig) error {
if n.Routing == nil {
return nil
}
// Skip bootstrap when routing is explicitly disabled (Routing.Type=none)
if _, ok := n.Routing.(routinghelpers.Null); ok {
return nil
}
// ... rest of bootstrap setup
This mirrors the existing pattern in HasActiveDHTClient() which already checks for routinghelpers.Null:
func (n *IpfsNode) HasActiveDHTClient() bool {
if n.DHTClient == nil {
return false
}
if _, ok := n.DHTClient.(routinghelpers.Null); ok {
return false
}
// ...
}
Workaround
Users can manually clear the backup bootstrap peers from the datastore, but this is fragile and the issue recurs if the node is ever run with routing enabled again:
# Stop the daemon, then:
ipfs repo gc # may not clear the key
# Or directly remove the leveldb key /local/temp_bootstrap_peers
Impact
- Unnecessary network traffic: DNS queries and connection attempts every 30s to external peers
- Log spam: Repeated
WARN swarm2 messages about failed DNS resolution
- Privacy: Node leaks information via DNS queries to external resolvers
- User confusion:
Routing.Type=none appears to not fully work (see forum discussion where users report "it didn't have any effect on the network traffic")
Related
Summary
When
Routing.Type=noneis set (to fully disable routing), the bootstrap process still runs becauseIpfsNode.Routingis set toroutinghelpers.Null{}(notnil). This causes the node to periodically dial backup bootstrap peers persisted from previous runs, triggering external DNS resolution and failed connection attempts every 30 seconds.Version
Configuration
{ "Routing": { "Type": "none" }, "Bootstrap": null, "Routing.DelegatedRouters": null, "Ipns.DelegatedPublishers": null, "Discovery": { "MDNS": { "Enabled": false } } }The node was previously run with default routing (
Routing.Type=auto), which persisted bootstrap peers and delegated routing peers to the datastore's backup bootstrap peer list (TempBootstrapPeersKey = /local/temp_bootstrap_peers) and the libp2p peerstore address book.Observed behavior
With
Routing.Type=noneandBootstrap=null, the daemon logs show repeated DNS resolution failures every ~30s (matchingDefaultBootstrapConfig.Period):The addresses being dialed include:
*.bootstrap.libp2p.io— DHT bootstrap nodes*.2n6.me— delegated routing endpoints (from previous runs)ipfs-amino-kubo.probelab.io— Amino DHT nodesipfs swarm peersshows zero connected peers, confirming the node is isolated as intended. But the bootstrap process keeps trying to reach external peers.Root cause
In
core/core.go,IpfsNode.Bootstrap():When
Routing.Type=none,constructNilRoutingincore/node/libp2p/routingopt.goreturnsroutinghelpers.Null{}:routinghelpers.Null{}is a non-nil interface value, so then.Routing == nilcheck does not short-circuit. The bootstrap process starts.Then in
bootstrap.Bootstrap()(boxo/bootstrap/bootstrap.go), sinceBootstrap=nullmeanscfg.BootstrapPeers()returns empty, butcfg.loadBackupBootstrapPeers(ctx)loads persisted peers from the datastore (keyTempBootstrapPeersKey), thebootstrapRoundfunction dials these stale backup peers:This repeats every 30 seconds (
DefaultBootstrapConfig.Period).Expected behavior
Routing.Type=noneshould fully disable the bootstrap process. The node should not attempt to dial any external peers, resolve external DNS, or load backup bootstrap peers. The intent ofRouting.Type=noneis to run a fully offline/local-only node.Proposed fix
In
core/core.go,IpfsNode.Bootstrap(), add a check forroutinghelpers.Null:This mirrors the existing pattern in
HasActiveDHTClient()which already checks forroutinghelpers.Null:Workaround
Users can manually clear the backup bootstrap peers from the datastore, but this is fragile and the issue recurs if the node is ever run with routing enabled again:
Impact
WARN swarm2messages about failed DNS resolutionRouting.Type=noneappears to not fully work (see forum discussion where users report "it didn't have any effect on the network traffic")Related
loadBackupBootstrapPeers