Skip to content

Commit 4606586

Browse files
authored
feat(gateway): expose /routing/v1 server (opt-in) (#9877)
1 parent 86bde28 commit 4606586

18 files changed

+505
-56
lines changed

cmd/ipfs/daemon.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -832,6 +832,12 @@ func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, e
832832
fmt.Printf("Gateway server listening on %s\n", listener.Multiaddr())
833833
}
834834

835+
if cfg.Gateway.ExposeRoutingAPI.WithDefault(config.DefaultExposeRoutingAPI) {
836+
for _, listener := range listeners {
837+
fmt.Printf("Routing V1 API exposed at http://%s/routing/v1\n", listener.Addr())
838+
}
839+
}
840+
835841
cmdctx := *cctx
836842
cmdctx.Gateway = true
837843

@@ -848,6 +854,10 @@ func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, e
848854
opts = append(opts, corehttp.P2PProxyOption())
849855
}
850856

857+
if cfg.Gateway.ExposeRoutingAPI.WithDefault(config.DefaultExposeRoutingAPI) {
858+
opts = append(opts, corehttp.RoutingOption())
859+
}
860+
851861
if len(cfg.Gateway.RootRedirect) > 0 {
852862
opts = append(opts, corehttp.RedirectOption("", cfg.Gateway.RootRedirect))
853863
}

config/gateway.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package config
33
const (
44
DefaultInlineDNSLink = false
55
DefaultDeserializedResponses = true
6+
DefaultExposeRoutingAPI = false
67
)
78

89
type GatewaySpec struct {
@@ -72,4 +73,8 @@ type Gateway struct {
7273
// PublicGateways configures behavior of known public gateways.
7374
// Each key is a fully qualified domain name (FQDN).
7475
PublicGateways map[string]*GatewaySpec
76+
77+
// ExposeRoutingAPI configures the gateway port to expose
78+
// routing system as HTTP API at /routing/v1 (https://specs.ipfs.tech/routing/http-routing-v1/).
79+
ExposeRoutingAPI Flag
7580
}

core/corehttp/routing.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package corehttp
2+
3+
import (
4+
"context"
5+
"net"
6+
"net/http"
7+
"time"
8+
9+
"github.com/ipfs/boxo/ipns"
10+
"github.com/ipfs/boxo/routing/http/server"
11+
"github.com/ipfs/boxo/routing/http/types"
12+
"github.com/ipfs/boxo/routing/http/types/iter"
13+
cid "github.com/ipfs/go-cid"
14+
core "github.com/ipfs/kubo/core"
15+
"github.com/libp2p/go-libp2p/core/peer"
16+
"github.com/libp2p/go-libp2p/core/routing"
17+
)
18+
19+
func RoutingOption() ServeOption {
20+
return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
21+
handler := server.Handler(&contentRouter{n})
22+
mux.Handle("/routing/v1/", handler)
23+
return mux, nil
24+
}
25+
}
26+
27+
type contentRouter struct {
28+
n *core.IpfsNode
29+
}
30+
31+
func (r *contentRouter) FindProviders(ctx context.Context, key cid.Cid, limit int) (iter.ResultIter[types.Record], error) {
32+
ctx, cancel := context.WithCancel(ctx)
33+
ch := r.n.Routing.FindProvidersAsync(ctx, key, limit)
34+
return iter.ToResultIter[types.Record](&peerChanIter{
35+
ch: ch,
36+
cancel: cancel,
37+
}), nil
38+
}
39+
40+
// nolint deprecated
41+
func (r *contentRouter) ProvideBitswap(ctx context.Context, req *server.BitswapWriteProvideRequest) (time.Duration, error) {
42+
return 0, routing.ErrNotSupported
43+
}
44+
45+
func (r *contentRouter) FindPeers(ctx context.Context, pid peer.ID, limit int) (iter.ResultIter[types.Record], error) {
46+
ctx, cancel := context.WithCancel(ctx)
47+
defer cancel()
48+
49+
addr, err := r.n.Routing.FindPeer(ctx, pid)
50+
if err != nil {
51+
return nil, err
52+
}
53+
54+
rec := &types.PeerRecord{
55+
Schema: types.SchemaPeer,
56+
ID: &addr.ID,
57+
}
58+
59+
for _, addr := range addr.Addrs {
60+
rec.Addrs = append(rec.Addrs, types.Multiaddr{Multiaddr: addr})
61+
}
62+
63+
return iter.ToResultIter[types.Record](iter.FromSlice[types.Record]([]types.Record{rec})), nil
64+
}
65+
66+
func (r *contentRouter) GetIPNS(ctx context.Context, name ipns.Name) (*ipns.Record, error) {
67+
ctx, cancel := context.WithCancel(ctx)
68+
defer cancel()
69+
70+
raw, err := r.n.Routing.GetValue(ctx, string(name.RoutingKey()))
71+
if err != nil {
72+
return nil, err
73+
}
74+
75+
return ipns.UnmarshalRecord(raw)
76+
}
77+
78+
func (r *contentRouter) PutIPNS(ctx context.Context, name ipns.Name, record *ipns.Record) error {
79+
ctx, cancel := context.WithCancel(ctx)
80+
defer cancel()
81+
82+
raw, err := ipns.MarshalRecord(record)
83+
if err != nil {
84+
return err
85+
}
86+
87+
// The caller guarantees that name matches the record. This is double checked
88+
// by the internals of PutValue.
89+
return r.n.Routing.PutValue(ctx, string(name.RoutingKey()), raw)
90+
}
91+
92+
type peerChanIter struct {
93+
ch <-chan peer.AddrInfo
94+
cancel context.CancelFunc
95+
next *peer.AddrInfo
96+
}
97+
98+
func (it *peerChanIter) Next() bool {
99+
addr, ok := <-it.ch
100+
if ok {
101+
it.next = &addr
102+
return true
103+
} else {
104+
it.next = nil
105+
return false
106+
}
107+
}
108+
109+
func (it *peerChanIter) Val() types.Record {
110+
if it.next == nil {
111+
return nil
112+
}
113+
114+
rec := &types.PeerRecord{
115+
Schema: types.SchemaPeer,
116+
ID: &it.next.ID,
117+
}
118+
119+
for _, addr := range it.next.Addrs {
120+
rec.Addrs = append(rec.Addrs, types.Multiaddr{Multiaddr: addr})
121+
}
122+
123+
return rec
124+
}
125+
126+
func (it *peerChanIter) Close() error {
127+
it.cancel()
128+
return nil
129+
}

