Skip to content

HTTP Routing V1 provider records contain raw 0.0.0.0 listen addresses instead of resolved interface addresses #11213

Description

@Rinse12

Checklist

Installation method

dist.ipfs.tech or ipfs-update

Version

## Version
## Installation method

npm (`kubo` package v0.40.0)


Kubo version: 0.40.0-882b7d2a9
Repo version: 18
System version: amd64/linux
Golang version: go1.26.0

Config

## Config

Default config from `ipfs init`, with only these changes:

- `Addresses.API` changed to avoid port conflicts
- `Addresses.Swarm` ports changed to avoid conflicts (still using `0.0.0.0` bind, which is the default)
- `Addresses.Gateway` disabled
- `Discovery.MDNS.Enabled` set to false
- Bootstrap peers removed
- `Routing` set to custom with a single HTTP router endpoint


{
  "Routing": {
    "Type": "custom",
    "Methods": {
      "find-providers": { "RouterName": "HttpRouter" },
      "provide": { "RouterName": "HttpRouter" },
      "find-peers": { "RouterName": "HttpRouter" },
      "get-ipns": { "RouterName": "HttpRouter" },
      "put-ipns": { "RouterName": "HttpRouter" }
    },
    "Routers": {
      "HttpRouter": {
        "Type": "http",
        "Parameters": { "Endpoint": "http://127.0.0.1:<mock-router-port>" }
      }
    }
  }
}

Description

When Kubo sends provider records to an HTTP router via PUT /routing/v1/providers, the Addrs field in the request body contains the raw Addresses.Swarm listen addresses (e.g. /ip4/0.0.0.0/tcp/24199) instead of resolved interface addresses.

ipfs id correctly resolves 0.0.0.0 to actual network interface addresses, but the HTTP Routing V1 code path does not perform this resolution before sending provider records.

What I was doing

  1. Started a fresh Kubo node with default swarm config (0.0.0.0 bind addresses)
  2. Configured Routing to use an HTTP router (a local mock server that records requests)
  3. Added content via ipfs add
  4. Triggered ipfs routing provide <cid>

What I expected

The PUT /routing/v1/providers request sent to the HTTP router should contain resolved interface addresses, matching what ipfs id reports:

/ip4/192.168.x.x/tcp/24199
/ip4/172.17.0.1/tcp/24199
/ip4/127.0.0.1/tcp/24199
/ip4/192.168.x.x/udp/24199/quic-v1
...

What actually happened

The provider records sent to the HTTP router contain the raw unresolved listen addresses:

/ip4/0.0.0.0/tcp/24199
/ip6/::/tcp/24199
/ip4/0.0.0.0/udp/24199/quic-v1
/ip6/::/udp/24199/quic-v1
/ip4/0.0.0.0/udp/24199/quic-v1/webtransport
/ip6/::/udp/24199/quic-v1/webtransport
/ip4/0.0.0.0/udp/24199/webrtc-direct
/ip6/::/udp/24199/webrtc-direct

While at the same time, ipfs id correctly reports resolved addresses like /ip4/192.168.x.x/tcp/24199, /ip4/172.17.0.1/tcp/24199, etc.

Impact

Any client that discovers a provider via an HTTP router receives useless 0.0.0.0 addresses and must fall back to a DHT FIND_PEER to locate the actual peer, defeating the purpose of the HTTP router.

As a workaround we run a proxy server between Kubo and the HTTP router that intercepts PUT /routing/v1/providers requests and rewrites the addresses using data from the id and swarm/addrs API endpoints.

Steps to reproduce

Minimal reproduction repo: https://github.com/Rinse12/kubo-http-router-0.0.0.0-address-bug

git clone https://github.com/Rinse12/kubo-http-router-0.0.0.0-address-bug.git
cd kubo-http-router-0.0.0.0-address-bug
npm install
node index.mjs

The script starts a mock HTTP Routing V1 server, starts a fresh Kubo daemon pointing at it, adds content, triggers routing provide, and prints the addresses from ipfs id alongside the addresses Kubo actually sent to the HTTP router.

