Skip to content

Commit

Permalink
Make Resolver return TTL information
Browse files Browse the repository at this point in the history
License: MIT
Signed-off-by: Johan Kiviniemi <[email protected]>
  • Loading branch information
ion1 committed Nov 5, 2015
1 parent 173c096 commit 0534038
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 109 deletions.
20 changes: 15 additions & 5 deletions core/corehttp/gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,26 @@ import (

type mockNamesys map[string]path.Path

func (m mockNamesys) Resolve(ctx context.Context, name string) (value path.Path, err error) {
return m.ResolveN(ctx, name, namesys.DefaultDepthLimit)
func (m mockNamesys) Resolve(ctx context.Context, name string) (path.Path, error) {
res, err := m.ResolveFull(ctx, name)
return res.Path, err
}

func (m mockNamesys) ResolveN(ctx context.Context, name string, depth int) (value path.Path, err error) {
func (m mockNamesys) ResolveN(ctx context.Context, name string, depth int) (path.Path, error) {
res, err := m.ResolveNFull(ctx, name, depth)
return res.Path, err
}

func (m mockNamesys) ResolveFull(ctx context.Context, name string) (namesys.ResolveResult, error) {
return m.ResolveNFull(ctx, name, namesys.DefaultDepthLimit)
}

func (m mockNamesys) ResolveNFull(ctx context.Context, name string, depth int) (namesys.ResolveResult, error) {
p, ok := m[name]
if !ok {
return "", namesys.ErrResolveFailed
return namesys.ResolveResult{}, namesys.ErrResolveFailed
}
return p, nil
return namesys.ResolveResult{p, 0}, nil
}

func (m mockNamesys) Publish(ctx context.Context, name ci.PrivKey, value path.Path) error {
Expand Down
36 changes: 28 additions & 8 deletions core/pathresolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package core
import (
"errors"
"strings"
"time"

context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"

merkledag "github.com/ipfs/go-ipfs/merkledag"
namesys "github.com/ipfs/go-ipfs/namesys"
path "github.com/ipfs/go-ipfs/path"
)

Expand All @@ -20,38 +22,56 @@ var ErrNoNamesys = errors.New(
// entries and returning the final merkledag node. Effectively
// enables /ipns/, /dns/, etc. in commands.
func Resolve(ctx context.Context, n *IpfsNode, p path.Path) (*merkledag.Node, error) {
res, err := ResolveFull(ctx, n, p)
return res.Node, err
}

// ResolveFull is like Resolve but also returns a time-to-live value which
// indicates the maximum amount of time the result may be cached.
func ResolveFull(ctx context.Context, n *IpfsNode, p path.Path) (ResolveResult, error) {
ttl := namesys.ImmutableTTL

if strings.HasPrefix(p.String(), "/ipns/") {
// resolve ipns paths

// TODO(cryptix): we sould be able to query the local cache for the path
if n.Namesys == nil {
return nil, ErrNoNamesys
return ResolveResult{TTL: 0}, ErrNoNamesys
}

seg := p.Segments()

if len(seg) < 2 || seg[1] == "" { // just "/<protocol/>" without further segments
return nil, path.ErrNoComponents
return ResolveResult{TTL: namesys.ImmutableTTL}, path.ErrNoComponents
}

extensions := seg[2:]
resolvable, err := path.FromSegments("/", seg[0], seg[1])
if err != nil {
return nil, err
return ResolveResult{TTL: namesys.ImmutableTTL}, err
}

respath, err := n.Namesys.Resolve(ctx, resolvable.String())
res, err := n.Namesys.ResolveFull(ctx, resolvable.String())
ttl = res.TTL
if err != nil {
return nil, err
return ResolveResult{TTL: ttl}, err
}

segments := append(respath.Segments(), extensions...)
segments := append(res.Path.Segments(), extensions...)
p, err = path.FromSegments("/", segments...)
if err != nil {
return nil, err
return ResolveResult{TTL: ttl}, err
}
}

// ok, we have an ipfs path now (or what we'll treat as one)
return n.Resolver.ResolvePath(ctx, p)
node, err := n.Resolver.ResolvePath(ctx, p)
return ResolveResult{node, ttl}, err
}

// ResolveResult is returned by ResolveFull and holds a time-to-live value in
// addition to the merkledag node.
type ResolveResult struct {
Node *merkledag.Node
TTL time.Duration
}
37 changes: 22 additions & 15 deletions namesys/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,54 @@ import (
"strings"

context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"

path "github.com/ipfs/go-ipfs/path"
)

type resolver interface {
// resolveOnce looks up a name once (without recursion).
resolveOnce(ctx context.Context, name string) (value path.Path, err error)
// resolveOnce looks up a name once (without recursion). It also
// returns a time-to-live value which indicates the maximum amount of
// time the result may be cached.
resolveOnce(ctx context.Context, name string) (res ResolveResult, err error)
}

// resolve is a helper for implementing Resolver.ResolveN using resolveOnce.
func resolve(ctx context.Context, r resolver, name string, depth int, prefixes ...string) (path.Path, error) {
// resolve is a helper for implementing Resolver.ResolveNFull using resolveOnce.
func resolve(ctx context.Context, r resolver, name string, depth int, prefixes ...string) (ResolveResult, error) {
// Start with a long TTL.
ttl := ImmutableTTL

for {
p, err := r.resolveOnce(ctx, name)
res, err := r.resolveOnce(ctx, name)
// Use the lowest TTL reported by the resolveOnce invocations.
if res.TTL < ttl {
ttl = res.TTL
}
if err != nil {
log.Warningf("Could not resolve %s", name)
return "", err
return ResolveResult{TTL: ttl}, err
}
log.Debugf("Resolved %s to %s", name, p.String())
log.Debugf("Resolved %s to %s (TTL %v -> %v)", name, res.Path.String(), res.TTL, ttl)

if strings.HasPrefix(p.String(), "/ipfs/") {
if strings.HasPrefix(res.Path.String(), "/ipfs/") {
// we've bottomed out with an IPFS path
return p, nil
return ResolveResult{res.Path, ttl}, nil
}

if depth == 1 {
return p, ErrResolveRecursion
return ResolveResult{res.Path, ttl}, ErrResolveRecursion
}

matched := false
for _, prefix := range prefixes {
if strings.HasPrefix(p.String(), prefix) {
if strings.HasPrefix(res.Path.String(), prefix) {
matched = true
if len(prefixes) == 1 {
name = strings.TrimPrefix(p.String(), prefix)
name = strings.TrimPrefix(res.Path.String(), prefix)
}
break
}
}

if !matched {
return p, nil
return ResolveResult{res.Path, ttl}, nil
}

if depth > 1 {
Expand Down
29 changes: 23 additions & 6 deletions namesys/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,36 +33,53 @@ func newDNSResolver() resolver {

// Resolve implements Resolver.
func (r *DNSResolver) Resolve(ctx context.Context, name string) (path.Path, error) {
return r.ResolveN(ctx, name, DefaultDepthLimit)
res, err := r.ResolveFull(ctx, name)
return res.Path, err
}

// ResolveN implements Resolver.
func (r *DNSResolver) ResolveN(ctx context.Context, name string, depth int) (path.Path, error) {
res, err := r.ResolveNFull(ctx, name, depth)
return res.Path, err
}

// ResolveFull implements Resolver.
func (r *DNSResolver) ResolveFull(ctx context.Context, name string) (ResolveResult, error) {
return r.ResolveNFull(ctx, name, DefaultDepthLimit)
}

// ResolveNFull implements Resolver.
func (r *DNSResolver) ResolveNFull(ctx context.Context, name string, depth int) (ResolveResult, error) {
return resolve(ctx, r, name, depth, "/ipns/")
}

// resolveOnce implements resolver.
// TXT records for a given domain name should contain a b58
// encoded multihash.
func (r *DNSResolver) resolveOnce(ctx context.Context, name string) (path.Path, error) {
func (r *DNSResolver) resolveOnce(ctx context.Context, name string) (ResolveResult, error) {
if !isd.IsDomain(name) {
return "", errors.New("not a valid domain name")
return ResolveResult{TTL: UnknownTTL}, errors.New("not a valid domain name")
}

log.Infof("DNSResolver resolving %s", name)

// XXX: net.LookupTXT does not provide TTL information, use UnknownTTL
// for now.
ttl := UnknownTTL

txt, err := r.lookupTXT(name)
if err != nil {
return "", err
return ResolveResult{TTL: ttl}, err
}

for _, t := range txt {
p, err := parseEntry(t)
if err == nil {
return p, nil
return ResolveResult{p, ttl}, nil
}
}

return "", ErrResolveFailed
return ResolveResult{TTL: ttl}, ErrResolveFailed
}

func parseEntry(txt string) (path.Path, error) {
Expand Down
32 changes: 16 additions & 16 deletions namesys/dns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,20 +93,20 @@ func newMockDNS() *mockDNS {
func TestDNSResolution(t *testing.T) {
mock := newMockDNS()
r := &DNSResolver{lookupTXT: mock.lookupTXT}
testResolution(t, r, "multihash.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", nil)
testResolution(t, r, "ipfs.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", nil)
testResolution(t, r, "dns1.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", nil)
testResolution(t, r, "dns1.example.com", 1, "/ipns/ipfs.example.com", ErrResolveRecursion)
testResolution(t, r, "dns2.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", nil)
testResolution(t, r, "dns2.example.com", 1, "/ipns/dns1.example.com", ErrResolveRecursion)
testResolution(t, r, "dns2.example.com", 2, "/ipns/ipfs.example.com", ErrResolveRecursion)
testResolution(t, r, "multi.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", nil)
testResolution(t, r, "multi.example.com", 1, "/ipns/dns1.example.com", ErrResolveRecursion)
testResolution(t, r, "multi.example.com", 2, "/ipns/ipfs.example.com", ErrResolveRecursion)
testResolution(t, r, "equals.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD/=equals", nil)
testResolution(t, r, "loop1.example.com", 1, "/ipns/loop2.example.com", ErrResolveRecursion)
testResolution(t, r, "loop1.example.com", 2, "/ipns/loop1.example.com", ErrResolveRecursion)
testResolution(t, r, "loop1.example.com", 3, "/ipns/loop2.example.com", ErrResolveRecursion)
testResolution(t, r, "loop1.example.com", DefaultDepthLimit, "/ipns/loop1.example.com", ErrResolveRecursion)
testResolution(t, r, "bad.example.com", DefaultDepthLimit, "", ErrResolveFailed)
testResolution(t, r, "multihash.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", UnknownTTL, nil)
testResolution(t, r, "ipfs.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", UnknownTTL, nil)
testResolution(t, r, "dns1.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", UnknownTTL, nil)
testResolution(t, r, "dns1.example.com", 1, "/ipns/ipfs.example.com", UnknownTTL, ErrResolveRecursion)
testResolution(t, r, "dns2.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", UnknownTTL, nil)
testResolution(t, r, "dns2.example.com", 1, "/ipns/dns1.example.com", UnknownTTL, ErrResolveRecursion)
testResolution(t, r, "dns2.example.com", 2, "/ipns/ipfs.example.com", UnknownTTL, ErrResolveRecursion)
testResolution(t, r, "multi.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", UnknownTTL, nil)
testResolution(t, r, "multi.example.com", 1, "/ipns/dns1.example.com", UnknownTTL, ErrResolveRecursion)
testResolution(t, r, "multi.example.com", 2, "/ipns/ipfs.example.com", UnknownTTL, ErrResolveRecursion)
testResolution(t, r, "equals.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD/=equals", UnknownTTL, nil)
testResolution(t, r, "loop1.example.com", 1, "/ipns/loop2.example.com", UnknownTTL, ErrResolveRecursion)
testResolution(t, r, "loop1.example.com", 2, "/ipns/loop1.example.com", UnknownTTL, ErrResolveRecursion)
testResolution(t, r, "loop1.example.com", 3, "/ipns/loop2.example.com", UnknownTTL, ErrResolveRecursion)
testResolution(t, r, "loop1.example.com", DefaultDepthLimit, "/ipns/loop1.example.com", UnknownTTL, ErrResolveRecursion)
testResolution(t, r, "bad.example.com", DefaultDepthLimit, "", UnknownTTL, ErrResolveFailed)
}
23 changes: 23 additions & 0 deletions namesys/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ const (
// trust resolution to eventually complete and can't put an upper
// limit on how many steps it will take.
UnlimitedDepth = 0

// ImmutableTTL is the time-to-live value to be reported for immutable
// paths.
ImmutableTTL = 10 * 365 * 24 * time.Hour

// UnknownTTL is the time-to-live value to be reported for mutable
// paths when accurate information is not available.
UnknownTTL = time.Minute
)

// ErrResolveFailed signals an error when attempting to resolve.
Expand Down Expand Up @@ -98,6 +106,21 @@ type Resolver interface {
// Most users should use Resolve, since the default limit works well
// in most real-world situations.
ResolveN(ctx context.Context, name string, depth int) (value path.Path, err error)

// ResolveFull is like Resolve but also returns a time-to-live value
// which indicates the maximum amount of time the result may be cached.
ResolveFull(ctx context.Context, name string) (value ResolveResult, err error)

// ResolveNFull is like ResolveN but also returns a time-to-live value
// which indicates the maximum amount of time the result may be cached.
ResolveNFull(ctx context.Context, name string, depth int) (value ResolveResult, err error)
}

// ResolveResult is returned by ResolveFull and ResolveNFull and holds a
// time-to-live value in addition to the dereferenced path.
type ResolveResult struct {
Path path.Path
TTL time.Duration
}

// Publisher is an object capable of publishing particular names.
Expand Down
34 changes: 21 additions & 13 deletions namesys/namesys.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,46 +39,54 @@ func NewNameSystem(r routing.IpfsRouting, ds ds.Datastore, cachesize int) NameSy
}
}

const DefaultResolverCacheTTL = time.Minute

// Resolve implements Resolver.
func (ns *mpns) Resolve(ctx context.Context, name string) (path.Path, error) {
return ns.ResolveN(ctx, name, DefaultDepthLimit)
res, err := ns.ResolveFull(ctx, name)
return res.Path, err
}

// ResolveN implements Resolver.
func (ns *mpns) ResolveN(ctx context.Context, name string, depth int) (path.Path, error) {
if strings.HasPrefix(name, "/ipfs/") {
return path.ParsePath(name)
}
res, err := ns.ResolveNFull(ctx, name, depth)
return res.Path, err
}

// ResolveFull implements Resolver.
func (ns *mpns) ResolveFull(ctx context.Context, name string) (ResolveResult, error) {
return ns.ResolveNFull(ctx, name, DefaultDepthLimit)
}

if !strings.HasPrefix(name, "/") {
return path.ParsePath("/ipfs/" + name)
// ResolveNFull implements Resolver.
func (ns *mpns) ResolveNFull(ctx context.Context, name string, depth int) (ResolveResult, error) {
if strings.HasPrefix(name, "/ipfs/") || !strings.HasPrefix(name, "/") {
// ParsePath also handles paths without a / prefix.
path, err := path.ParsePath(name)
return ResolveResult{path, ImmutableTTL}, err
}

return resolve(ctx, ns, name, depth, "/ipns/")
}

// resolveOnce implements resolver.
func (ns *mpns) resolveOnce(ctx context.Context, name string) (path.Path, error) {
func (ns *mpns) resolveOnce(ctx context.Context, name string) (ResolveResult, error) {
if !strings.HasPrefix(name, "/ipns/") {
name = "/ipns/" + name
}
segments := strings.SplitN(name, "/", 3)
if len(segments) < 3 || segments[0] != "" {
log.Warningf("Invalid name syntax for %s", name)
return "", ErrResolveFailed
return ResolveResult{TTL: ImmutableTTL}, ErrResolveFailed
}

for protocol, resolver := range ns.resolvers {
log.Debugf("Attempting to resolve %s with %s", name, protocol)
p, err := resolver.resolveOnce(ctx, segments[2])
res, err := resolver.resolveOnce(ctx, segments[2])
if err == nil {
return p, err
return res, err
}
}
log.Warningf("No resolver found for %s", name)
return "", ErrResolveFailed
return ResolveResult{TTL: UnknownTTL}, ErrResolveFailed
}

// Publish implements Publisher
Expand Down
Loading

0 comments on commit 0534038

Please sign in to comment.