From 3d0e9877b90cf7d1c497274b4a4f8bbd571682c1 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Mon, 6 Feb 2023 15:55:40 +0100 Subject: [PATCH] feat: add proxy routing via /api/v0/routing/get --- gateway.go | 5 +- go.mod | 4 +- main.go | 6 +-- routing.go | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 132 insertions(+), 14 deletions(-) diff --git a/gateway.go b/gateway.go index 229d7f3..9931bcd 100644 --- a/gateway.go +++ b/gateway.go @@ -38,7 +38,7 @@ type bifrostGateway struct { namesys namesys.NameSystem } -func newBifrostGateway(blockService blockservice.BlockService) (*bifrostGateway, error) { +func newBifrostGateway(blockService blockservice.BlockService, routing routing.ValueStore) (*bifrostGateway, error) { // Setup the DAG services, which use the CAR block store. dagService := merkledag.NewDAGService(blockService) @@ -54,8 +54,7 @@ func newBifrostGateway(blockService blockservice.BlockService) (*bifrostGateway, resolver := resolver.NewBasicResolver(fetcher) // Setup name system for DNSLink and IPNS resolution. - // TODO: NoOpRouting must be replaced by something that can resolve IPNS names. - namesys, err := namesys.NewNameSystem(&NoOpRouting{}) + namesys, err := namesys.NewNameSystem(routing) if err != nil { return nil, err } diff --git a/go.mod b/go.mod index 11e8867..03c5633 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,14 @@ go 1.19 require ( github.com/filecoin-saturn/caboose v0.0.0-20230202180001-ec33a82edca8 + github.com/gogo/protobuf v1.3.2 github.com/ipfs/go-blockservice v0.5.0 github.com/ipfs/go-cid v0.3.2 github.com/ipfs/go-fetcher v1.6.1 github.com/ipfs/go-ipfs-blockstore v1.2.0 github.com/ipfs/go-ipfs-exchange-offline v0.3.0 github.com/ipfs/go-ipld-format v0.4.0 + github.com/ipfs/go-ipns v0.3.0 github.com/ipfs/go-libipfs v0.4.1-0.20230202091244-302b2799386d github.com/ipfs/go-merkledag v0.9.0 github.com/ipfs/go-namesys v0.7.0 @@ -36,7 +38,6 @@ require ( github.com/gabriel-vasile/mimetype v1.4.1 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/uuid v1.3.0 // indirect @@ -54,7 +55,6 @@ require ( github.com/ipfs/go-ipfs-util v0.0.2 // indirect github.com/ipfs/go-ipld-cbor v0.0.6 // indirect github.com/ipfs/go-ipld-legacy v0.1.1 // indirect - github.com/ipfs/go-ipns v0.3.0 // indirect github.com/ipfs/go-log v1.0.5 // indirect github.com/ipfs/go-log/v2 v2.5.1 // indirect github.com/ipfs/go-metrics-interface v0.0.1 // indirect diff --git a/main.go b/main.go index 4540362..aec2365 100644 --- a/main.go +++ b/main.go @@ -107,10 +107,10 @@ func makeGatewayHandler(saturnOrchestrator, saturnLogger string, kuboRPC []strin blockService := blockservice.New(blockStore, offline.Exchange(blockStore)) // // Sets up the routing system, which will proxy the IPNS routing requests to the given gateway. - // routing := newProxyRouting(*gatewayUrlPtr, nil) + routing := newProxyRouting(kuboRPC, nil) // Creates the gateway with the block service and the routing. - gwAPI, err := newBifrostGateway(blockService) + gwAPI, err := newBifrostGateway(blockService, routing) if err != nil { return nil, err } @@ -151,7 +151,7 @@ func newAPIHandler(endpoints []string) http.Handler { rand := rand.New(s) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // TODO: naive redirection. How to choose better? + // Naively choose one of the Kubo RPC clients. endpoint := endpoints[rand.Intn(len(endpoints))] http.Redirect(w, r, endpoint+r.URL.Path+"?"+r.URL.RawQuery, http.StatusFound) }) diff --git a/routing.go b/routing.go index de70ac5..6e31b6f 100644 --- a/routing.go +++ b/routing.go @@ -2,20 +2,139 @@ package main import ( "context" + "encoding/base64" + "fmt" + "io" + "math/rand" + "net/http" + "net/url" + "strings" + "time" + "github.com/gogo/protobuf/proto" + "github.com/ipfs/go-ipns" + ipns_pb "github.com/ipfs/go-ipns/pb" + ic "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/routing" ) -type NoOpRouting struct{} +type proxyRouting struct { + kuboRPC []string + httpClient *http.Client + rand *rand.Rand +} + +func newProxyRouting(kuboRPC []string, client *http.Client) routing.ValueStore { + if client == nil { + client = http.DefaultClient + } + + s := rand.NewSource(time.Now().Unix()) + rand := rand.New(s) -func (r *NoOpRouting) PutValue(context.Context, string, []byte, ...routing.Option) error { + return &proxyRouting{ + kuboRPC: kuboRPC, + httpClient: client, + rand: rand, + } +} + +func (ps *proxyRouting) PutValue(context.Context, string, []byte, ...routing.Option) error { return routing.ErrNotSupported } -func (r *NoOpRouting) GetValue(context.Context, string, ...routing.Option) ([]byte, error) { - return nil, routing.ErrNotSupported +func (ps *proxyRouting) GetValue(ctx context.Context, k string, opts ...routing.Option) ([]byte, error) { + return ps.fetch(ctx, k) } -func (r *NoOpRouting) SearchValue(context.Context, string, ...routing.Option) (<-chan []byte, error) { - return nil, routing.ErrNotSupported +func (ps *proxyRouting) SearchValue(ctx context.Context, k string, opts ...routing.Option) (<-chan []byte, error) { + if !strings.HasPrefix(k, "/ipns/") { + return nil, routing.ErrNotSupported + } + + ch := make(chan []byte) + + go func() { + v, err := ps.fetch(ctx, k) + if err != nil { + close(ch) + } else { + ch <- v + close(ch) + } + }() + + return ch, nil +} + +func (ps *proxyRouting) fetch(ctx context.Context, key string) ([]byte, error) { + key = strings.TrimPrefix(key, "/ipns/") + id, err := peer.IDFromBytes([]byte(key)) + if err != nil { + return nil, err + } + + key = "/ipns/" + peer.ToCid(id).String() + + // Naively choose one of the Kubo RPC clients. + endpoint := ps.kuboRPC[rand.Intn(len(ps.kuboRPC))] + + u, err := url.Parse(fmt.Sprintf("%s/api/v0/routing/get?arg=%s", endpoint, key)) + if err != nil { + return nil, err + } + + resp, err := ps.httpClient.Do(&http.Request{ + Method: http.MethodPost, + URL: u, + }) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status from remote gateway: %s", resp.Status) + } + + rb, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + b64 := string(rb) + b64 = strings.TrimSpace(b64) + b64 = strings.TrimPrefix(b64, "\"") + b64 = strings.TrimSuffix(b64, "\"") + + rb, err = base64.StdEncoding.DecodeString(b64) + if err != nil { + return nil, err + } + + var entry ipns_pb.IpnsEntry + err = proto.Unmarshal(rb, &entry) + if err != nil { + return nil, err + } + + pub, err := id.ExtractPublicKey() + if err != nil { + // Make sure it works with all those RSA that cannot be embedded into the + // Peer ID. + if len(entry.PubKey) > 0 { + pub, err = ic.UnmarshalPublicKey(entry.PubKey) + } + } + if err != nil { + return nil, err + } + + err = ipns.Validate(pub, &entry) + if err != nil { + return nil, err + } + + return rb, nil }