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

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

8
	pb "github.com/ipfs/go-ipfs/namesys/pb"
9
	path "github.com/ipfs/go-ipfs/path"
Jeromy's avatar
Jeromy committed
10

Steven Allen's avatar
Steven Allen committed
11
	u "gx/ipfs/QmNiJuT8Ja3hMVpBHXv3Q6dwmperaQ6JjLtpMQgMCD7xvx/go-ipfs-util"
Steven Allen's avatar
Steven Allen committed
12 13
	logging "gx/ipfs/QmRb5jh8z2E8hMGN2tkvs1yHynUanqnZ3UeKwgN1i9P1F8/go-log"
	routing "gx/ipfs/QmTiWLZ6Fo5j4KcTVutZJ5KWRRJrbxzmxA4td8NfEdrPh7/go-libp2p-routing"
Jeromy's avatar
Jeromy committed
14 15
	lru "gx/ipfs/QmVYxfoJQiZijTgPNHCHgHELvQpbsJNTg6Crmc3dQkj3yy/golang-lru"
	proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
16
	peer "gx/ipfs/QmZoWKhxUmZ2seW4BzX6fJkNR8hh9PsGModr7q171yq2SS/go-libp2p-peer"
Dirk McCormick's avatar
go fmt  
Dirk McCormick committed
17
	mh "gx/ipfs/QmZyZDi491cCNTLfAhwcaDii2Kg4pwKRkhqQzURGDvY6ua/go-multihash"
Steven Allen's avatar
Steven Allen committed
18
	cid "gx/ipfs/QmcZfnkapfECQGcLZaf9B79NRg7cRa9EnZh4LSbkCzwNvY/go-cid"
19 20
)

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

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
23 24
// routingResolver implements NSResolver for the main IPFS SFS-like naming
type routingResolver struct {
25
	routing routing.ValueStore
26 27

	cache *lru.Cache
Jeromy's avatar
Jeromy committed
28 29
}

30 31 32 33 34 35 36 37
func (r *routingResolver) cacheGet(name string) (path.Path, bool) {
	if r.cache == nil {
		return "", false
	}

	ientry, ok := r.cache.Get(name)
	if !ok {
		return "", false
38 39
	}

40 41 42 43 44 45 46 47 48 49 50 51 52
	entry, ok := ientry.(cacheEntry)
	if !ok {
		// should never happen, purely for sanity
		log.Panicf("unexpected type %T in cache for %q.", ientry, name)
	}

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

	r.cache.Remove(name)

	return "", false
53 54
}

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
func (r *routingResolver) cacheSet(name string, val path.Path, rec *pb.IpnsEntry) {
	if r.cache == nil {
		return
	}

	// if completely unspecified, just use one minute
	ttl := DefaultResolverCacheTTL
	if rec.Ttl != nil {
		recttl := time.Duration(rec.GetTtl())
		if recttl >= 0 {
			ttl = recttl
		}
	}

	cacheTil := time.Now().Add(ttl)
	eol, ok := checkEOL(rec)
	if ok && eol.Before(cacheTil) {
		cacheTil = eol
	}

	r.cache.Add(name, cacheEntry{
		val: val,
		eol: cacheTil,
	})
}

type cacheEntry struct {
	val path.Path
	eol time.Time
}

// NewRoutingResolver constructs a name resolver using the IPFS Routing system
// to implement SFS-like naming on top.
// cachesize is the limit of the number of entries in the lru cache. Setting it
// to '0' will disable caching.
90
func NewRoutingResolver(route routing.ValueStore, cachesize int) *routingResolver {
91 92 93 94
	if route == nil {
		panic("attempt to create resolver with nil routing system")
	}

95 96 97 98 99 100 101 102 103
	var cache *lru.Cache
	if cachesize > 0 {
		cache, _ = lru.New(cachesize)
	}

	return &routingResolver{
		routing: route,
		cache:   cache,
	}
Jeromy's avatar
Jeromy committed
104 105
}

