Skip to content

Routing.Type=none still runs bootstrap process, dials stale backup peers and triggers external DNS resolution #11452

Description

@karawitan

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

Kubo version: 0.42.0

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions