Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add implementation of gateway that can serve proofs of correct behavior. #6126

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions core/coreapi/unixfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,17 @@ func (api *UnixfsAPI) Get(ctx context.Context, p coreiface.Path) (files.Node, er
return unixfile.NewUnixfsFile(ctx, ses.dag, nd)
}

func (api *UnixfsAPI) GetWithProof(ctx context.Context, p coreiface.Path) (coreiface.ProofReader, error) {
ses := api.core().getSession(ctx)

nd, err := ses.ResolveNode(ctx, p)
if err != nil {
return nil, err
}

return uio.NewDagReaderWithProof(ctx, nd, ses.dag)
}

// Ls returns the contents of an IPFS or IPNS object(s) at path p, with the format:
// `<link base58 hash> <link size in bytes> <link name>`
func (api *UnixfsAPI) Ls(ctx context.Context, p coreiface.Path, opts ...options.UnixfsLsOption) (<-chan coreiface.DirEntry, error) {
Expand Down
141 changes: 141 additions & 0 deletions core/corehttp/gateway_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import (
"errors"
"fmt"
"io"
"mime"
"net/http"
"net/url"
gopath "path"
"runtime/debug"
"strings"
"sync"
"time"

"github.com/ipfs/go-ipfs/core"
Expand All @@ -25,6 +27,7 @@ import (
"github.com/ipfs/go-path/resolver"
ft "github.com/ipfs/go-unixfs"
"github.com/ipfs/go-unixfs/importer"
uio "github.com/ipfs/go-unixfs/io"
coreiface "github.com/ipfs/interface-go-ipfs-core"
"github.com/libp2p/go-libp2p-routing"
"github.com/multiformats/go-multibase"
Expand Down Expand Up @@ -347,6 +350,144 @@ func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWr
}
}

func (i *gatewayHandler) secureGetHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
urlPath := r.URL.Path
escapedURLPath := r.URL.EscapedPath()

// If the gateway is behind a reverse proxy and mounted at a sub-path,
// the prefix header can be set to signal this sub-path.
// It will be prepended to links in directory listings and the index.html redirect.
prefix := ""
if prfx := r.Header.Get("X-Ipfs-Gateway-Prefix"); len(prfx) > 0 {
for _, p := range i.config.PathPrefixes {
if prfx == p || strings.HasPrefix(prfx, p+"/") {
prefix = prfx
break
}
}
}

// IPNSHostnameOption might have constructed an IPNS path using the Host header.
// In this case, we need the original path for constructing redirects
// and links that match the requested URL.
// For example, http://example.net would become /ipns/example.net, and
// the redirects and links would end up as http://example.net/ipns/example.net
originalUrlPath := prefix + urlPath
if hdr := r.Header.Get("X-Ipns-Original-Path"); len(hdr) > 0 {
originalUrlPath = prefix + hdr
}

parsedPath, err := coreiface.ParsePath(urlPath)
if err != nil {
webError(w, "invalid ipfs path", err, http.StatusBadRequest)
return
}

// Resolve path to the final DAG node for the ETag
preamble := &proofBuffer{}
resolvedPath, err := i.api.ResolvePath(context.WithValue(ctx, "proxy-preamble", preamble), parsedPath)
if err == coreiface.ErrOffline && !i.node.IsOnline {
webError(w, "ipfs resolve -r "+escapedURLPath, err, http.StatusServiceUnavailable)
return
} else if err != nil {
webError(w, "ipfs resolve -r "+escapedURLPath, err, http.StatusNotFound)
return
}

// Check etag send back to us
etag := "\"" + resolvedPath.Cid().String() + "\""
if r.Header.Get("If-None-Match") == etag || r.Header.Get("If-None-Match") == "W/"+etag {
w.WriteHeader(http.StatusNotModified)
return
}

pr, err := i.api.Unixfs().GetWithProof(ctx, resolvedPath)
if err == uio.ErrIsDir {
http.Redirect(w, r, gopath.Join(originalUrlPath, "index.html"), 302)
return
} else if err != nil {
webError(w, "ipfs cat "+escapedURLPath, err, http.StatusNotFound)
return
}
defer pr.Close()

i.addUserHeaders(w) // ok, _now_ write user's headers.
w.Header().Set("X-IPFS-Path", urlPath)
w.Header().Set("Etag", etag)

if strings.HasPrefix(urlPath, ipfsPathPrefix) {
w.Header().Set("Cache-Control", "public, max-age=29030400, immutable")
}
if contentType := mime.TypeByExtension(gopath.Ext(urlPath)); contentType != "" {
w.Header().Set("Content-Type", contentType)
} else {
w.Header().Set("Content-Type", "text/html")
}

if err := copyChunks(w, preamble); err != nil {
log.Warningf("error writing response preamble: %v", err)
} else if err := copyChunks(w, pr); err != nil {
log.Warningf("error writing response body: %v", err)
}
}