core/node/libp2p/routingopt.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ func ConstructDelegatedRouting(routers config.Routers, methods config.Methods, p
139139
PeerID: peerID,
140140
Addrs: httpAddrsFromConfig(addrs),
141141
PrivKeyB64: privKey,
142-
})
142+
},
143+
)
143144
}
144145
}
145146

docs/changelogs/v0.23.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- [Mplex deprecation](#mplex-deprecation)
1010
- [Gateway: meaningful CAR responses on Not Found errors](#gateway-meaningful-car-responses-on-not-found-errors)
1111
- [Binary characters in file names: no longer works with old clients and new Kubo servers](#binary-characters-in-file-names-no-longer-works-with-old-clients-and-new-kubo-servers)
12+
- [Self-hosting `/routing/v1` endpoint for delegated routing needs](#self-hosting-routingv1-endpoint-for-delegated-routing-needs)
1213
- [📝 Changelog](#-changelog)
1314
- [👨‍👩‍👧‍👦 Contributors](#-contributors)
1415

@@ -59,6 +60,13 @@ the compatibility table:
5960

6061
*Old clients can only send Unicode file paths to the server.
6162

63+
#### Self-hosting `/routing/v1` endpoint for delegated routing needs
64+
65+
The `Routing` system configured in Kubo can be now exposed on the gateway port as a standard
66+
HTTP [Routing V1](https://specs.ipfs.tech/routing/http-routing-v1/) API endpoint. This allows
67+
self-hosting and experimentation with custom delegated routers. This is disabled by default,
68+
but can be enabled by setting [`Gateway.ExposeRoutingAPI`](https://github.com/ipfs/kubo/blob/master/docs/config.md#gatewayexposeroutingapi) to `true` .
69+
6270
### 📝 Changelog
6371

6472
### 👨‍👩‍👧‍👦 Contributors

docs/config.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,16 @@ Default: `true`
658658

659659
Type: `flag`
660660

661+
#### `Gateway.ExposeRoutingAPI`
662+
663+
An optional flag to expose Kubo `Routing` system on the gateway port as a [Routing
664+
V1](https://specs.ipfs.tech/routing/routing-v1/) endpoint. This only affects your
665+
local gateway, at `127.0.0.1`.
666+
667+
Default: `false`
668+
669+
Type: `flag`
670+
661671
### `Gateway.HTTPHeaders`
662672

663673
Headers to set on gateway responses.

docs/examples/kubo-as-a-library/go.mod

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ go 1.20
77
replace github.com/ipfs/kubo => ./../../..
88

99
require (
10-
github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7
10+
github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd
1111
github.com/ipfs/kubo v0.0.0-00010101000000-000000000000
1212
github.com/libp2p/go-libp2p v0.30.0
1313
github.com/multiformats/go-multiaddr v0.11.0
@@ -52,7 +52,6 @@ require (
5252
github.com/google/gopacket v1.1.19 // indirect
5353
github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect
5454
github.com/google/uuid v1.3.0 // indirect
55-
github.com/gorilla/mux v1.8.0 // indirect
5655
github.com/gorilla/websocket v1.5.0 // indirect
5756
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
5857
github.com/hannahhoward/go-pubsub v0.0.0-20200423002714-8d62886cc36e // indirect

docs/examples/kubo-as-a-library/go.sum

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,6 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
270270
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
271271
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
272272
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
273-
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
274273
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
275274
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
276275
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
@@ -301,8 +300,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
301300
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
302301
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
303302
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
304-
github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7 h1:f7n4M8UIf+4BY6Q0kcZ5FbpkxKaIqq/BW3evqI87DNo=
305-
github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7/go.mod h1:btrtHy0lmO1ODMECbbEY1pxNtrLilvKSYLoGQt1yYCk=
303+
github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd h1:uAp9W7FRQ7W16FENlURZqBh7/3PnakG0DjHpKPirKVY=
304+
github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd/go.mod h1:btrtHy0lmO1ODMECbbEY1pxNtrLilvKSYLoGQt1yYCk=
306305
github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
307306
github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
308307
github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY=

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ require (
1515
github.com/fsnotify/fsnotify v1.6.0
1616
github.com/google/uuid v1.3.0
1717
github.com/hashicorp/go-multierror v1.1.1
18-
github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7
18+
github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd
1919
github.com/ipfs/go-block-format v0.1.2
2020
github.com/ipfs/go-cid v0.4.1
2121
github.com/ipfs/go-cidutil v0.1.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,8 +335,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
335335
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
336336
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
337337
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
338-
github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7 h1:f7n4M8UIf+4BY6Q0kcZ5FbpkxKaIqq/BW3evqI87DNo=
339-
github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7/go.mod h1:btrtHy0lmO1ODMECbbEY1pxNtrLilvKSYLoGQt1yYCk=
338+
github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd h1:uAp9W7FRQ7W16FENlURZqBh7/3PnakG0DjHpKPirKVY=
339+
github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd/go.mod h1:btrtHy0lmO1ODMECbbEY1pxNtrLilvKSYLoGQt1yYCk=
340340
github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
341341
github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
342342
github.com/ipfs/go-bitswap v0.11.0 h1:j1WVvhDX1yhG32NTC9xfxnqycqYIlhzEzLXG/cU1HyQ=

routing/delegated.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,8 @@ func httpRoutingFromConfig(conf config.Router, extraHTTP *ExtraHTTPParams) (rout
224224

225225
return &httpRoutingWrapper{
226226
ContentRouting: cr,
227+
PeerRouting: cr,
228+
ValueStore: cr,
227229
ProvideManyRouter: cr,
228230
}, nil
229231
}

routing/wrapper.go

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55

66
routinghelpers "github.com/libp2p/go-libp2p-routing-helpers"
7-
"github.com/libp2p/go-libp2p/core/peer"
87
"github.com/libp2p/go-libp2p/core/routing"
98
)
109

@@ -22,27 +21,11 @@ var (
2221
// http delegated routing.
2322
type httpRoutingWrapper struct {
2423
routing.ContentRouting
24+
routing.PeerRouting
25+
routing.ValueStore
2526
routinghelpers.ProvideManyRouter
2627
}
2728

2829
func (c *httpRoutingWrapper) Bootstrap(ctx context.Context) error {
2930
return nil
3031
}
31-
32-
func (c *httpRoutingWrapper) FindPeer(ctx context.Context, id peer.ID) (peer.AddrInfo, error) {
33-
return peer.AddrInfo{}, routing.ErrNotSupported
34-
}
35-
36-
func (c *httpRoutingWrapper) PutValue(context.Context, string, []byte, ...routing.Option) error {
37-
return routing.ErrNotSupported
38-
}
39-
40-
func (c *httpRoutingWrapper) GetValue(context.Context, string, ...routing.Option) ([]byte, error) {
41-
return nil, routing.ErrNotSupported
42-
}
43-
44-
func (c *httpRoutingWrapper) SearchValue(context.Context, string, ...routing.Option) (<-chan []byte, error) {
45-
out := make(chan []byte)
46-
close(out)
47-
return out, routing.ErrNotSupported
48-
}

test/cli/content_routing_http_test.go

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,42 +16,45 @@ import (
1616
"github.com/ipfs/go-cid"
1717
"github.com/ipfs/kubo/test/cli/harness"
1818
"github.com/ipfs/kubo/test/cli/testutils"
19+
"github.com/libp2p/go-libp2p/core/peer"
1920
"github.com/libp2p/go-libp2p/core/routing"
2021
"github.com/stretchr/testify/assert"
2122
)
2223

2324
type fakeHTTPContentRouter struct {
24-
m sync.Mutex
25-
findProvidersCalls int
26-
provideCalls int
25+
m sync.Mutex
26+
provideBitswapCalls int
27+
findProvidersCalls int
28+
findPeersCalls int
2729
}
2830

29-
func (r *fakeHTTPContentRouter) FindProviders(ctx context.Context, key cid.Cid, limit int) (iter.ResultIter[types.ProviderResponse], error) {
31+
func (r *fakeHTTPContentRouter) FindProviders(ctx context.Context, key cid.Cid, limit int) (iter.ResultIter[types.Record], error) {
3032
r.m.Lock()
3133
defer r.m.Unlock()
3234
r.findProvidersCalls++
33-
return iter.FromSlice([]iter.Result[types.ProviderResponse]{}), nil
35+
return iter.FromSlice([]iter.Result[types.Record]{}), nil
3436
}
3537

38+
// nolint deprecated
3639
func (r *fakeHTTPContentRouter) ProvideBitswap(ctx context.Context, req *server.BitswapWriteProvideRequest) (time.Duration, error) {
3740
r.m.Lock()
3841
defer r.m.Unlock()
39-
r.provideCalls++
42+
r.provideBitswapCalls++
4043
return 0, nil
4144
}
4245

43-
func (r *fakeHTTPContentRouter) Provide(ctx context.Context, req *server.WriteProvideRequest) (types.ProviderResponse, error) {
46+
func (r *fakeHTTPContentRouter) FindPeers(ctx context.Context, pid peer.ID, limit int) (iter.ResultIter[types.Record], error) {
4447
r.m.Lock()
4548
defer r.m.Unlock()
46-
r.provideCalls++
47-
return nil, nil
49+
r.findPeersCalls++
50+
return iter.FromSlice([]iter.Result[types.Record]{}), nil
4851
}
4952

50-
func (r *fakeHTTPContentRouter) FindIPNSRecord(ctx context.Context, name ipns.Name) (*ipns.Record, error) {
53+
func (r *fakeHTTPContentRouter) GetIPNS(ctx context.Context, name ipns.Name) (*ipns.Record, error) {
5154
return nil, routing.ErrNotSupported
5255
}
5356

54-
func (r *fakeHTTPContentRouter) ProvideIPNSRecord(ctx context.Context, name ipns.Name, rec *ipns.Record) error {
57+
func (r *fakeHTTPContentRouter) PutIPNS(ctx context.Context, name ipns.Name, rec *ipns.Record) error {
5558
return routing.ErrNotSupported
5659
}
5760

0 commit comments

Comments
 (0)