diff --git a/cmd/ipfs/daemon.go b/cmd/ipfs/daemon.go index 1e03a8264a8e..c6f3e027390c 100644 --- a/cmd/ipfs/daemon.go +++ b/cmd/ipfs/daemon.go @@ -847,6 +847,10 @@ func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, e opts = append(opts, corehttp.P2PProxyOption()) } + if cfg.Gateway.ExposeRoutingAPI.WithDefault(config.DefaultExposeRoutingAPI) { + opts = append(opts, corehttp.RoutingOption()) + } + if len(cfg.Gateway.RootRedirect) > 0 { opts = append(opts, corehttp.RedirectOption("", cfg.Gateway.RootRedirect)) } diff --git a/config/gateway.go b/config/gateway.go index 8ae312b59aee..7c3446d87e0b 100644 --- a/config/gateway.go +++ b/config/gateway.go @@ -1,6 +1,9 @@ package config -const DefaultInlineDNSLink = false +const ( + DefaultInlineDNSLink = false + DefaultExposeRoutingAPI = false +) type GatewaySpec struct { // Paths is explicit list of path prefixes that should be handled by @@ -59,4 +62,8 @@ type Gateway struct { // PublicGateways configures behavior of known public gateways. // Each key is a fully qualified domain name (FQDN). PublicGateways map[string]*GatewaySpec + + // ExposeRoutingAPI configures the gateway to expose a Routing v1 HTTP Server + // under /routing/v1: https://specs.ipfs.tech/routing/routing-v1/. + ExposeRoutingAPI Flag } diff --git a/core/corehttp/routing.go b/core/corehttp/routing.go new file mode 100644 index 000000000000..3bff38469f33 --- /dev/null +++ b/core/corehttp/routing.go @@ -0,0 +1,102 @@ +package corehttp + +import ( + "context" + "net" + "net/http" + "time" + + "github.com/ipfs/boxo/routing/http/server" + "github.com/ipfs/boxo/routing/http/types" + "github.com/ipfs/boxo/routing/http/types/iter" + cid "github.com/ipfs/go-cid" + core "github.com/ipfs/kubo/core" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/routing" + "github.com/multiformats/go-multiaddr" +) + +func RoutingOption() ServeOption { + return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { + handler := server.Handler(&contentRouter{n}) + mux.Handle("/routing/v1/", handler) + return mux, nil + } +} + +type contentRouter struct { + n *core.IpfsNode +} + +func (r *contentRouter) FindProviders(ctx context.Context, key cid.Cid) (iter.ResultIter[types.ProviderResponse], error) { + ctx, cancel := context.WithCancel(ctx) + ch := r.n.Routing.FindProvidersAsync(ctx, key, 20) // TODO: magic number, where to get this from + return iter.ToResultIter[types.ProviderResponse](&peerChanIter{ + ch: ch, + cancel: cancel, + }), nil +} + +func (r *contentRouter) Provide(ctx context.Context, req *server.WriteProvideRequest) (types.ProviderResponse, error) { + // Kubo /routing/v1 endpoint does not support write operations. + return nil, routing.ErrNotSupported +} + +func (r *contentRouter) ProvideBitswap(ctx context.Context, req *server.BitswapWriteProvideRequest) (time.Duration, error) { + // Kubo /routing/v1 endpoint does not support write operations. + return 0, routing.ErrNotSupported +} + +type peerChanIter struct { + ch <-chan peer.AddrInfo + cancel context.CancelFunc + next *peer.AddrInfo +} + +func (it *peerChanIter) Next() bool { + addr, ok := <-it.ch + if ok { + it.next = &addr + return true + } else { + it.next = nil + return false + } +} + +func (it *peerChanIter) Val() types.ProviderResponse { + if it.next == nil { + return nil + } + + // We don't know what type of protocol this peer provides. It is likely Bitswap + // but it might not be. Therefore, we set an unknown protocol with an unknown schema. + rec := &providerRecord{ + Protocol: "transport-unknown", + Schema: "unknown", + ID: it.next.ID, + Addrs: it.next.Addrs, + } + + return rec +} + +func (it *peerChanIter) Close() error { + it.cancel() + return nil +} + +type providerRecord struct { + Protocol string + Schema string + ID peer.ID + Addrs []multiaddr.Multiaddr +} + +func (pr *providerRecord) GetProtocol() string { + return pr.Protocol +} + +func (pr *providerRecord) GetSchema() string { + return pr.Schema +}