// proofBuffer is an in-memory implementation of ProofWriter and ProofReader for
// the gateway's preamble, where proofs of name resolution are provided.
type proofBuffer struct {
mu sync.Mutex
buff [][]byte
}

func (pb *proofBuffer) WriteChunk(data []byte) error {
pb.mu.Lock()
defer pb.mu.Unlock()

pb.buff = append(pb.buff, data)
return nil
}

func (pb *proofBuffer) ReadChunk() ([]byte, error) {
pb.mu.Lock()
defer pb.mu.Unlock()

if len(pb.buff) == 0 {
return nil, io.EOF
}
out := pb.buff[0]
pb.buff = pb.buff[1:]
return out, nil
}

func (pb *proofBuffer) Close() error {
pb.mu.Lock()
defer pb.mu.Unlock()

pb.buff = nil
return nil
}

func copyChunks(w io.Writer, pr coreiface.ProofReader) error {
for {
chunk, err := pr.ReadChunk()
if err == io.EOF {
return nil
} else if err != nil {
return err
}

l := make([]byte, 3)
l[0] = byte(len(chunk) >> 16)
l[1] = byte(len(chunk) >> 8)
l[2] = byte(len(chunk))

if _, err := w.Write(l); err != nil {
return err
} else if _, err := w.Write(chunk); err != nil {
return err
}
}
}

