routing.go 5.05 KB
Newer Older
1 2 3
package namesys

import (
Jeromy's avatar
Jeromy committed
4
	"context"
5
	"strings"
6
	"time"
7

Dirk McCormick's avatar
Dirk McCormick committed
8
	opts "github.com/ipfs/go-ipfs/namesys/opts"
Steven Allen's avatar
Steven Allen committed
9
	path "gx/ipfs/QmcjwUb36Z16NJkvDX6ccXPqsFswo6AsRXynyXcLLCphV2/go-path"
Jeromy's avatar
Jeromy committed
10

11
	cid "gx/ipfs/QmPSQnBKM9g7BaUcZCvswUJVscQ1ipjmwxN5PXCjkp9EQ7/go-cid"
12
	mh "gx/ipfs/QmPnFwZ2JXKnXgMw8CdBPxn7FWh6LLdjUjxV1fKHuJnkr8/go-multihash"
Steven Allen's avatar
Steven Allen committed
13 14 15 16 17 18
	routing "gx/ipfs/QmVBnJDKhtFXTRVjXKinqpwGu8t1DyNqPKan2iGX8PR8xG/go-libp2p-routing"
	logging "gx/ipfs/QmZChCsSt8DctjceaL56Eibc29CVQq4dGKRXC5JRZ6Ppae/go-log"
	dht "gx/ipfs/QmZVakpN44VAUxs9eXAuUGLFYTCGmSyqSy6hyEKfMv68ME/go-libp2p-kad-dht"
	ipns "gx/ipfs/QmZrmn2BPZbSviQAWeyY2iXkCukmJHv9n7zrLgWU5KgbTb/go-ipns"
	pb "gx/ipfs/QmZrmn2BPZbSviQAWeyY2iXkCukmJHv9n7zrLgWU5KgbTb/go-ipns/pb"
	peer "gx/ipfs/QmbNepETomvmXfz1X5pHNFD2QuPqnqi47dTd94QJWSorQ3/go-libp2p-peer"
Steven Allen's avatar
Steven Allen committed
19
	proto "gx/ipfs/QmdxUuburamoF6zF9qjeQC4WYcWGbWuRmdLacMEsW8ioD8/gogo-protobuf/proto"
20 21
)

Jeromy's avatar
Jeromy committed
22
var log = logging.Logger("namesys")
23

Steven Allen's avatar
Steven Allen committed
24 25
// IpnsResolver implements NSResolver for the main IPFS SFS-like naming
type IpnsResolver struct {
26
	routing routing.ValueStore
27 28
}

Steven Allen's avatar
Steven Allen committed
29
// NewIpnsResolver constructs a name resolver using the IPFS Routing system
30
// to implement SFS-like naming on top.
Steven Allen's avatar
Steven Allen committed
31
func NewIpnsResolver(route routing.ValueStore) *IpnsResolver {
32 33 34
	if route == nil {
		panic("attempt to create resolver with nil routing system")
	}
Steven Allen's avatar
Steven Allen committed
35
	return &IpnsResolver{
36 37
		routing: route,
	}
Jeromy's avatar
Jeromy committed
38 39
}

40
// Resolve implements Resolver.
Steven Allen's avatar
Steven Allen committed
41
func (r *IpnsResolver) Resolve(ctx context.Context, name string, options ...opts.ResolveOpt) (path.Path, error) {
Dirk McCormick's avatar
Dirk McCormick committed
42
	return resolve(ctx, r, name, opts.ProcessOpts(options), "/ipns/")
43 44
}

45
// ResolveAsync implements Resolver.
46 47 48 49
func (r *IpnsResolver) ResolveAsync(ctx context.Context, name string, options ...opts.ResolveOpt) <-chan Result {
	return resolveAsync(ctx, r, name, opts.ProcessOpts(options), "/ipns/")
}