106
// Resolve implements Resolver.
107 108
func (r *routingResolver) Resolve(ctx context.Context, name string, opts *ResolveOpts) (path.Path, error) {
	return resolve(ctx, r, name, opts, "/ipns/")
109 110 111 112
}

// resolveOnce implements resolver. Uses the IPFS routing system to
// resolve SFS-like names.
113
func (r *routingResolver) resolveOnce(ctx context.Context, name string, opts *ResolveOpts) (path.Path, error) {
114
	log.Debugf("RoutingResolver resolving %s", name)
115 116 117 118 119
	cached, ok := r.cacheGet(name)
	if ok {
		return cached, nil
	}

120 121 122 123 124 125 126
	if opts.DhtTimeout != 0 {
		// Resolution must complete within the timeout
		var cancel context.CancelFunc
		ctx, cancel = context.WithTimeout(ctx, opts.DhtTimeout)
		defer cancel()
	}

127
	name = strings.TrimPrefix(name, "/ipns/")
128 129
	hash, err := mh.FromB58String(name)
	if err != nil {
Jeromy's avatar
Jeromy committed
130
		// name should be a multihash. if it isn't, error out here.
131
		log.Debugf("RoutingResolver: bad input hash: [%s]\n", name)
132 133 134
		return "", err
	}

135 136 137 138 139 140 141 142 143 144
	// 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, hash)
	if err != nil {
		log.Debugf("RoutingResolver: could not retrieve public key %s: %s\n", name, err)
		return "", err
	}
Jeromy's avatar
Jeromy committed
145

146 147 148 149
	pid, err := peer.IDFromBytes(hash)
	if err != nil {
		log.Debugf("RoutingResolver: could not convert public key hash %s to peer ID: %s\n", name, err)
		return "", err
150
	}
151

152 153 154
	// 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
155
	_, ipnsKey := IpnsKeysForID(pid)
156
	val, err := r.getValue(ctx, ipnsKey, opts)
157 158 159
	if err != nil {
		log.Debugf("RoutingResolver: dht get for name %s failed: %s", name, err)
		return "", err
160 161
	}

162 163 164 165 166 167
	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)
		return "", err
	}
168 169 170 171 172

	// check for old style record:
	valh, err := mh.Cast(entry.GetValue())
	if err != nil {
		// Not a multihash, probably a new record
173 174 175 176 177 178 179
		p, err := path.ParsePath(string(entry.GetValue()))
		if err != nil {
			return "", err
		}

		r.cacheSet(name, p, entry)
		return p, nil
180 181
	} else {
		// Its an old style multihash record
182
		log.Debugf("encountered CIDv0 ipns entry: %s", valh)
Jeromy's avatar
Jeromy committed
183
		p := path.FromCid(cid.NewCidV0(valh))
184 185 186 187 188
		r.cacheSet(name, p, entry)
		return p, nil
	}
}

189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
func (r *routingResolver) getValue(ctx context.Context, ipnsKey string, opts *ResolveOpts) ([]byte, error) {
	// Get specified number of values from the DHT
	vals, err := r.routing.GetValues(ctx, ipnsKey, int(opts.DhtRecordCount))
	if err != nil {
		return nil, err
	}

	// Select the best value
	recs := make([][]byte, 0, len(vals))
	for _, v := range vals {
		if v.Val != nil {
			recs = append(recs, v.Val)
		}
	}

	i, err := IpnsSelectorFunc(ipnsKey, recs)
	if err != nil {
		return nil, err
	}

	best := recs[i]
	if best == nil {
		log.Errorf("GetValues %s yielded record with nil value", ipnsKey)
		return nil, routing.ErrNotFound
	}

	return best, nil
}

218 219 220 221 222 223 224
func checkEOL(e *pb.IpnsEntry) (time.Time, bool) {
	if e.GetValidityType() == pb.IpnsEntry_EOL {
		eol, err := u.ParseRFC3339(string(e.GetValidity()))
		if err != nil {
			return time.Time{}, false
		}
		return eol, true
225
	}
226
	return time.Time{}, false
227
}