type sizeReadSeeker interface {
Size() (int64, error)

Expand Down
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
github.com/fatih/color v1.7.0 // indirect
github.com/fsnotify/fsnotify v1.4.7
github.com/gogo/protobuf v1.2.1
github.com/golang/protobuf v1.3.0
github.com/hashicorp/golang-lru v0.5.1
github.com/hsanjuan/go-libp2p-http v0.0.2
github.com/ipfs/dir-index-html v1.0.3
Expand Down Expand Up @@ -51,10 +52,10 @@ require (
github.com/ipfs/go-metrics-prometheus v0.0.2
github.com/ipfs/go-mfs v0.0.4
github.com/ipfs/go-path v0.0.3
github.com/ipfs/go-unixfs v0.0.4
github.com/ipfs/go-unixfs v0.0.5-0.20190326013806-f9e9b27cc2bb
github.com/ipfs/go-verifcid v0.0.1
github.com/ipfs/hang-fds v0.0.1
github.com/ipfs/interface-go-ipfs-core v0.0.5
github.com/ipfs/interface-go-ipfs-core v0.0.6-0.20190326010311-c4e66131e60c
github.com/ipfs/iptb v1.4.0
github.com/ipfs/iptb-plugins v0.0.2
github.com/jbenet/go-is-domain v1.0.2
Expand Down Expand Up @@ -91,6 +92,7 @@ require (
github.com/libp2p/go-testutil v0.0.1
github.com/mattn/go-runewidth v0.0.4 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/miekg/dns v1.1.4
github.com/mitchellh/go-homedir v1.1.0
github.com/mr-tron/base58 v1.1.0
github.com/multiformats/go-multiaddr v0.0.1
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ github.com/ipfs/go-unixfs v0.0.3 h1:09koecZaoJVoYy6Wkd/vo1lyQ4AdgAe83eJylQ7gAZw=
github.com/ipfs/go-unixfs v0.0.3/go.mod h1:FX/6aS/Xg95JRc6UMyiMdZeNn+N5VkD8/yfLNwKW0Ks=
github.com/ipfs/go-unixfs v0.0.4 h1:IApzQ+SnY0tfjqM7aU2b80CFYLZNHvhLmEZDIWr4e/E=
github.com/ipfs/go-unixfs v0.0.4/go.mod h1:eIo/p9ADu/MFOuyxzwU+Th8D6xoxU//r590vUpWyfz8=
github.com/ipfs/go-unixfs v0.0.5-0.20190326013806-f9e9b27cc2bb h1:0jADgQuAWDo2nyCg/oS82btTJ1FZuRo8sf0kdgSMU4k=
github.com/ipfs/go-unixfs v0.0.5-0.20190326013806-f9e9b27cc2bb/go.mod h1:3xZc6JK+AWFYDnuLWPL4PX7MccwW3pc09oG8QE58JOI=
github.com/ipfs/go-verifcid v0.0.1 h1:m2HI7zIuR5TFyQ1b79Da5N9dnnCP1vcu2QqawmWlK2E=
github.com/ipfs/go-verifcid v0.0.1/go.mod h1:5Hrva5KBeIog4A+UpqlaIU+DEstipcJYQQZc0g37pY0=
github.com/ipfs/hang-fds v0.0.1 h1:KGAxiGtJPT3THVRNT6yxgpdFPeX4ZemUjENOt6NlOn4=
Expand All @@ -214,6 +216,8 @@ github.com/ipfs/interface-go-ipfs-core v0.0.5-0.20190325175850-33e0648669fb h1:k
github.com/ipfs/interface-go-ipfs-core v0.0.5-0.20190325175850-33e0648669fb/go.mod h1:VceUOYu+kPEy8Ev/gAhzXFTIfc/7xILKnL4fgZg8tZM=
github.com/ipfs/interface-go-ipfs-core v0.0.5 h1:lePQnz+SqDupeDrVWtzEIjZlcYAbG8tJLrttQWRmGRg=
github.com/ipfs/interface-go-ipfs-core v0.0.5/go.mod h1:VceUOYu+kPEy8Ev/gAhzXFTIfc/7xILKnL4fgZg8tZM=
github.com/ipfs/interface-go-ipfs-core v0.0.6-0.20190326010311-c4e66131e60c h1:caBRZYmSPNCrAoFjlg0pEQyVzsWEc2bY3lQddV7Hz7c=
github.com/ipfs/interface-go-ipfs-core v0.0.6-0.20190326010311-c4e66131e60c/go.mod h1:VceUOYu+kPEy8Ev/gAhzXFTIfc/7xILKnL4fgZg8tZM=
github.com/ipfs/iptb v1.4.0 h1:YFYTrCkLMRwk/35IMyC6+yjoQSHTEcNcefBStLJzgvo=
github.com/ipfs/iptb v1.4.0/go.mod h1:1rzHpCYtNp87/+hTxG5TfCVn/yMY3dKnLn8tBiMfdmg=
github.com/ipfs/iptb-plugins v0.0.2 h1:JZp4h/+7f00dY4Epr8gzF+VqKITXmVGsZabvmZp7E9I=
Expand Down
26 changes: 20 additions & 6 deletions namesys/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,48 +6,60 @@ import (
"time"

path "github.com/ipfs/go-path"
coreiface "github.com/ipfs/interface-go-ipfs-core"
opts "github.com/ipfs/interface-go-ipfs-core/options/namesys"
)

type onceResult struct {
value path.Path
proof [][]byte
ttl time.Duration
err error
}

type resolver interface {
resolveOnceAsync(ctx context.Context, name string, options opts.ResolveOpts) <-chan onceResult
resolveOnceAsync(ctx context.Context, name string, needsProof bool, options opts.ResolveOpts) <-chan onceResult
}

// resolve is a helper for implementing Resolver.ResolveN using resolveOnce.
func resolve(ctx context.Context, r resolver, name string, options opts.ResolveOpts) (path.Path, error) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()

err := ErrResolveFailed
var p path.Path
var (
p path.Path
proof [][]byte
err = ErrResolveFailed
)

resCh := resolveAsync(ctx, r, name, options)

for res := range resCh {
p, err = res.Path, res.Err
p, proof, err = res.Path, res.Proof, res.Err
if err != nil {
break
}
}

if pw, ok := ctx.Value("proxy-preamble").(coreiface.ProofWriter); ok {
for _, p := range proof {
pw.WriteChunk(p)
}
}
return p, err
}

func resolveAsync(ctx context.Context, r resolver, name string, options opts.ResolveOpts) <-chan Result {
resCh := r.resolveOnceAsync(ctx, name, options)
_, needsProof := ctx.Value("proxy-preamble").(coreiface.ProofWriter)
resCh := r.resolveOnceAsync(ctx, name, needsProof, options)
depth := options.Depth
outCh := make(chan Result, 1)

go func() {
defer close(outCh)
var subCh <-chan Result
var cancelSub context.CancelFunc
var proofStub [][]byte
defer func() {
if cancelSub != nil {
cancelSub()
Expand All @@ -68,7 +80,7 @@ func resolveAsync(ctx context.Context, r resolver, name string, options opts.Res
}
log.Debugf("resolved %s to %s", name, res.value.String())
if !strings.HasPrefix(res.value.String(), ipnsPrefix) {
emitResult(ctx, outCh, Result{Path: res.value})
emitResult(ctx, outCh, Result{Path: res.value, Proof: res.proof})
break
}

Expand All @@ -92,6 +104,7 @@ func resolveAsync(ctx context.Context, r resolver, name string, options opts.Res

p := strings.TrimPrefix(res.value.String(), ipnsPrefix)
subCh = resolveAsync(subCtx, r, p, subopts)
proofStub = res.proof
case res, ok := <-subCh:
if !ok {
subCh = nil
Expand All @@ -100,6 +113,7 @@ func resolveAsync(ctx context.Context, r resolver, name string, options opts.Res

// We don't bother returning here in case of context timeout as there is
// no good reason to do that, and we may still be able to emit a result
res.Proof = append(proofStub, res.Proof...)
emitResult(ctx, outCh, res)
case <-ctx.Done():
return
Expand Down
22 changes: 12 additions & 10 deletions namesys/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import (
path "github.com/ipfs/go-path"
)

func (ns *mpns) cacheGet(name string) (path.Path, bool) {
func (ns *mpns) cacheGet(name string) (path.Path, [][]byte, bool) {
if ns.cache == nil {
return "", false
return "", nil, false
}

ientry, ok := ns.cache.Get(name)
if !ok {
return "", false
return "", nil, false
}

entry, ok := ientry.(cacheEntry)
Expand All @@ -23,25 +23,27 @@ func (ns *mpns) cacheGet(name string) (path.Path, bool) {
}

if time.Now().Before(entry.eol) {
return entry.val, true
return entry.val, entry.proof, true
}

ns.cache.Remove(name)

return "", false
return "", nil, false
}

func (ns *mpns) cacheSet(name string, val path.Path, ttl time.Duration) {
func (ns *mpns) cacheSet(name string, val path.Path, proof [][]byte, ttl time.Duration) {
if ns.cache == nil || ttl <= 0 {
return
}
ns.cache.Add(name, cacheEntry{
val: val,
eol: time.Now().Add(ttl),
val: val,
proof: proof,
eol: time.Now().Add(ttl),
})
}

type cacheEntry struct {
val path.Path
eol time.Time
val path.Path
proof [][]byte
eol time.Time
}
Loading