50 51
// resolveOnce implements resolver. Uses the IPFS routing system to
// resolve SFS-like names.
52 53 54
func (r *IpnsResolver) resolveOnceAsync(ctx context.Context, name string, options opts.ResolveOpts) <-chan onceResult {
	out := make(chan onceResult, 1)
	log.Debugf("RoutingResolver resolving %s", name)
55 56
	cancel := func() {}

57 58 59 60 61 62
	if options.DhtTimeout != 0 {
		// Resolution must complete within the timeout
		ctx, cancel = context.WithTimeout(ctx, options.DhtTimeout)
	}

	name = strings.TrimPrefix(name, "/ipns/")
63
	pid, err := peer.IDB58Decode(name)
64 65 66 67
	if err != nil {
		log.Debugf("RoutingResolver: could not convert public key hash %s to peer ID: %s\n", name, err)
		out <- onceResult{err: err}
		close(out)
68
		cancel()
69 70 71 72 73 74 75 76 77 78 79 80 81
		return out
	}

	// Name should be the hash of a public key retrievable from ipfs.
	// We retrieve the public key here to make certain that it's in the peer
	// store before calling GetValue() on the DHT - the DHT will call the
	// ipns validator, which in turn will get the public key from the peer
	// store to verify the record signature
	_, err = routing.GetPublicKey(r.routing, ctx, pid)
	if err != nil {
		log.Debugf("RoutingResolver: could not retrieve public key %s: %s\n", name, err)
		out <- onceResult{err: err}
		close(out)
82
		cancel()
83 84 85 86 87 88 89 90
		return out
	}

	// Use the routing system to get the name.
	// Note that the DHT will call the ipns validator when retrieving
	// the value, which in turn verifies the ipns record signature
	ipnsKey := ipns.RecordKey(pid)

91
	vals, err := r.routing.SearchValue(ctx, ipnsKey, dht.Quorum(int(options.DhtRecordCount)))
92 93 94 95
	if err != nil {
		log.Debugf("RoutingResolver: dht get for name %s failed: %s", name, err)
		out <- onceResult{err: err}
		close(out)
96
		cancel()
97 98 99 100
		return out
	}

	go func() {
101
		defer cancel()
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
		defer close(out)
		for {
			select {
			case val, ok := <-vals:
				if !ok {
					return
				}

				entry := new(pb.IpnsEntry)
				err = proto.Unmarshal(val, entry)
				if err != nil {
					log.Debugf("RoutingResolver: could not unmarshal value for name %s: %s", name, err)
					select {
					case out <- onceResult{err: err}:
					case <-ctx.Done():
					}
					return
				}

				var p path.Path
				// check for old style record:
				if valh, err := mh.Cast(entry.GetValue()); err == nil {
					// Its an old style multihash record
					log.Debugf("encountered CIDv0 ipns entry: %s", valh)
					p = path.FromCid(cid.NewCidV0(valh))
				} else {
					// Not a multihash, probably a new style record
					p, err = path.ParsePath(string(entry.GetValue()))
					if err != nil {
						select {
						case out <- onceResult{err: err}:
						case <-ctx.Done():
						}
						return
					}
				}

				ttl := DefaultResolverCacheTTL
				if entry.Ttl != nil {
					ttl = time.Duration(*entry.Ttl)
				}
				switch eol, err := ipns.GetEOL(entry); err {
				case ipns.ErrUnrecognizedValidity:
					// No EOL.
				case nil:
					ttEol := eol.Sub(time.Now())
					if ttEol < 0 {
						// It *was* valid when we first resolved it.
						ttl = 0
					} else if ttEol < ttl {
						ttl = ttEol
					}
				default:
					log.Errorf("encountered error when parsing EOL: %s", err)
					select {
					case out <- onceResult{err: err}:
					case <-ctx.Done():
					}
					return
				}

				select {
				case out <- onceResult{value: p, ttl: ttl}:
				case <-ctx.Done():
				}
			case <-ctx.Done():
				return
			}
		}
	}()

	return out
}