mirror of
https://github.com/ipfs/kubo.git
synced 2026-02-21 10:27:46 +08:00
Some checks failed
CodeQL / codeql (push) Has been cancelled
Docker Check / lint (push) Has been cancelled
Docker Check / build (push) Has been cancelled
Gateway Conformance / gateway-conformance (push) Has been cancelled
Gateway Conformance / gateway-conformance-libp2p-experiment (push) Has been cancelled
Go Build / go-build (push) Has been cancelled
Go Check / go-check (push) Has been cancelled
Go Lint / go-lint (push) Has been cancelled
Go Test / unit-tests (push) Has been cancelled
Go Test / cli-tests (push) Has been cancelled
Go Test / example-tests (push) Has been cancelled
Interop / interop-prep (push) Has been cancelled
Sharness / sharness-test (push) Has been cancelled
Spell Check / spellcheck (push) Has been cancelled
Interop / helia-interop (push) Has been cancelled
Interop / ipfs-webui (push) Has been cancelled
* docs: mark custom routing as experimental reorganize Routing.Type section for clarity, group production and experimental options, consolidate DHT explanation, add limitations section to delegated-routing.md documenting that HTTP-only routing cannot provide content reliably * chore(config): reorder Routing sections and improve callout formatting move DelegatedRouters after Type, add config option names to CAUTION headers * docs: address reviewer feedback on config.md - clarify that `auto` can be combined with custom URLs in `Routing.DelegatedRouters` - rename headers for consistency: `Routing.Routers.[name].Type`, `Routing.Routers.[name].Parameters`, `Routing.Methods` - replace deprecated Strategic Providing reference with `Provide.*` config - remove outdated caveat about 0.39 sweep limitation - wording: "likely suffer" → "will be most affected" * docs: remove redundant Summary section from delegated-routing.md the IMPORTANT callout and Motivation section already cover what users need to know. historical version info was noise for researchers trying to configure custom routing. addresses reviewer feedback from #11111. --------- Co-authored-by: Daniel Norman <2color@users.noreply.github.com> Co-authored-by: Andrew Gillis <11790789+gammazero@users.noreply.github.com>
372 lines
10 KiB
Go
372 lines
10 KiB
Go
package routing
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"path"
|
|
"strings"
|
|
|
|
drclient "github.com/ipfs/boxo/routing/http/client"
|
|
"github.com/ipfs/boxo/routing/http/contentrouter"
|
|
"github.com/ipfs/go-datastore"
|
|
logging "github.com/ipfs/go-log/v2"
|
|
version "github.com/ipfs/kubo"
|
|
"github.com/ipfs/kubo/config"
|
|
dht "github.com/libp2p/go-libp2p-kad-dht"
|
|
"github.com/libp2p/go-libp2p-kad-dht/dual"
|
|
"github.com/libp2p/go-libp2p-kad-dht/fullrt"
|
|
record "github.com/libp2p/go-libp2p-record"
|
|
routinghelpers "github.com/libp2p/go-libp2p-routing-helpers"
|
|
ic "github.com/libp2p/go-libp2p/core/crypto"
|
|
host "github.com/libp2p/go-libp2p/core/host"
|
|
"github.com/libp2p/go-libp2p/core/peer"
|
|
"github.com/libp2p/go-libp2p/core/routing"
|
|
ma "github.com/multiformats/go-multiaddr"
|
|
"go.opencensus.io/stats/view"
|
|
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
|
)
|
|
|
|
var log = logging.Logger("routing/delegated")
|
|
|
|
// Parse creates a composed router from the custom routing configuration.
|
|
//
|
|
// EXPERIMENTAL: Custom routing (Routing.Type=custom with Routing.Routers and
|
|
// Routing.Methods) is for research and testing only, not production use.
|
|
// The configuration format and behavior may change without notice between
|
|
// releases. HTTP-only configurations cannot reliably provide content.
|
|
// See docs/delegated-routing.md for limitations.
|
|
func Parse(routers config.Routers, methods config.Methods, extraDHT *ExtraDHTParams, extraHTTP *ExtraHTTPParams) (routing.Routing, error) {
|
|
if err := methods.Check(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
createdRouters := make(map[string]routing.Routing)
|
|
finalRouter := &Composer{}
|
|
|
|
// Create all needed routers from method names
|
|
for mn, m := range methods {
|
|
router, err := parse(make(map[string]bool), createdRouters, m.RouterName, routers, extraDHT, extraHTTP)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch mn {
|
|
case config.MethodNamePutIPNS:
|
|
finalRouter.PutValueRouter = router
|
|
case config.MethodNameGetIPNS:
|
|
finalRouter.GetValueRouter = router
|
|
case config.MethodNameFindPeers:
|
|
finalRouter.FindPeersRouter = router
|
|
case config.MethodNameFindProviders:
|
|
finalRouter.FindProvidersRouter = router
|
|
case config.MethodNameProvide:
|
|
finalRouter.ProvideRouter = router
|
|
}
|
|
|
|
log.Info("using method ", mn, " with router ", m.RouterName)
|
|
}
|
|
|
|
return finalRouter, nil
|
|
}
|
|
|
|
func parse(visited map[string]bool,
|
|
createdRouters map[string]routing.Routing,
|
|
routerName string,
|
|
routersCfg config.Routers,
|
|
extraDHT *ExtraDHTParams,
|
|
extraHTTP *ExtraHTTPParams,
|
|
) (routing.Routing, error) {
|
|
// check if we already created it
|
|
r, ok := createdRouters[routerName]
|
|
if ok {
|
|
return r, nil
|
|
}
|
|
|
|
// check if we are in a dep loop
|
|
if visited[routerName] {
|
|
return nil, fmt.Errorf("dependency loop creating router with name %q", routerName)
|
|
}
|
|
|
|
// set node as visited
|
|
visited[routerName] = true
|
|
|
|
cfg, ok := routersCfg[routerName]
|
|
if !ok {
|
|
return nil, fmt.Errorf("config for router with name %q not found", routerName)
|
|
}
|
|
|
|
var router routing.Routing
|
|
var err error
|
|
switch cfg.Type {
|
|
case config.RouterTypeHTTP:
|
|
router, err = httpRoutingFromConfig(cfg.Router, extraHTTP)
|
|
case config.RouterTypeDHT:
|
|
router, err = dhtRoutingFromConfig(cfg.Router, extraDHT)
|
|
case config.RouterTypeParallel:
|
|
crp := cfg.Parameters.(*config.ComposableRouterParams)
|
|
var pr []*routinghelpers.ParallelRouter
|
|
for _, cr := range crp.Routers {
|
|
ri, err := parse(visited, createdRouters, cr.RouterName, routersCfg, extraDHT, extraHTTP)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pr = append(pr, &routinghelpers.ParallelRouter{
|
|
Router: ri,
|
|
IgnoreError: cr.IgnoreErrors,
|
|
DoNotWaitForSearchValue: true,
|
|
Timeout: cr.Timeout.Duration,
|
|
ExecuteAfter: cr.ExecuteAfter.WithDefault(0),
|
|
})
|
|
|
|
}
|
|
|
|
router = routinghelpers.NewComposableParallel(pr)
|
|
case config.RouterTypeSequential:
|
|
crp := cfg.Parameters.(*config.ComposableRouterParams)
|
|
var sr []*routinghelpers.SequentialRouter
|
|
for _, cr := range crp.Routers {
|
|
ri, err := parse(visited, createdRouters, cr.RouterName, routersCfg, extraDHT, extraHTTP)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sr = append(sr, &routinghelpers.SequentialRouter{
|
|
Router: ri,
|
|
IgnoreError: cr.IgnoreErrors,
|
|
Timeout: cr.Timeout.Duration,
|
|
})
|
|
|
|
}
|
|
|
|
router = routinghelpers.NewComposableSequential(sr)
|
|
default:
|
|
return nil, fmt.Errorf("unknown router type %q", cfg.Type)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
createdRouters[routerName] = router
|
|
|
|
log.Info("created router ", routerName, " with params ", cfg.Parameters)
|
|
|
|
return router, nil
|
|
}
|
|
|
|
type ExtraHTTPParams struct {
|
|
PeerID string
|
|
Addrs []string
|
|
PrivKeyB64 string
|
|
HTTPRetrieval bool
|
|
}
|
|
|
|
func ConstructHTTPRouter(endpoint string, peerID string, addrs []string, privKey string, httpRetrieval bool) (routing.Routing, error) {
|
|
return httpRoutingFromConfig(
|
|
config.Router{
|
|
Type: "http",
|
|
Parameters: &config.HTTPRouterParams{
|
|
Endpoint: endpoint,
|
|
},
|
|
},
|
|
&ExtraHTTPParams{
|
|
PeerID: peerID,
|
|
Addrs: addrs,
|
|
PrivKeyB64: privKey,
|
|
HTTPRetrieval: httpRetrieval,
|
|
},
|
|
)
|
|
}
|
|
|
|
func httpRoutingFromConfig(conf config.Router, extraHTTP *ExtraHTTPParams) (routing.Routing, error) {
|
|
params := conf.Parameters.(*config.HTTPRouterParams)
|
|
if params.Endpoint == "" {
|
|
return nil, NewParamNeededErr("Endpoint", conf.Type)
|
|
}
|
|
|
|
params.FillDefaults()
|
|
|
|
// Increase per-host connection pool since we are making lots of concurrent requests.
|
|
transport := http.DefaultTransport.(*http.Transport).Clone()
|
|
transport.MaxIdleConns = 500
|
|
transport.MaxIdleConnsPerHost = 100
|
|
|
|
delegateHTTPClient := &http.Client{
|
|
Transport: &drclient.ResponseBodyLimitedTransport{
|
|
RoundTripper: otelhttp.NewTransport(transport,
|
|
otelhttp.WithSpanNameFormatter(func(operation string, req *http.Request) string {
|
|
if req.Method == http.MethodGet {
|
|
switch {
|
|
case strings.HasPrefix(req.URL.Path, "/routing/v1/providers"):
|
|
return "DelegatedHTTPClient.FindProviders"
|
|
case strings.HasPrefix(req.URL.Path, "/routing/v1/peers"):
|
|
return "DelegatedHTTPClient.FindPeers"
|
|
case strings.HasPrefix(req.URL.Path, "/routing/v1/ipns"):
|
|
return "DelegatedHTTPClient.GetIPNS"
|
|
}
|
|
} else if req.Method == http.MethodPut {
|
|
switch {
|
|
case strings.HasPrefix(req.URL.Path, "/routing/v1/ipns"):
|
|
return "DelegatedHTTPClient.PutIPNS"
|
|
}
|
|
}
|
|
return "DelegatedHTTPClient." + path.Dir(req.URL.Path)
|
|
}),
|
|
),
|
|
LimitBytes: 1 << 20,
|
|
},
|
|
}
|
|
|
|
key, err := decodePrivKey(extraHTTP.PrivKeyB64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
addrInfo, err := createAddrInfo(extraHTTP.PeerID, extraHTTP.Addrs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
protocols := config.DefaultHTTPRoutersFilterProtocols
|
|
if extraHTTP.HTTPRetrieval {
|
|
protocols = append(protocols, "transport-ipfs-gateway-http")
|
|
}
|
|
|
|
cli, err := drclient.New(
|
|
params.Endpoint,
|
|
drclient.WithHTTPClient(delegateHTTPClient),
|
|
drclient.WithIdentity(key),
|
|
drclient.WithProviderInfo(addrInfo.ID, addrInfo.Addrs),
|
|
drclient.WithUserAgent(version.GetUserAgentVersion()),
|
|
drclient.WithProtocolFilter(protocols),
|
|
drclient.WithStreamResultsRequired(), // https://specs.ipfs.tech/routing/http-routing-v1/#streaming
|
|
drclient.WithDisabledLocalFiltering(false), // force local filtering in case remote server does not support IPIP-484
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cr := contentrouter.NewContentRoutingClient(
|
|
cli,
|
|
contentrouter.WithMaxProvideBatchSize(params.MaxProvideBatchSize),
|
|
contentrouter.WithMaxProvideConcurrency(params.MaxProvideConcurrency),
|
|
)
|
|
|
|
err = view.Register(drclient.OpenCensusViews...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("registering HTTP delegated routing views: %w", err)
|
|
}
|
|
|
|
return &httpRoutingWrapper{
|
|
ContentRouting: cr,
|
|
PeerRouting: cr,
|
|
ValueStore: cr,
|
|
ProvideManyRouter: cr,
|
|
}, nil
|
|
}
|
|
|
|
func decodePrivKey(keyB64 string) (ic.PrivKey, error) {
|
|
pk, err := base64.StdEncoding.DecodeString(keyB64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ic.UnmarshalPrivateKey(pk)
|
|
}
|
|
|
|
func createAddrInfo(peerID string, addrs []string) (peer.AddrInfo, error) {
|
|
pID, err := peer.Decode(peerID)
|
|
if err != nil {
|
|
return peer.AddrInfo{}, err
|
|
}
|
|
|
|
var mas []ma.Multiaddr
|
|
for _, a := range addrs {
|
|
m, err := ma.NewMultiaddr(a)
|
|
if err != nil {
|
|
return peer.AddrInfo{}, err
|
|
}
|
|
|
|
mas = append(mas, m)
|
|
}
|
|
|
|
return peer.AddrInfo{
|
|
ID: pID,
|
|
Addrs: mas,
|
|
}, nil
|
|
}
|
|
|
|
type ExtraDHTParams struct {
|
|
BootstrapPeers []peer.AddrInfo
|
|
Host host.Host
|
|
Validator record.Validator
|
|
Datastore datastore.Batching
|
|
Context context.Context
|
|
}
|
|
|
|
func dhtRoutingFromConfig(conf config.Router, extra *ExtraDHTParams) (routing.Routing, error) {
|
|
params, ok := conf.Parameters.(*config.DHTRouterParams)
|
|
if !ok {
|
|
return nil, errors.New("incorrect params for DHT router")
|
|
}
|
|
|
|
if params.AcceleratedDHTClient {
|
|
return createFullRT(extra)
|
|
}
|
|
|
|
var mode dht.ModeOpt
|
|
switch params.Mode {
|
|
case config.DHTModeAuto:
|
|
mode = dht.ModeAuto
|
|
case config.DHTModeClient:
|
|
mode = dht.ModeClient
|
|
case config.DHTModeServer:
|
|
mode = dht.ModeServer
|
|
default:
|
|
return nil, fmt.Errorf("invalid DHT mode: %q", params.Mode)
|
|
}
|
|
|
|
return createDHT(extra, params.PublicIPNetwork, mode)
|
|
}
|
|
|
|
func createDHT(params *ExtraDHTParams, public bool, mode dht.ModeOpt) (routing.Routing, error) {
|
|
var opts []dht.Option
|
|
|
|
if public {
|
|
opts = append(opts, dht.QueryFilter(dht.PublicQueryFilter),
|
|
dht.RoutingTableFilter(dht.PublicRoutingTableFilter),
|
|
dht.RoutingTablePeerDiversityFilter(dht.NewRTPeerDiversityFilter(params.Host, 2, 3)))
|
|
} else {
|
|
opts = append(opts, dht.ProtocolExtension(dual.LanExtension),
|
|
dht.QueryFilter(dht.PrivateQueryFilter),
|
|
dht.RoutingTableFilter(dht.PrivateRoutingTableFilter))
|
|
}
|
|
|
|
opts = append(opts,
|
|
dht.Concurrency(10),
|
|
dht.Mode(mode),
|
|
dht.Datastore(params.Datastore),
|
|
dht.Validator(params.Validator),
|
|
dht.BootstrapPeers(params.BootstrapPeers...))
|
|
|
|
return dht.New(
|
|
params.Context, params.Host, opts...,
|
|
)
|
|
}
|
|
|
|
func createFullRT(params *ExtraDHTParams) (routing.Routing, error) {
|
|
return fullrt.NewFullRT(params.Host,
|
|
dht.DefaultPrefix,
|
|
fullrt.DHTOption(
|
|
dht.Validator(params.Validator),
|
|
dht.Datastore(params.Datastore),
|
|
dht.BootstrapPeers(params.BootstrapPeers...),
|
|
dht.BucketSize(20),
|
|
),
|
|
)
|
|
}
|