From 02cb5f3b32f101076a16d29033c1b0aa8528c18a Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 7 May 2015 15:08:09 -0700 Subject: [PATCH 01/11] namesys/publisher: Drop the 'namesys: ' prefix for the Publish log This is already handled by setup in namesys/routing.go: var log = u.Logger("namesys") --- namesys/publisher.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/namesys/publisher.go b/namesys/publisher.go index 38dd8d08273..b20a47bea57 100644 --- a/namesys/publisher.go +++ b/namesys/publisher.go @@ -42,7 +42,7 @@ func NewRoutingPublisher(route routing.IpfsRouting) Publisher { // Publish implements Publisher. Accepts a keypair and a value, // and publishes it out to the routing system func (p *ipnsPublisher) Publish(ctx context.Context, k ci.PrivKey, value path.Path) error { - log.Debugf("namesys: Publish %s", value) + log.Debugf("Publish %s", value) data, err := createRoutingEntryData(k, value) if err != nil { From 03260a92817c2fe8c8a328b7aa8fbae1f227bbfa Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 17 May 2015 11:49:30 -0700 Subject: [PATCH 02/11] namesys/interface: Expand package docs to discuss mutable names What they are, why you'd use them, and which command-line tools you can use to access this functionality. --- namesys/interface.go | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/namesys/interface.go b/namesys/interface.go index 4ceb3b9d966..74f68612898 100644 --- a/namesys/interface.go +++ b/namesys/interface.go @@ -1,4 +1,32 @@ -// package namesys implements various functionality for the ipns naming system. +/* +Package namesys implements resolvers and publishers for the IPFS +naming system (IPNS). + +The core of IPFS is an immutable, content-addressable Merkle graph. +That works well for many use cases, but doesn't allow you to answer +questions like "what is Alice's current homepage?". The mutable name +system allows Alice to publish information like: + + The current homepage for alice.example.com is + /ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj + +or: + + The current homepage for node + QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + is + /ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj + +The mutable name system also allows users to resolve those references +to find the immutable IPFS object currently referenced by a given +mutable name. + +For command-line bindings to this functionality, see: + + ipfs name + ipfs dns + ipfs resolve +*/ package namesys import ( From 04a969835eb06efbb0895a202d7c8d0c3b6c0780 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 16 May 2015 09:34:08 -0700 Subject: [PATCH 03/11] namesys/dns: Use SplitN to find dnslink references RFC 6763 requires printable ASCII except '=' for the key [1], but allows any character including '=' in the value [2]. This patch adjusts our parsing to avoid splitting on '=' in the value, and then ignoring anything after that split. [1]: https://tools.ietf.org/html/rfc6763#section-6.4 [2]: https://tools.ietf.org/html/rfc6763#section-6.5 --- namesys/dns.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/namesys/dns.go b/namesys/dns.go index 086adee9eef..3e703d42059 100644 --- a/namesys/dns.go +++ b/namesys/dns.go @@ -52,10 +52,10 @@ func parseEntry(txt string) (path.Path, error) { } func tryParseDnsLink(txt string) (path.Path, error) { - parts := strings.Split(txt, "=") - if len(parts) == 1 || parts[0] != "dnslink" { - return "", errors.New("not a valid dnslink entry") + parts := strings.SplitN(txt, "=", 2) + if len(parts) == 2 && parts[0] == "dnslink" { + return path.ParsePath(parts[1]) } - return path.ParsePath(parts[1]) + return "", errors.New("not a valid dnslink entry") } From 3ead2443e5cd55ef551b811ac0a6764ed65c3ec1 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 7 May 2015 14:31:14 -0700 Subject: [PATCH 04/11] namesys: Add recursive resolution This allows direct access to the earlier protocol-specific Resolve implementations. The guts of each protocol-specific resolver are in the internal resolveOnce method, and we've added a new: ResolveN(ctx, name, depth) method to the public interface. There's also: Resolve(ctx, name) which wraps ResolveN using DefaultDepthLimit. The extra API endpoint is intended to reduce the likelyhood of clients accidentally calling the more dangerous ResolveN with a nonsensically high or infinite depth. On IRC on 2015-05-17, Juan said: 15:34 If 90% of uses is the reduced API with no chance to screw it up, that's a huge win. 15:34 Why would those 90% not just set depth=0 or depth=1, depending on which they need? 15:34 Because people will start writing `r.Resolve(ctx, name, d)` where d is a variable. 15:35 And then accidentally set that variable to some huge number? 15:35 Grom experience, i've seen this happen _dozens_ of times. people screw trivial things up. 15:35 Why won't those same people be using ResolveN? 15:36 Because almost every example they see will tell them to use Resolve(), and they will mostly stay away from ResolveN. The per-prodocol versions also resolve recursively within their protocol. For example: DNSResolver.Resolve(ctx, "ipfs.io", 0) will recursively resolve DNS links until the referenced value is no longer a DNS link. I also renamed the multi-protocol ipfs NameSystem (defined in namesys/namesys.go) to 'mpns' (for Multi-Protocol Name System), because I wasn't clear on whether IPNS applied to the whole system or just to to the DHT-based system. The new name is unambiguously multi-protocol, which is good. It would be nice to have a distinct name for the DHT-based link system. Now that resolver output is always prefixed with a namespace and unprefixed mpns resolver input is interpreted as /ipfs/, core/corehttp/ipns_hostname.go can dispense with it's old manual /ipfs/ injection. Now that the Resolver interface handles recursion, we don't need the resolveRecurse helper in core/pathresolver.go. The pathresolver cleanup also called for an adjustment to FromSegments to more easily get slash-prefixed paths. Now that recursive resolution with the namesys/namesys.go composite resolver always gets you to an /ipfs/... path, there's no need for the /ipns/ special case in fuse/ipns/ipns_unix.go. Now that DNS links can be things other than /ipfs/ or DHT-link references (e.g. they could be /ipns/ references) I've also loosened the ParsePath logic to only attempt multihash validation on IPFS paths. It checks to ensure that other paths have a known-protocol prefix, but otherwise leaves them alone. I also changed some key-stringification from .Pretty() to .String() following the potential deprecation mentioned in util/key.go. --- core/commands/resolve.go | 2 +- core/corehttp/gateway_test.go | 9 ++-- core/corehttp/ipns_hostname.go | 2 +- core/pathresolver.go | 63 +++++++++------------------- fuse/ipns/ipns_test.go | 8 ++-- fuse/ipns/ipns_unix.go | 3 -- ipnsfs/system.go | 2 +- namesys/base.go | 54 ++++++++++++++++++++++++ namesys/dns.go | 23 ++++++---- namesys/interface.go | 40 ++++++++++++++++-- namesys/namesys.go | 76 ++++++++++++++++++++++------------ namesys/proquint.go | 20 +++++---- namesys/routing.go | 25 ++++++++--- path/path.go | 20 ++++----- path/resolver_test.go | 4 +- 15 files changed, 229 insertions(+), 122 deletions(-) create mode 100644 namesys/base.go diff --git a/core/commands/resolve.go b/core/commands/resolve.go index e84d9dabe66..2d9b5906e2f 100644 --- a/core/commands/resolve.go +++ b/core/commands/resolve.go @@ -75,7 +75,7 @@ Resolve te value of another name: name = req.Arguments()[0] } - output, err := n.Namesys.Resolve(n.Context(), name) + output, err := n.Namesys.Resolve(n.Context(), "/ipns/"+name) if err != nil { res.SetError(err, cmds.ErrNormal) return diff --git a/core/corehttp/gateway_test.go b/core/corehttp/gateway_test.go index 818338c1c8a..01d4295b719 100644 --- a/core/corehttp/gateway_test.go +++ b/core/corehttp/gateway_test.go @@ -22,6 +22,10 @@ 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) ResolveN(ctx context.Context, name string, depth int) (value path.Path, err error) { p, ok := m[name] if !ok { return "", namesys.ErrResolveFailed @@ -29,11 +33,6 @@ func (m mockNamesys) Resolve(ctx context.Context, name string) (value path.Path, return p, nil } -func (m mockNamesys) CanResolve(name string) bool { - _, ok := m[name] - return ok -} - func (m mockNamesys) Publish(ctx context.Context, name ci.PrivKey, value path.Path) error { return errors.New("not implemented for mockNamesys") } diff --git a/core/corehttp/ipns_hostname.go b/core/corehttp/ipns_hostname.go index 7361001d1e4..a6e8e91f5bb 100644 --- a/core/corehttp/ipns_hostname.go +++ b/core/corehttp/ipns_hostname.go @@ -20,7 +20,7 @@ func IPNSHostnameOption() ServeOption { host := strings.SplitN(r.Host, ":", 2)[0] if p, err := n.Namesys.Resolve(ctx, host); err == nil { - r.URL.Path = "/ipfs/" + p.String() + r.URL.Path + r.URL.Path = p.String() + r.URL.Path } childMux.ServeHTTP(w, r) }) diff --git a/core/pathresolver.go b/core/pathresolver.go index d439e2082c8..f73a83fd364 100644 --- a/core/pathresolver.go +++ b/core/pathresolver.go @@ -2,7 +2,6 @@ package core import ( "errors" - "fmt" "strings" context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" @@ -11,64 +10,42 @@ import ( path "github.com/ipfs/go-ipfs/path" ) -const maxLinks = 32 +// ErrNoNamesys is an explicit error for when an IPFS node doesn't +// (yet) have a name system +var ErrNoNamesys = errors.New( + "core/resolve: no Namesys on IpfsNode - can't resolve ipns entry") -// errors returned by Resolve function -var ( - ErrTooManyLinks = errors.New("core/resolve: exceeded maximum number of links in ipns entry") - ErrNoNamesys = errors.New("core/resolve: no Namesys on IpfsNode - can't resolve ipns entry") -) - -// Resolve resolves the given path by parsing out /ipns/ entries and then going -// through the /ipfs/ entries and returning the final merkledage node. -// Effectively enables /ipns/ in CLI commands. +// Resolve resolves the given path by parsing out protocol-specific +// entries (e.g. /ipns/) and then going through the /ipfs/ +// entries and returning the final merkledage node. Effectively +// enables /ipns/, /dns/, etc. in commands. func Resolve(ctx context.Context, n *IpfsNode, p path.Path) (*merkledag.Node, error) { - r := resolver{ctx, n, p} - return r.resolveRecurse(0) -} - -type resolver struct { - ctx context.Context - n *IpfsNode - p path.Path -} - -func (r *resolver) resolveRecurse(depth int) (*merkledag.Node, error) { - if depth >= maxLinks { - return nil, ErrTooManyLinks - } - // for now, we only try to resolve ipns paths if - // they begin with "/ipns/". Otherwise, ambiguity - // emerges when resolving just a . Is it meant - // to be an ipfs or an ipns resolution? - - if strings.HasPrefix(r.p.String(), "/ipns/") { + if strings.HasPrefix(p.String(), "/") { + // namespaced path (/ipfs/..., /ipns/..., etc.) // TODO(cryptix): we sould be able to query the local cache for the path - if r.n.Namesys == nil { + if n.Namesys == nil { return nil, ErrNoNamesys } - // if it's an ipns path, try to resolve it. - // if we can't, we can give that error back to the user. - seg := r.p.Segments() - if len(seg) < 2 || seg[1] == "" { // just "/ipns/" - return nil, fmt.Errorf("invalid path: %s", string(r.p)) - } - ipnsPath := seg[1] + seg := p.Segments() extensions := seg[2:] - respath, err := r.n.Namesys.Resolve(r.ctx, ipnsPath) + resolvable, err := path.FromSegments("/", seg[0], seg[1]) + if err != nil { + return nil, err + } + + respath, err := n.Namesys.Resolve(ctx, resolvable.String()) if err != nil { return nil, err } segments := append(respath.Segments(), extensions...) - r.p, err = path.FromSegments(segments...) + p, err = path.FromSegments("/", segments...) if err != nil { return nil, err } - return r.resolveRecurse(depth + 1) } // ok, we have an ipfs path now (or what we'll treat as one) - return r.n.Resolver.ResolvePath(r.ctx, r.p) + return n.Resolver.ResolvePath(ctx, p) } diff --git a/fuse/ipns/ipns_test.go b/fuse/ipns/ipns_test.go index d3dda0658fe..8ac2c403a27 100644 --- a/fuse/ipns/ipns_test.go +++ b/fuse/ipns/ipns_test.go @@ -462,7 +462,7 @@ func TestFastRepublish(t *testing.T) { if err != nil { t.Fatal(err) } - pubkeyHash := u.Key(h).Pretty() + pubkeyPath := "/ipns/" + u.Key(h).String() // set them back defer func() { @@ -482,9 +482,9 @@ func TestFastRepublish(t *testing.T) { writeFileData(t, dataA, fname) // random <-time.After(shortRepublishTimeout * 2) log.Debug("resolving first hash") - resolvedHash, err := node.Namesys.Resolve(context.Background(), pubkeyHash) + resolvedHash, err := node.Namesys.Resolve(context.Background(), pubkeyPath) if err != nil { - t.Fatal("resolve err:", pubkeyHash, err) + t.Fatal("resolve err:", pubkeyPath, err) } // constantly keep writing to the file @@ -501,7 +501,7 @@ func TestFastRepublish(t *testing.T) { }(shortRepublishTimeout) hasPublished := func() bool { - res, err := node.Namesys.Resolve(context.Background(), pubkeyHash) + res, err := node.Namesys.Resolve(context.Background(), pubkeyPath) if err != nil { t.Fatalf("resolve err: %v", err) } diff --git a/fuse/ipns/ipns_unix.go b/fuse/ipns/ipns_unix.go index 8f2a772a2f4..6cf39c9f843 100644 --- a/fuse/ipns/ipns_unix.go +++ b/fuse/ipns/ipns_unix.go @@ -150,9 +150,6 @@ func (s *Root) Lookup(ctx context.Context, name string) (fs.Node, error) { if segments[0] == "ipfs" { p := strings.Join(resolved.Segments()[1:], "/") return &Link{s.IpfsRoot + "/" + p}, nil - } else if segments[0] == "ipns" { - p := strings.Join(resolved.Segments()[1:], "/") - return &Link{s.IpnsRoot + "/" + p}, nil } else { log.Error("Invalid path.Path: ", resolved) return nil, errors.New("invalid path from ipns record") diff --git a/ipnsfs/system.go b/ipnsfs/system.go index ecdb4c4a135..46b33425599 100644 --- a/ipnsfs/system.go +++ b/ipnsfs/system.go @@ -141,7 +141,7 @@ func (fs *Filesystem) newKeyRoot(parent context.Context, k ci.PrivKey) (*KeyRoot return nil, err } - name := u.Key(hash).Pretty() + name := "/ipns/" + u.Key(hash).String() root := new(KeyRoot) root.key = k diff --git a/namesys/base.go b/namesys/base.go new file mode 100644 index 00000000000..e552fce464a --- /dev/null +++ b/namesys/base.go @@ -0,0 +1,54 @@ +package namesys + +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) +} + +// 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) { + for { + p, err := r.resolveOnce(ctx, name) + if err != nil { + log.Warningf("Could not resolve %s", name) + return "", err + } + log.Debugf("Resolved %s to %s", name, p.String()) + + if strings.HasPrefix(p.String(), "/ipfs/") { + // we've bottomed out with an IPFS path + return p, nil + } + + if depth == 1 { + return p, ErrResolveRecursion + } + + matched := false + for _, prefix := range prefixes { + if strings.HasPrefix(p.String(), prefix) { + matched = true + if len(prefixes) == 1 { + name = strings.TrimPrefix(p.String(), prefix) + } + break + } + } + + if !matched { + return p, nil + } + + if depth > 1 { + depth-- + } + } +} diff --git a/namesys/dns.go b/namesys/dns.go index 3e703d42059..f57ddce5901 100644 --- a/namesys/dns.go +++ b/namesys/dns.go @@ -17,16 +17,25 @@ type DNSResolver struct { // cache would need a timeout } -// CanResolve implements Resolver -func (r *DNSResolver) CanResolve(name string) bool { - return isd.IsDomain(name) +// Resolve implements Resolver. +func (r *DNSResolver) Resolve(ctx context.Context, name string) (path.Path, error) { + return r.ResolveN(ctx, name, DefaultDepthLimit) +} + +// ResolveN implements Resolver. +func (r *DNSResolver) ResolveN(ctx context.Context, name string, depth int) (path.Path, error) { + return resolve(ctx, r, name, depth, "/ipns/") } -// Resolve implements Resolver +// resolveOnce implements resolver. // TXT records for a given domain name should contain a b58 // encoded multihash. -func (r *DNSResolver) Resolve(ctx context.Context, name string) (path.Path, error) { - log.Info("DNSResolver resolving %v", name) +func (r *DNSResolver) resolveOnce(ctx context.Context, name string) (path.Path, error) { + if !isd.IsDomain(name) { + return "", errors.New("not a valid domain name") + } + + log.Infof("DNSResolver resolving %s", name) txt, err := net.LookupTXT(name) if err != nil { return "", err @@ -43,7 +52,7 @@ func (r *DNSResolver) Resolve(ctx context.Context, name string) (path.Path, erro } func parseEntry(txt string) (path.Path, error) { - p, err := path.ParseKeyToPath(txt) + p, err := path.ParseKeyToPath(txt) // bare IPFS multihashes if err == nil { return p, nil } diff --git a/namesys/interface.go b/namesys/interface.go index 74f68612898..5903c78a394 100644 --- a/namesys/interface.go +++ b/namesys/interface.go @@ -37,9 +37,24 @@ import ( path "github.com/ipfs/go-ipfs/path" ) +const ( + // DefaultDepthLimit is the default depth limit used by Resolve. + DefaultDepthLimit = 32 + + // UnlimitedDepth allows infinite recursion in ResolveN. You + // probably don't want to use this, but it's here if you absolutely + // trust resolution to eventually complete and can't put an upper + // limit on how many steps it will take. + UnlimitedDepth = 0 +) + // ErrResolveFailed signals an error when attempting to resolve. var ErrResolveFailed = errors.New("could not resolve name.") +// ErrResolveRecursion signals a recursion-depth limit. +var ErrResolveRecursion = errors.New( + "could not resolve name (recursion limit exceeded).") + // ErrPublishFailed signals an error when attempting to publish. var ErrPublishFailed = errors.New("could not publish name.") @@ -58,11 +73,30 @@ type NameSystem interface { // Resolver is an object capable of resolving names. type Resolver interface { - // Resolve looks up a name, and returns the value previously published. + // Resolve performs a recursive lookup, returning the dereferenced + // path. For example, if ipfs.io has a DNS TXT record pointing to + // /ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + // and there is a DHT IPNS entry for + // QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + // -> /ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj + // then + // Resolve(ctx, "/ipns/ipfs.io") + // will resolve both names, returning + // /ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj + // + // There is a default depth-limit to avoid infinite recursion. Most + // users will be fine with this default limit, but if you need to + // adjust the limit you can use ResolveN. Resolve(ctx context.Context, name string) (value path.Path, err error) - // CanResolve checks whether this Resolver can resolve a name - CanResolve(name string) bool + // ResolveN performs a recursive lookup, returning the dereferenced + // path. The only difference from Resolve is that the depth limit + // is configurable. You can use DefaultDepthLimit, UnlimitedDepth, + // or a depth limit of your own choosing. + // + // 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) } // Publisher is an object capable of publishing particular names. diff --git a/namesys/namesys.go b/namesys/namesys.go index 65530772396..0f5b853bea7 100644 --- a/namesys/namesys.go +++ b/namesys/namesys.go @@ -1,59 +1,83 @@ package namesys import ( + "strings" + context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" ci "github.com/ipfs/go-ipfs/p2p/crypto" path "github.com/ipfs/go-ipfs/path" routing "github.com/ipfs/go-ipfs/routing" ) -// ipnsNameSystem implements IPNS naming. +// mpns (a multi-protocol NameSystem) implements generic IPFS naming. // -// Uses three Resolvers: +// Uses several Resolvers: // (a) ipfs routing naming: SFS-like PKI names. // (b) dns domains: resolves using links in DNS TXT records // (c) proquints: interprets string as the raw byte data. // // It can only publish to: (a) ipfs routing naming. // -type ipns struct { - resolvers []Resolver - publisher Publisher +type mpns struct { + resolvers map[string]resolver + publishers map[string]Publisher } // NewNameSystem will construct the IPFS naming system based on Routing func NewNameSystem(r routing.IpfsRouting) NameSystem { - return &ipns{ - resolvers: []Resolver{ - new(DNSResolver), - new(ProquintResolver), - NewRoutingResolver(r), + return &mpns{ + resolvers: map[string]resolver{ + "dns": new(DNSResolver), + "proquint": new(ProquintResolver), + "dht": newRoutingResolver(r), + }, + publishers: map[string]Publisher{ + "/ipns/": NewRoutingPublisher(r), }, - publisher: NewRoutingPublisher(r), } } -// Resolve implements Resolver -func (ns *ipns) Resolve(ctx context.Context, name string) (path.Path, error) { - for _, r := range ns.resolvers { - if r.CanResolve(name) { - return r.Resolve(ctx, name) - } +// Resolve implements Resolver. +func (ns *mpns) Resolve(ctx context.Context, name string) (path.Path, error) { + return ns.ResolveN(ctx, name, DefaultDepthLimit) +} + +// 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) } - return "", ErrResolveFailed + + if !strings.HasPrefix(name, "/") { + return path.ParsePath("/ipfs/" + name) + } + + return resolve(ctx, ns, name, depth, "/ipns/") } -// CanResolve implements Resolver -func (ns *ipns) CanResolve(name string) bool { - for _, r := range ns.resolvers { - if r.CanResolve(name) { - return true +// resolveOnce implements resolver. +func (ns *mpns) resolveOnce(ctx context.Context, name string) (path.Path, 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 + } + + for protocol, resolver := range ns.resolvers { + log.Debugf("Attempting to resolve %s with %s", name, protocol) + p, err := resolver.resolveOnce(ctx, segments[2]) + if err == nil { + return p, err } } - return false + log.Warningf("No resolver found for %s", name) + return "", ErrResolveFailed } // Publish implements Publisher -func (ns *ipns) Publish(ctx context.Context, name ci.PrivKey, value path.Path) error { - return ns.publisher.Publish(ctx, name, value) +func (ns *mpns) Publish(ctx context.Context, name ci.PrivKey, value path.Path) error { + return ns.publishers["/ipns/"].Publish(ctx, name, value) } diff --git a/namesys/proquint.go b/namesys/proquint.go index 66bd54e2428..2ad3275a4ad 100644 --- a/namesys/proquint.go +++ b/namesys/proquint.go @@ -10,16 +10,20 @@ import ( type ProquintResolver struct{} -// CanResolve implements Resolver. Checks whether the name is a proquint string. -func (r *ProquintResolver) CanResolve(name string) bool { - ok, err := proquint.IsProquint(name) - return err == nil && ok +// Resolve implements Resolver. +func (r *ProquintResolver) Resolve(ctx context.Context, name string) (path.Path, error) { + return r.ResolveN(ctx, name, DefaultDepthLimit) } -// Resolve implements Resolver. Decodes the proquint string. -func (r *ProquintResolver) Resolve(ctx context.Context, name string) (path.Path, error) { - ok := r.CanResolve(name) - if !ok { +// ResolveN implements Resolver. +func (r *ProquintResolver) ResolveN(ctx context.Context, name string, depth int) (path.Path, error) { + return resolve(ctx, r, name, depth, "/ipns/") +} + +// resolveOnce implements resolver. Decodes the proquint string. +func (r *ProquintResolver) resolveOnce(ctx context.Context, name string) (path.Path, error) { + ok, err := proquint.IsProquint(name) + if err != nil || !ok { return "", errors.New("not a valid proquint string") } return path.FromString(string(proquint.Decode(name))), nil diff --git a/namesys/routing.go b/namesys/routing.go index 38cb250d0c2..290c06cb2f1 100644 --- a/namesys/routing.go +++ b/namesys/routing.go @@ -30,15 +30,28 @@ func NewRoutingResolver(route routing.IpfsRouting) Resolver { return &routingResolver{routing: route} } -// CanResolve implements Resolver. Checks whether name is a b58 encoded string. -func (r *routingResolver) CanResolve(name string) bool { - _, err := mh.FromB58String(name) - return err == nil +// newRoutingResolver returns a resolver instead of a Resolver. +func newRoutingResolver(route routing.IpfsRouting) resolver { + if route == nil { + panic("attempt to create resolver with nil routing system") + } + + return &routingResolver{routing: route} } -// Resolve implements Resolver. Uses the IPFS routing system to resolve SFS-like -// names. +// Resolve implements Resolver. func (r *routingResolver) Resolve(ctx context.Context, name string) (path.Path, error) { + return r.ResolveN(ctx, name, DefaultDepthLimit) +} + +// ResolveN implements Resolver. +func (r *routingResolver) ResolveN(ctx context.Context, name string, depth int) (path.Path, error) { + return resolve(ctx, r, name, depth, "/ipns/") +} + +// resolveOnce implements resolver. Uses the IPFS routing system to +// resolve SFS-like names. +func (r *routingResolver) resolveOnce(ctx context.Context, name string) (path.Path, error) { log.Debugf("RoutingResolve: '%s'", name) hash, err := mh.FromB58String(name) if err != nil { diff --git a/path/path.go b/path/path.go index 0ab15e5c96f..ba75810c86d 100644 --- a/path/path.go +++ b/path/path.go @@ -44,12 +44,8 @@ func (p Path) String() string { return string(p) } -func FromSegments(seg ...string) (Path, error) { - var pref string - if seg[0] == "ipfs" || seg[0] == "ipns" { - pref = "/" - } - return ParsePath(pref + strings.Join(seg, "/")) +func FromSegments(prefix string, seg ...string) (Path, error) { + return ParsePath(prefix + strings.Join(seg, "/")) } func ParsePath(txt string) (Path, error) { @@ -68,15 +64,15 @@ func ParsePath(txt string) (Path, error) { return "", ErrBadPath } - if parts[1] != "ipfs" && parts[1] != "ipns" { + if parts[1] == "ipfs" { + _, err := ParseKeyToPath(parts[2]) + if err != nil { + return "", err + } + } else if parts[1] != "ipns" { return "", ErrBadPath } - _, err := ParseKeyToPath(parts[2]) - if err != nil { - return "", err - } - return Path(txt), nil } diff --git a/path/resolver_test.go b/path/resolver_test.go index 3772f1b9b8e..88fcb743370 100644 --- a/path/resolver_test.go +++ b/path/resolver_test.go @@ -59,8 +59,8 @@ func TestRecurivePathResolution(t *testing.T) { t.Fatal(err) } - segments := []string{"", "ipfs", aKey.String(), "child", "grandchild"} - p, err := path.FromSegments(segments...) + segments := []string{aKey.String(), "child", "grandchild"} + p, err := path.FromSegments("/ipfs/", segments...) if err != nil { t.Fatal(err) } From c2ff02850b9a7064a23df6cea9cf6187a6d4155e Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 11 May 2015 20:07:13 -0700 Subject: [PATCH 05/11] core/commands/resolve: Add a -r / --recursive option For explicitly enabling recursive behaviour (it was previously always enabled). That allows folks who are interested in understanding layered indirection to step through the chain one link at a time. --- core/commands/resolve.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/core/commands/resolve.go b/core/commands/resolve.go index 2d9b5906e2f..ff7b5df5b93 100644 --- a/core/commands/resolve.go +++ b/core/commands/resolve.go @@ -6,6 +6,7 @@ import ( "strings" cmds "github.com/ipfs/go-ipfs/commands" + namesys "github.com/ipfs/go-ipfs/namesys" path "github.com/ipfs/go-ipfs/path" u "github.com/ipfs/go-ipfs/util" ) @@ -46,6 +47,9 @@ Resolve te value of another name: Arguments: []cmds.Argument{ cmds.StringArg("name", false, false, "The IPNS name to resolve. Defaults to your node's peerID.").EnableStdin(), }, + Options: []cmds.Option{ + cmds.BoolOption("recursive", "r", "Resolve until the result is not an IPNS name"), + }, Run: func(req cmds.Request, res cmds.Response) { n, err := req.Context().GetNode() @@ -75,7 +79,13 @@ Resolve te value of another name: name = req.Arguments()[0] } - output, err := n.Namesys.Resolve(n.Context(), "/ipns/"+name) + recursive, _, _ := req.Option("recursive").Bool() + depth := 1 + if recursive { + depth = namesys.DefaultDepthLimit + } + + output, err := n.Namesys.ResolveN(n.Context(), "/ipns/"+name, depth) if err != nil { res.SetError(err, cmds.ErrNormal) return From e643f72c86f122416131d00f12cb595a23695b6d Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 7 May 2015 15:09:08 -0700 Subject: [PATCH 06/11] core/commands/dns: Add 'ipfs dns ...' for resolving DNS references This lets users resolve (recursively or not) DNS links without pulling in the other protocols. That makes an easier, more isolated target for alternative implemenations, since they don't need to understand IPNS, proquint, etc. to handle these resolutions. --- core/commands/dns.go | 82 +++++++++++++++++++++++++++++++++++++++++++ core/commands/root.go | 2 ++ 2 files changed, 84 insertions(+) create mode 100644 core/commands/dns.go diff --git a/core/commands/dns.go b/core/commands/dns.go new file mode 100644 index 00000000000..900f0ee1e0f --- /dev/null +++ b/core/commands/dns.go @@ -0,0 +1,82 @@ +package commands + +import ( + "io" + "strings" + + cmds "github.com/ipfs/go-ipfs/commands" + namesys "github.com/ipfs/go-ipfs/namesys" + util "github.com/ipfs/go-ipfs/util" +) + +var DNSCmd = &cmds.Command{ + Helptext: cmds.HelpText{ + Tagline: "DNS link resolver", + ShortDescription: ` +Multihashes are hard to remember, but domain names are usually easy to +remember. To create memorable aliases for multihashes, DNS TXT +records can point to other DNS links, IPFS objects, IPNS keys, etc. +This command resolves those links to the referenced object. +`, + LongDescription: ` +Multihashes are hard to remember, but domain names are usually easy to +remember. To create memorable aliases for multihashes, DNS TXT +records can point to other DNS links, IPFS objects, IPNS keys, etc. +This command resolves those links to the referenced object. + +For example, with this DNS TXT record: + + ipfs.io. TXT "dnslink=/ipfs/QmRzTuh2Lpuz7Gr39stNr6mTFdqAghsZec1JoUnfySUzcy ..." + +The resolver will give: + + > ipfs dns ipfs.io + /ipfs/QmRzTuh2Lpuz7Gr39stNr6mTFdqAghsZec1JoUnfySUzcy + +And with this DNS TXT record: + + ipfs.ipfs.io. TXT "dnslink=/dns/ipfs.io ..." + +The resolver will give: + + > ipfs dns ipfs.io + /dns/ipfs.io + > ipfs dns --recursive + /ipfs/QmRzTuh2Lpuz7Gr39stNr6mTFdqAghsZec1JoUnfySUzcy +`, + }, + + Arguments: []cmds.Argument{ + cmds.StringArg("domain-name", true, false, "The domain-name name to resolve.").EnableStdin(), + }, + Options: []cmds.Option{ + cmds.BoolOption("recursive", "r", "Resolve until the result is not a DNS link"), + }, + Run: func(req cmds.Request, res cmds.Response) { + + recursive, _, _ := req.Option("recursive").Bool() + name := req.Arguments()[0] + var resolver namesys.DNSResolver + + depth := 1 + if recursive { + depth = namesys.DefaultDepthLimit + } + output, err := resolver.ResolveN(req.Context().Context, name, depth) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + res.SetOutput(&ResolvedPath{output}) + }, + Marshalers: cmds.MarshalerMap{ + cmds.Text: func(res cmds.Response) (io.Reader, error) { + output, ok := res.Output().(*ResolvedPath) + if !ok { + return nil, util.ErrCast() + } + return strings.NewReader(output.Path.String()), nil + }, + }, + Type: ResolvedPath{}, +} diff --git a/core/commands/root.go b/core/commands/root.go index 5b1ef364d48..41daa00b029 100644 --- a/core/commands/root.go +++ b/core/commands/root.go @@ -41,6 +41,7 @@ ADVANCED COMMANDS daemon Start a long-running daemon process mount Mount an ipfs read-only mountpoint name Publish or resolve IPNS names + dns Resolve DNS links pin Pin objects to local storage repo gc Garbage collect unpinned objects @@ -84,6 +85,7 @@ var rootSubcommands = map[string]*cmds.Command{ "config": ConfigCmd, "dht": DhtCmd, "diag": DiagCmd, + "dns": DNSCmd, "get": GetCmd, "id": IDCmd, "log": LogCmd, From 416d454b42ed5301bf38fd93654b173b7f944025 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 7 May 2015 20:47:19 -0700 Subject: [PATCH 07/11] core/commands: Make 'ipfs name resolve' IPNS-only And add a generic 'ipfs resolve' to handle cross-protocol name resolution. --- core/commands/ipns.go | 104 +++++++++++++++++++++++++++++++++++++++ core/commands/name.go | 6 +-- core/commands/resolve.go | 57 +++++++++------------ core/commands/root.go | 2 + 4 files changed, 133 insertions(+), 36 deletions(-) create mode 100644 core/commands/ipns.go diff --git a/core/commands/ipns.go b/core/commands/ipns.go new file mode 100644 index 00000000000..76a455ef522 --- /dev/null +++ b/core/commands/ipns.go @@ -0,0 +1,104 @@ +package commands + +import ( + "errors" + "io" + "strings" + + cmds "github.com/ipfs/go-ipfs/commands" + namesys "github.com/ipfs/go-ipfs/namesys" + u "github.com/ipfs/go-ipfs/util" +) + +var ipnsCmd = &cmds.Command{ + Helptext: cmds.HelpText{ + Tagline: "Gets the value currently published at an IPNS name", + ShortDescription: ` +IPNS is a PKI namespace, where names are the hashes of public keys, and +the private key enables publishing new (signed) values. In resolve, the +default value of is your own identity public key. +`, + LongDescription: ` +IPNS is a PKI namespace, where names are the hashes of public keys, and +the private key enables publishing new (signed) values. In resolve, the +default value of is your own identity public key. + + +Examples: + +Resolve the value of your identity: + + > ipfs name resolve + QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + +Resolve the value of another name: + + > ipfs name resolve QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n + QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + +`, + }, + + Arguments: []cmds.Argument{ + cmds.StringArg("name", false, false, "The IPNS name to resolve. Defaults to your node's peerID.").EnableStdin(), + }, + Options: []cmds.Option{ + cmds.BoolOption("recursive", "r", "Resolve until the result is not an IPNS name"), + }, + Run: func(req cmds.Request, res cmds.Response) { + + n, err := req.Context().GetNode() + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + + if !n.OnlineMode() { + err := n.SetupOfflineRouting() + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + } + + var name string + + if len(req.Arguments()) == 0 { + if n.Identity == "" { + res.SetError(errors.New("Identity not loaded!"), cmds.ErrNormal) + return + } + name = n.Identity.Pretty() + + } else { + name = req.Arguments()[0] + } + + recursive, _, _ := req.Option("recursive").Bool() + depth := 1 + if recursive { + depth = namesys.DefaultDepthLimit + } + + resolver := namesys.NewRoutingResolver(n.Routing) + output, err := resolver.ResolveN(n.Context(), name, depth) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + + // TODO: better errors (in the case of not finding the name, we get "failed to find any peer in table") + + res.SetOutput(&ResolvedPath{output}) + }, + Marshalers: cmds.MarshalerMap{ + cmds.Text: func(res cmds.Response) (io.Reader, error) { + output, ok := res.Output().(*ResolvedPath) + if !ok { + return nil, u.ErrCast() + } + return strings.NewReader(output.Path.String()), nil + }, + }, + Type: ResolvedPath{}, +} diff --git a/core/commands/name.go b/core/commands/name.go index d272d9e4ed8..7f263083d18 100644 --- a/core/commands/name.go +++ b/core/commands/name.go @@ -40,18 +40,18 @@ Publish a to another public key: Resolve the value of your identity: > ipfs name resolve - QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + /ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy Resolve the value of another name: > ipfs name resolve QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n - QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + /ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy `, }, Subcommands: map[string]*cmds.Command{ "publish": publishCmd, - "resolve": resolveCmd, + "resolve": ipnsCmd, }, } diff --git a/core/commands/resolve.go b/core/commands/resolve.go index ff7b5df5b93..9e8845cafa8 100644 --- a/core/commands/resolve.go +++ b/core/commands/resolve.go @@ -1,7 +1,6 @@ package commands import ( - "errors" "io" "strings" @@ -15,40 +14,46 @@ type ResolvedPath struct { Path path.Path } -var resolveCmd = &cmds.Command{ +var ResolveCmd = &cmds.Command{ Helptext: cmds.HelpText{ - Tagline: "Gets the value currently published at an IPNS name", + Tagline: "Resolve the value of names to IPFS", ShortDescription: ` -IPNS is a PKI namespace, where names are the hashes of public keys, and -the private key enables publishing new (signed) values. In resolve, the -default value of is your own identity public key. +There are a number of mutable name protocols that can link among +themselves and into IPNS. This command accepts any of these +identifiers and resolves them to the referenced item. `, LongDescription: ` -IPNS is a PKI namespace, where names are the hashes of public keys, and -the private key enables publishing new (signed) values. In resolve, the -default value of is your own identity public key. - +There are a number of mutable name protocols that can link among +themselves and into IPNS. For example IPNS references can (currently) +point at IPFS object, and DNS links can point at other DNS links, IPNS +entries, or IPFS objects. This command accepts any of these +identifiers and resolves them to the referenced item. Examples: Resolve the value of your identity: - > ipfs name resolve - QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + > ipfs resolve /ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + /ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj + +Resolve the value of another name: + + > ipfs resolve /ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n + /ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy -Resolve te value of another name: +Resolve the value of another name recursively: - > ipfs name resolve QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n - QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + > ipfs resolve -r /ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n + /ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj `, }, Arguments: []cmds.Argument{ - cmds.StringArg("name", false, false, "The IPNS name to resolve. Defaults to your node's peerID.").EnableStdin(), + cmds.StringArg("name", true, false, "The name to resolve.").EnableStdin(), }, Options: []cmds.Option{ - cmds.BoolOption("recursive", "r", "Resolve until the result is not an IPNS name"), + cmds.BoolOption("recursive", "r", "Resolve until the result is an IPFS name"), }, Run: func(req cmds.Request, res cmds.Response) { @@ -66,33 +71,19 @@ Resolve te value of another name: } } - var name string - - if len(req.Arguments()) == 0 { - if n.Identity == "" { - res.SetError(errors.New("Identity not loaded!"), cmds.ErrNormal) - return - } - name = n.Identity.Pretty() - - } else { - name = req.Arguments()[0] - } - + name := req.Arguments()[0] recursive, _, _ := req.Option("recursive").Bool() depth := 1 if recursive { depth = namesys.DefaultDepthLimit } - output, err := n.Namesys.ResolveN(n.Context(), "/ipns/"+name, depth) + output, err := n.Namesys.ResolveN(n.Context(), name, depth) if err != nil { res.SetError(err, cmds.ErrNormal) return } - // TODO: better errors (in the case of not finding the name, we get "failed to find any peer in table") - res.SetOutput(&ResolvedPath{output}) }, Marshalers: cmds.MarshalerMap{ diff --git a/core/commands/root.go b/core/commands/root.go index 41daa00b029..5de32a21325 100644 --- a/core/commands/root.go +++ b/core/commands/root.go @@ -40,6 +40,7 @@ ADVANCED COMMANDS daemon Start a long-running daemon process mount Mount an ipfs read-only mountpoint + resolve Resolve any type of name name Publish or resolve IPNS names dns Resolve DNS links pin Pin objects to local storage @@ -97,6 +98,7 @@ var rootSubcommands = map[string]*cmds.Command{ "ping": PingCmd, "refs": RefsCmd, "repo": RepoCmd, + "resolve": ResolveCmd, "stats": StatsCmd, "swarm": SwarmCmd, "update": UpdateCmd, From e4447b3c961eb874d50476957236102335feca9b Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 7 May 2015 20:58:15 -0700 Subject: [PATCH 08/11] core/commands/publish: Fix published message Previously we had a confusing situation, with: * single-arg doc: published name to * double-arg doc: published name to * implementation: Published name to Now we have the uniform: Published to : With the following goals: 1. It's clear that we're writing to 's IPNS slot in the DHT. 2. We preserve the order of arguments from the command-line invocation: $ ipfs name publish Published to : --- core/commands/name.go | 12 ++++++------ core/commands/publish.go | 9 +++++---- test/sharness/t0100-name.sh | 6 +++--- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/core/commands/name.go b/core/commands/name.go index 7f263083d18..eba937304fe 100644 --- a/core/commands/name.go +++ b/core/commands/name.go @@ -27,15 +27,15 @@ and resolve, the default value of is your own identity public key. Examples: -Publish a to your identity name: +Publish an to your identity name: - > ipfs name publish QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy - published name QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n to QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + > ipfs name publish /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + Published to QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n: /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy -Publish a to another public key: +Publish an to another public key: - > ipfs name publish QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy - published name QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n to QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + > ipfs name publish /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n + Published to QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n: /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy Resolve the value of your identity: diff --git a/core/commands/publish.go b/core/commands/publish.go index c784bbcd245..2f52bf3560d 100644 --- a/core/commands/publish.go +++ b/core/commands/publish.go @@ -35,12 +35,13 @@ Examples: Publish an to your identity name: > ipfs name publish /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy - published name QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n to QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + Published to QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n: /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy Publish an to another public key (not implemented): - > ipfs name publish QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy - published name QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n to QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + > ipfs name publish /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n + Published to QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n: /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + `, }, @@ -102,7 +103,7 @@ Publish an to another public key (not implemented): Marshalers: cmds.MarshalerMap{ cmds.Text: func(res cmds.Response) (io.Reader, error) { v := res.Output().(*IpnsEntry) - s := fmt.Sprintf("Published name %s to %s\n", v.Name, v.Value) + s := fmt.Sprintf("Published to %s: %s\n", v.Name, v.Value) return strings.NewReader(s), nil }, }, diff --git a/test/sharness/t0100-name.sh b/test/sharness/t0100-name.sh index 495323393cb..531eddf9dd9 100755 --- a/test/sharness/t0100-name.sh +++ b/test/sharness/t0100-name.sh @@ -14,11 +14,11 @@ test_init_ipfs test_expect_success "'ipfs name publish' succeeds" ' PEERID=`ipfs id --format=""` && - ipfs name publish "$HASH_WELCOME_DOCS" >publish_out + ipfs name publish "/ipfs/$HASH_WELCOME_DOCS" >publish_out ' test_expect_success "publish output looks good" ' - echo "Published name $PEERID to /ipfs/$HASH_WELCOME_DOCS" >expected1 && + echo "Published to ${PEERID}: /ipfs/$HASH_WELCOME_DOCS" >expected1 && test_cmp publish_out expected1 ' @@ -39,7 +39,7 @@ test_expect_success "'ipfs name publish' succeeds" ' ' test_expect_success "publish a path looks good" ' - echo "Published name $PEERID to /ipfs/$HASH_WELCOME_DOCS/help" >expected3 && + echo "Published to ${PEERID}: /ipfs/$HASH_WELCOME_DOCS/help" >expected3 && test_cmp publish_out expected3 ' From 1e6594d087bc47e278d91fb1b41c077f298064d7 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 16 May 2015 09:33:22 -0700 Subject: [PATCH 09/11] namesys/dns: Pluggable lookupTXT field So we can attach a mock lookup function for testing. --- core/commands/dns.go | 2 +- namesys/dns.go | 16 +++++++++++++++- namesys/namesys.go | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/core/commands/dns.go b/core/commands/dns.go index 900f0ee1e0f..e24b6a483d5 100644 --- a/core/commands/dns.go +++ b/core/commands/dns.go @@ -56,7 +56,7 @@ The resolver will give: recursive, _, _ := req.Option("recursive").Bool() name := req.Arguments()[0] - var resolver namesys.DNSResolver + resolver := namesys.NewDNSResolver() depth := 1 if recursive { diff --git a/namesys/dns.go b/namesys/dns.go index f57ddce5901..3703bd8d01d 100644 --- a/namesys/dns.go +++ b/namesys/dns.go @@ -11,12 +11,26 @@ import ( path "github.com/ipfs/go-ipfs/path" ) +type LookupTXTFunc func(name string) (txt []string, err error) + // DNSResolver implements a Resolver on DNS domains type DNSResolver struct { + lookupTXT LookupTXTFunc // TODO: maybe some sort of caching? // cache would need a timeout } +// NewDNSResolver constructs a name resolver using DNS TXT records. +func NewDNSResolver() Resolver { + return &DNSResolver{lookupTXT: net.LookupTXT} +} + +// newDNSResolver constructs a name resolver using DNS TXT records, +// returning a resolver instead of NewDNSResolver's Resolver. +func newDNSResolver() resolver { + return &DNSResolver{lookupTXT: net.LookupTXT} +} + // Resolve implements Resolver. func (r *DNSResolver) Resolve(ctx context.Context, name string) (path.Path, error) { return r.ResolveN(ctx, name, DefaultDepthLimit) @@ -36,7 +50,7 @@ func (r *DNSResolver) resolveOnce(ctx context.Context, name string) (path.Path, } log.Infof("DNSResolver resolving %s", name) - txt, err := net.LookupTXT(name) + txt, err := r.lookupTXT(name) if err != nil { return "", err } diff --git a/namesys/namesys.go b/namesys/namesys.go index 0f5b853bea7..7fe317b6695 100644 --- a/namesys/namesys.go +++ b/namesys/namesys.go @@ -27,7 +27,7 @@ type mpns struct { func NewNameSystem(r routing.IpfsRouting) NameSystem { return &mpns{ resolvers: map[string]resolver{ - "dns": new(DNSResolver), + "dns": newDNSResolver(), "proquint": new(ProquintResolver), "dht": newRoutingResolver(r), }, From c9fceeb19b96ef54e3b46b6301f11e9cfbffde8b Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 16 May 2015 09:54:06 -0700 Subject: [PATCH 10/11] namesys/dns_test: Add DNS resolution tests with a mock resolver --- namesys/dns_test.go | 86 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/namesys/dns_test.go b/namesys/dns_test.go index 402156addc3..6bb75ff9f59 100644 --- a/namesys/dns_test.go +++ b/namesys/dns_test.go @@ -1,9 +1,24 @@ package namesys import ( + "fmt" "testing" + + context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" ) +type mockDNS struct { + entries map[string][]string +} + +func (m *mockDNS) lookupTXT(name string) (txt []string, err error) { + txt, ok := m.entries[name] + if !ok { + return nil, fmt.Errorf("No TXT entry for %s", name) + } + return txt, nil +} + func TestDnsEntryParsing(t *testing.T) { goodEntries := []string{ "QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", @@ -40,3 +55,74 @@ func TestDnsEntryParsing(t *testing.T) { } } } + +func newMockDNS() *mockDNS { + return &mockDNS{ + entries: map[string][]string{ + "multihash.example.com": []string{ + "dnslink=QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", + }, + "ipfs.example.com": []string{ + "dnslink=/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", + }, + "dns1.example.com": []string{ + "dnslink=/ipns/ipfs.example.com", + }, + "dns2.example.com": []string{ + "dnslink=/ipns/dns1.example.com", + }, + "multi.example.com": []string{ + "some stuff", + "dnslink=/ipns/dns1.example.com", + "masked dnslink=/ipns/example.invalid", + }, + "equals.example.com": []string{ + "dnslink=/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD/=equals", + }, + "loop1.example.com": []string{ + "dnslink=/ipns/loop2.example.com", + }, + "loop2.example.com": []string{ + "dnslink=/ipns/loop1.example.com", + }, + "bad.example.com": []string{ + "dnslink=", + }, + }, + } +} + +func testResolution(t *testing.T, resolver Resolver, name string, depth int, expected string, expError error) { + p, err := resolver.ResolveN(context.Background(), name, depth) + if err != expError { + t.Fatal(fmt.Errorf( + "Expected %s with a depth of %d to have a '%s' error, but got '%s'", + name, depth, expError, err)) + } + if p.String() != expected { + t.Fatal(fmt.Errorf( + "%s with depth %d resolved to %s != %s", + name, depth, p.String(), expected)) + } +} + +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) +} From ce015d241b799defe2d54ef5c86e0aed3805bbb9 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 19 May 2015 12:48:32 -0700 Subject: [PATCH 11/11] namesys/namesys_test: Excercise mpns.ResolveN Shifting the generic testResolution helper from the protocol-specific dns_test.go to the generic namesys_test.go. --- namesys/dns_test.go | 16 ---------- namesys/namesys_test.go | 71 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 16 deletions(-) create mode 100644 namesys/namesys_test.go diff --git a/namesys/dns_test.go b/namesys/dns_test.go index 6bb75ff9f59..40bf702c35c 100644 --- a/namesys/dns_test.go +++ b/namesys/dns_test.go @@ -3,8 +3,6 @@ package namesys import ( "fmt" "testing" - - context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" ) type mockDNS struct { @@ -92,20 +90,6 @@ func newMockDNS() *mockDNS { } } -func testResolution(t *testing.T, resolver Resolver, name string, depth int, expected string, expError error) { - p, err := resolver.ResolveN(context.Background(), name, depth) - if err != expError { - t.Fatal(fmt.Errorf( - "Expected %s with a depth of %d to have a '%s' error, but got '%s'", - name, depth, expError, err)) - } - if p.String() != expected { - t.Fatal(fmt.Errorf( - "%s with depth %d resolved to %s != %s", - name, depth, p.String(), expected)) - } -} - func TestDNSResolution(t *testing.T) { mock := newMockDNS() r := &DNSResolver{lookupTXT: mock.lookupTXT} diff --git a/namesys/namesys_test.go b/namesys/namesys_test.go new file mode 100644 index 00000000000..256228c3e65 --- /dev/null +++ b/namesys/namesys_test.go @@ -0,0 +1,71 @@ +package namesys + +import ( + "fmt" + "testing" + + context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" + + path "github.com/ipfs/go-ipfs/path" +) + +type mockResolver struct { + entries map[string]string +} + +func testResolution(t *testing.T, resolver Resolver, name string, depth int, expected string, expError error) { + p, err := resolver.ResolveN(context.Background(), name, depth) + if err != expError { + t.Fatal(fmt.Errorf( + "Expected %s with a depth of %d to have a '%s' error, but got '%s'", + name, depth, expError, err)) + } + if p.String() != expected { + t.Fatal(fmt.Errorf( + "%s with depth %d resolved to %s != %s", + name, depth, p.String(), expected)) + } +} + +func (r *mockResolver) resolveOnce(ctx context.Context, name string) (path.Path, error) { + return path.ParsePath(r.entries[name]) +} + +func mockResolverOne() *mockResolver { + return &mockResolver{ + entries: map[string]string{ + "QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy": "/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", + "QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n": "/ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy", + "QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD": "/ipns/ipfs.io", + }, + } +} + +func mockResolverTwo() *mockResolver { + return &mockResolver{ + entries: map[string]string{ + "ipfs.io": "/ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", + }, + } +} + +func TestNamesysResolution(t *testing.T) { + r := &mpns{ + resolvers: map[string]resolver{ + "one": mockResolverOne(), + "two": mockResolverTwo(), + }, + } + + testResolution(t, r, "Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", DefaultDepthLimit, "/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", nil) + testResolution(t, r, "/ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy", DefaultDepthLimit, "/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", nil) + testResolution(t, r, "/ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", DefaultDepthLimit, "/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", nil) + testResolution(t, r, "/ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", 1, "/ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy", ErrResolveRecursion) + testResolution(t, r, "/ipns/ipfs.io", DefaultDepthLimit, "/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", nil) + testResolution(t, r, "/ipns/ipfs.io", 1, "/ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", ErrResolveRecursion) + testResolution(t, r, "/ipns/ipfs.io", 2, "/ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy", ErrResolveRecursion) + testResolution(t, r, "/ipns/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", DefaultDepthLimit, "/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", nil) + testResolution(t, r, "/ipns/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", 1, "/ipns/ipfs.io", ErrResolveRecursion) + testResolution(t, r, "/ipns/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", 2, "/ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", ErrResolveRecursion) + testResolution(t, r, "/ipns/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", 3, "/ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy", ErrResolveRecursion) +}