Activity

  1. added
    kind/bugA bug in existing code (including security flaws)
    need/triageNeeds initial labeling and prioritization
    on Feb 26, 2026
  2. lidel commented on Feb 27, 2026

    @lidel
    Contributor

    Thank you for reporting.

    Triage note:

    The issue is in boxo's routing/http/client.Client Provider addresses are captured once at client construction time via WithProviderInfo(peerID, addrs) and stored as a static []types.Multiaddr field. When ProvideBitswap() builds the request, it reads c.addrs directly:

    // boxo routing/http/client/client.go
    
    type Client struct {
        // ...
        peerID   peer.ID
        addrs    []types.Multiaddr  // static, set once at construction
        // ...
    }
    
    func (c *Client) ProvideBitswap(ctx context.Context, keys []cid.Cid, ttl time.Duration) (time.Duration, error) {
        // ...
        req := types.WriteBitswapRecord{
            Payload: types.BitswapPayload{
                ID:    &c.peerID,
                Addrs: c.addrs,       // <-- uses whatever was set at init
            },
        }
        // ...
    }

    Kubo calls WithProviderInfo with raw config strings from Addresses.Swarm (via httpAddrsFromConfig()), so 0.0.0.0 / :: end up in provider records verbatim.

    The DHT provider path does not have this problem because it uses a func() []ma.Multiaddr callback that is evaluated at provide-time:

    // kubo core/node/provider.go
    selfAddrsFunc = func() []ma.Multiaddr { return impl.Host().Addrs() }
    // ...
    dhtprovider.WithSelfAddrs(selfAddrsFunc)

    Proposed fix in boxo

    Add a callback-based alternative to WithProviderInfo so addresses are resolved at provide-time, not construction-time. This follows the same pattern kubo's DHT provider already uses with WithSelfAddrs.

    1. New option in routing/http/client/client.go:

    // Client struct gets one new field:
    type Client struct {
        // ...
        peerID    peer.ID
        addrs     []types.Multiaddr
        addrsFunc func() []types.Multiaddr  // if set, called instead of using static addrs
        // ...
    }
    
    // New option:
    func WithProviderInfoFunc(peerID peer.ID, addrsFunc func() []multiaddr.Multiaddr) Option {
        return func(c *Client) error {
            c.peerID = peerID
            c.addrsFunc = func() []types.Multiaddr {
                addrs := addrsFunc()
                out := make([]types.Multiaddr, len(addrs))
                for i, a := range addrs {
                    out[i] = types.Multiaddr{Multiaddr: a}
                }
                return out
            }
            return nil
        }
    }

    2. ProvideBitswap resolves addresses lazily:

    func (c *Client) providerAddrs() []types.Multiaddr {
        if c.addrsFunc != nil {
            return c.addrsFunc()
        }
        return c.addrs
    }
    
    func (c *Client) ProvideBitswap(ctx context.Context, keys []cid.Cid, ttl time.Duration) (time.Duration, error) {
        // ...
        req := types.WriteBitswapRecord{
            Payload: types.BitswapPayload{
                // ...
                ID:    &c.peerID,
                Addrs: c.providerAddrs(),  // <-- dynamic resolution
            },
        }
        // ...
    }

    That's the entire boxo change: one new field, one new option func, one helper method, one changed line in ProvideBitswap.

    Kubo side

    Once boxo exposes WithProviderInfoFunc, kubo threads host.Host into the HTTP router construction (it is already available in RoutingOptionArgs.Host at that point) and provides a callback:

    drclient.WithProviderInfoFunc(host.ID(), func() []multiaddr.Multiaddr {
        // prefer AutoNAT V2 confirmed-reachable addresses
        type confirmedAddrsHost interface {
            ConfirmedAddrs() (reachable, unreachable, unknown []ma.Multiaddr)
        }
        if ch, ok := host.(confirmedAddrsHost); ok {
            reachable, _, _ := ch.ConfirmedAddrs()
            if len(reachable) > 0 {
                return reachable
            }
        }
        // fallback: host.Addrs() resolves 0.0.0.0 to interface addrs
        // and applies Announce/NoAnnounce/AppendAnnounce filters
        return host.Addrs()
    })

    This gives us:

    • AutoNAT-confirmed public addresses when available (best case)
    • Resolved interface addresses otherwise (fixes this issue)
    • Existing WithProviderInfo keeps working as-is (no breaking change)

    Since this requires new boxo release, and is a bug in experimental opt-in feature, low priority, but we could ship this in 0.41 if someone submits a PR.

  3. added
    P2Medium: Good to have, but can wait until someone steps up
    help wantedSeeking public contribution on this issue
    and removed
    need/triageNeeds initial labeling and prioritization
    on Feb 27, 2026
  4. rayspock commented on Mar 1, 2026

    @rayspock

    Hi, I’m new to the Kubo repo but I’ve been looking into this issue and the proposed direction seems reasonable.

    From what I understand, HTTP Routing V1 provider records currently expose raw 0.0.0.0 listen addresses, and the goal would be to resolve these into actual interface addresses before returning them.

    If no one is actively working on this, I’d be happy to take it on and put together a PR. Please let me know if there are any design constraints or preferred approaches I should be aware of before starting.

  5. added a commit that references this issue on Mar 14, 2026
    44d8d85
  6. lidel commented on Mar 14, 2026

    @lidel
    Contributor

    @Rinse12 are you able to test if

    addresses your needs?

    It uses confirmed public addrs from AutoNATv2 system, and also respects Addresses.Announce (static override) and Addresses.AppendAnnounce (adding static addrs in addition to dynamic / Wwarm ones)

  7. Cripto5588 commented on Mar 16, 2026

    @Cripto5588
  8. lidel commented on Mar 16, 2026

    @lidel
  9. self-assigned this
    on Mar 18, 2026
  10. removed
    help wantedSeeking public contribution on this issue
    on Mar 18, 2026
  11. added a commit that references this issue on Mar 18, 2026
    9c4e268
  12. Rinse12 commented on Apr 24, 2026

    @Rinse12
    Author

    I just tested it on Kubo 0.41.0 and unfortunately it still announces 0.0.0.0 addresses.

    You can test it out on https://github.com/Rinse12/kubo-http-router-0.0.0.0-address-bug

  13. lidel commented on Apr 24, 2026

    @lidel
    Contributor

    @Rinse12 i suspect this is due to you not running libp2p DHT and/or AutoNAT? You really should consider enabling DHT, not doing so is bit counter-productive, especially with recent improvements (provide sweep, strategy pinned+mfs+unique etc).

    Update:

    • You're right, 0.41.0 only fully resolves addresses when AutoNATv2 has confirmed at least one reachable address. When the node is isolated (like your mock-router repro) the fallback path sends the raw Addresses.Swarm strings verbatim, so /ip4/0.0.0.0/... goes through untouched.
    • Working on a proper fix that delegates to the same address resolution the DHT provide path already uses, so wildcard binds get resolved to concrete interface addresses, and Addresses.NoAnnounce CIDR filtering applies correctly. Targeting Kubo 0.42.

    Workaround until 0.42

    Set Addresses.Announce explicitly to the address(es) you want in provider records (include all transports you use):

    "Addresses": {
      "Announce": [
        "/ip4/<your-reachable-ip>/tcp/<swarm-port>",
        "/ip4/<your-reachable-ip>/udp/<swarm-port>/quic-v1"
      ]
    }
  14. reopened this on Apr 24, 2026
  15. Rinse12 commented on Apr 26, 2026

    @Rinse12
    Author

    i suspect this is due to you not running libp2p DHT

    Correct we're not using them intentionally. We would like to keep our apps very light in terms of network and computing resources with less moving parts involving IPFS. We've had issues with DHT in past, while they're probably fixed by now but we wanna get an MVP out with only http routers for now, then eventually explore http routers + DHT setup for backup.

    Feel free to try out one of our apps bitsocial-cli.

  16. added a commit that references this issue on Apr 27, 2026
    508f112
  17. added a commit that references this issue on Jun 11, 2026
    6bc9c47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P2Medium: Good to have, but can wait until someone steps upkind/bugA bug in existing code (including security flaws)

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions