@cryptotaxi247 / kubo / commits / 508f112ed

fix: resolve wildcard swarm in http provides (#11297)

0.41.0's httpRouterAddrFunc only resolved 0.0.0.0/:: when AutoNATv2 had a confirmed reachable address. Otherwise it forwarded raw Addresses.Swarm strings to HTTP routers, so isolated or LAN-only nodes published unreachable provider records. - core/node/libp2p/routingopt.go: fallback now calls host.Addrs(), which resolves wildcard binds to concrete interface addrs and applies the libp2p AddrsFactory (NoAnnounce CIDR, Swarm.AddrFilters); matches the DHT provide path (core/node/provider.go selfAddrsFunc) - core/node/libp2p/routingopt_test.go: stubHost.Addrs is configurable; cases rewritten around resolved host addrs, with a new case pinning that NoAnnounce CIDR filtering belongs upstream in host.Addrs - test/cli/delegated_routing_v1_http_client_test.go: new end-to-end case asserts provider records sent over HTTP never contain 0.0.0.0 or :: when Addresses.Swarm uses the default wildcard bind Fixes #11213

Marcin Rataj committed Apr 27, 2026 at 15:35 UTC 508f112eddd25da652e5cce40345c1a125642d89
3 files changed +75 -34
core/node/libp2p/routingopt.go
+11 -19
@@ -340,7 +340,9 @@ var _ confirmedAddrsHost = (*basichost.BasicHost)(nil)
340 // Resolution logic:
341 // - If Announce is set, use it as a static override (no dynamic resolution).
342 // - Otherwise, prefer AutoNAT V2 confirmed reachable addresses when available,
343 -// falling back to static Swarm addresses (filtered by NoAnnounce).
343 +// falling back to host.Addrs() which resolves 0.0.0.0/:: Swarm binds to
344 +// concrete interface addresses and applies the libp2p AddrsFactory
345 +// (Addresses.NoAnnounce CIDR filters and Swarm.AddrFilters).
346 // - AppendAnnounce addresses are always appended.
347 func httpRouterAddrFunc(h host.Host, cfgAddrs config.Addresses) func() []ma.Multiaddr {
348 appendAddrs := parseMultiaddrs(cfgAddrs.AppendAnnounce)
@@ -351,23 +353,6 @@ func httpRouterAddrFunc(h host.Host, cfgAddrs config.Addresses) func() []ma.Mult
353 return func() []ma.Multiaddr { return staticAddrs }
354 }
355
354 - // Precompute fallback: Swarm minus NoAnnounce plus AppendAnnounce.
355 - fallbackStrs := cfgAddrs.Swarm
356 - if len(cfgAddrs.NoAnnounce) > 0 {
357 - noAnnounce := map[string]struct{}{}
358 - for _, a := range cfgAddrs.NoAnnounce {
359 - noAnnounce[a] = struct{}{}
360 - }
361 - filtered := make([]string, 0, len(fallbackStrs))
362 - for _, a := range fallbackStrs {
363 - if _, skip := noAnnounce[a]; !skip {
364 - filtered = append(filtered, a)
365 - }
366 - }
367 - fallbackStrs = filtered
368 - }
369 - fallbackResult := slices.Concat(parseMultiaddrs(fallbackStrs), appendAddrs)
370 -
356 ch, hasConfirmed := h.(confirmedAddrsHost)
357 return func() []ma.Multiaddr {
358 if hasConfirmed {
@@ -379,7 +364,14 @@ func httpRouterAddrFunc(h host.Host, cfgAddrs config.Addresses) func() []ma.Mult
364 return slices.Concat(reachable, appendAddrs)
365 }
366 }
382 - return fallbackResult
367 + // Fallback: host.Addrs() resolves wildcard binds (0.0.0.0, ::) to
368 + // concrete interface addresses and applies the libp2p AddrsFactory,
369 + // which is where Addresses.NoAnnounce CIDR filtering happens.
370 + hostAddrs := h.Addrs()
371 + if len(appendAddrs) == 0 {
372 + return hostAddrs
373 + }
374 + return slices.Concat(hostAddrs, appendAddrs)
375 }
376 }
377
core/node/libp2p/routingopt_test.go
+41 -15
@@ -206,10 +206,12 @@ func TestEndpointCapabilitiesReadWriteLogic(t *testing.T) {
206 }
207
208 // stubHost is a minimal host.Host stub for testing httpRouterAddrFunc.
209 -// Only the methods checked via type assertion (confirmedAddrsHost) matter;
210 -// all other methods panic if called.
209 +// reachable mocks ConfirmedAddrs (AutoNAT V2 result); hostAddrs mocks
210 +// Addrs(), which in a real host returns wildcard-resolved interface
211 +// addresses after the AddrsFactory filters (NoAnnounce/AddrFilters).
212 type stubHost struct {
213 reachable []ma.Multiaddr
214 + hostAddrs []ma.Multiaddr
215 }
216
217 func (h *stubHost) ConfirmedAddrs() (reachable, unreachable, unknown []ma.Multiaddr) {
@@ -217,7 +219,7 @@ func (h *stubHost) ConfirmedAddrs() (reachable, unreachable, unknown []ma.Multia
219 }
220
221 func (h *stubHost) ID() peer.ID { panic("unused") }
220 -func (h *stubHost) Addrs() []ma.Multiaddr { panic("unused") }
222 +func (h *stubHost) Addrs() []ma.Multiaddr { return h.hostAddrs }
223 func (h *stubHost) Peerstore() peerstore.Peerstore { panic("unused") }
224 func (h *stubHost) Network() network.Network { panic("unused") }
225 func (h *stubHost) Mux() protocol.Switch { panic("unused") }
@@ -235,44 +237,65 @@ func (h *stubHost) ConnManager() connmgr.ConnManager { panic("unused") }
237 func (h *stubHost) EventBus() event.Bus { panic("unused") }
238
239 func TestHttpRouterAddrFunc(t *testing.T) {
240 + // hostAddrs simulates what host.Addrs() returns in a running daemon:
241 + // wildcard Swarm binds resolved to concrete interfaces, with the
242 + // libp2p AddrsFactory (NoAnnounce/AddrFilters) already applied.
243 + resolvedAddrs := []string{
244 + "/ip4/192.168.1.10/tcp/4001",
245 + "/ip4/192.168.1.10/udp/4001/quic-v1",
246 + }
247 +
248 tests := []struct {
249 name string
250 reachable []string // autonat confirmed addrs (nil = none)
251 + hostAddrs []string // host.Addrs() output (nil = none)
252 cfg config.Addresses
253 want []string
254 }{
255 {
245 - name: "prefers autonat confirmed reachable addrs over swarm fallback",
256 + name: "prefers autonat confirmed reachable addrs over host.Addrs fallback",
257 reachable: []string{"/ip4/1.2.3.4/tcp/4001", "/ip4/1.2.3.4/udp/4001/quic-v1"},
258 + hostAddrs: resolvedAddrs,
259 cfg: config.Addresses{Swarm: []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/udp/4001/quic-v1"}},
260 want: []string{"/ip4/1.2.3.4/tcp/4001", "/ip4/1.2.3.4/udp/4001/quic-v1"},
261 },
262 {
251 - name: "falls back to swarm when autonat has no confirmed addrs",
252 - cfg: config.Addresses{Swarm: []string{"/ip4/0.0.0.0/tcp/4001"}},
253 - want: []string{"/ip4/0.0.0.0/tcp/4001"},
263 + name: "falls back to host.Addrs when autonat has no confirmed addrs",
264 + hostAddrs: resolvedAddrs,
265 + cfg: config.Addresses{Swarm: []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/udp/4001/quic-v1"}},
266 + want: resolvedAddrs,
267 },
268 {
256 - name: "Announce overrides autonat and swarm",
269 + name: "Announce overrides autonat and host.Addrs",
270 reachable: []string{"/ip4/1.2.3.4/tcp/4001"},
271 + hostAddrs: resolvedAddrs,
272 cfg: config.Addresses{Swarm: []string{"/ip4/0.0.0.0/tcp/4001"}, Announce: []string{"/ip4/5.6.7.8/tcp/4001"}},
273 want: []string{"/ip4/5.6.7.8/tcp/4001"},
274 },
275 {
276 name: "AppendAnnounce added to autonat addrs",
277 reachable: []string{"/ip4/1.2.3.4/tcp/4001"},
278 + hostAddrs: resolvedAddrs,
279 cfg: config.Addresses{Swarm: []string{"/ip4/0.0.0.0/tcp/4001"}, AppendAnnounce: []string{"/ip4/10.0.0.1/tcp/4001"}},
280 want: []string{"/ip4/1.2.3.4/tcp/4001", "/ip4/10.0.0.1/tcp/4001"},
281 },
282 {
268 - name: "AppendAnnounce added to swarm fallback",
269 - cfg: config.Addresses{Swarm: []string{"/ip4/0.0.0.0/tcp/4001"}, AppendAnnounce: []string{"/ip4/10.0.0.1/tcp/4001"}},
270 - want: []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/10.0.0.1/tcp/4001"},
283 + name: "AppendAnnounce added to host.Addrs fallback",
284 + hostAddrs: resolvedAddrs,
285 + cfg: config.Addresses{Swarm: []string{"/ip4/0.0.0.0/tcp/4001"}, AppendAnnounce: []string{"/ip4/10.0.0.1/tcp/4001"}},
286 + want: append(append([]string{}, resolvedAddrs...), "/ip4/10.0.0.1/tcp/4001"),
287 },
288 {
273 - name: "NoAnnounce filters swarm fallback",
274 - cfg: config.Addresses{Swarm: []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/udp/4001/quic-v1"}, NoAnnounce: []string{"/ip4/0.0.0.0/tcp/4001"}},
275 - want: []string{"/ip4/0.0.0.0/udp/4001/quic-v1"},
289 + // NoAnnounce (including server profile CIDR ranges) is applied by the
290 + // libp2p AddrsFactory before host.Addrs() returns, so httpRouterAddrFunc
291 + // itself performs no filtering on the fallback.
292 + name: "NoAnnounce filtering happens upstream in host.Addrs",
293 + hostAddrs: []string{"/ip4/192.168.1.10/tcp/4001"}, // already filtered by addrFactory
294 + cfg: config.Addresses{
295 + Swarm: []string{"/ip4/0.0.0.0/tcp/4001"},
296 + NoAnnounce: []string{"/ip4/127.0.0.0/ipcidr/8"},
297 + },
298 + want: []string{"/ip4/192.168.1.10/tcp/4001"},
299 },
300 {
301 name: "AppendAnnounce added to Announce",
@@ -282,7 +305,10 @@ func TestHttpRouterAddrFunc(t *testing.T) {
305 }
306 for _, tt := range tests {
307 t.Run(tt.name, func(t *testing.T) {
285 - h := &stubHost{reachable: parseMultiaddrs(tt.reachable)}
308 + h := &stubHost{
309 + reachable: parseMultiaddrs(tt.reachable),
310 + hostAddrs: parseMultiaddrs(tt.hostAddrs),
311 + }
312 fn := httpRouterAddrFunc(h, tt.cfg)
313 assert.Equal(t, parseMultiaddrs(tt.want), fn())
314 })
test/cli/delegated_routing_v1_http_client_test.go
+23
@@ -274,4 +274,27 @@ func TestHTTPDelegatedRoutingProviderAddrs(t *testing.T) {
274 require.NotEmpty(t, addrs, "provider record should contain addresses")
275 assert.Contains(t, addrs, "/ip4/5.6.7.8/tcp/4001", "AppendAnnounce address should be present")
276 })
277 +
278 + t.Run("provider records resolve 0.0.0.0 Swarm bind to interface addresses", func(t *testing.T) {
279 + t.Parallel()
280 + srv, getAddrs := captureProviderAddrs(t)
281 +
282 + // Default Addresses.Swarm binds to /ip4/0.0.0.0/... If httpRouterAddrFunc
283 + // forwards those verbatim, HTTP routers receive useless unroutable entries.
284 + // See https://github.com/ipfs/kubo/issues/11213.
285 + node := harness.NewT(t).NewNode().Init()
286 + node.SetIPFSConfig("Routing", customRoutingConf(srv.URL))
287 + node.StartDaemon()
288 + defer node.StopDaemon()
289 +
290 + cidStr := node.IPFSAddStr(time.Now().String())
291 + node.IPFS("routing", "provide", cidStr)
292 +
293 + addrs := getAddrs()
294 + require.NotEmpty(t, addrs, "provider record should contain addresses")
295 + for _, a := range addrs {
296 + assert.NotContains(t, a, "/ip4/0.0.0.0/", "unresolved 0.0.0.0 in provider record: %s", a)
297 + assert.NotContains(t, a, "/ip6/::/", "unresolved :: in provider record: %s", a)
298 + }
299 + })
300 }