From 74c3a499b653c8a750afb3cbe6b3d94449eb7fbf Mon Sep 17 00:00:00 2001 From: Nitin Patel Date: Thu, 14 Mar 2019 18:21:34 +0530 Subject: [PATCH 1/6] feat: enable recursive lookups on ipfs.dns --- README.md | 1 + src/cli/commands/dns.js | 10 ++++-- src/core/runtime/dns-nodejs.js | 28 +++++++++++++-- src/http/api/resources/dns.js | 6 ++-- test/core/dns.spec.js | 62 ++++++++++++++++++++++++++++++++++ 5 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 test/core/dns.spec.js diff --git a/README.md b/README.md index 508f25d2f9..e281262e78 100644 --- a/README.md +++ b/README.md @@ -639,6 +639,7 @@ The core API is grouped into several areas: - [`ipfs.stop([callback])`](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/MISCELLANEOUS.md#stop) - `ipfs.isOnline()` - [`ipfs.resolve(name, [options], [callback])`](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/MISCELLANEOUS.md#resolve) + - [`ipfs.dns(name, [options], [callback]`](https://github.com/ipfs/interface-js-ipfs-core/blob/master/SPEC/MISCELLANEOUS.md#dns) - [repo](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/REPO.md) - `ipfs.repo.init` diff --git a/src/cli/commands/dns.js b/src/cli/commands/dns.js index 020259ef3f..cf4f023f23 100644 --- a/src/cli/commands/dns.js +++ b/src/cli/commands/dns.js @@ -7,15 +7,21 @@ module.exports = { describe: 'Resolve DNS links', builder: { + recursive: { + type: 'boolean', + default: false, + alias: 'r', + desc: 'Resolve until the result is not a DNS link' + }, format: { type: 'string' } }, - handler ({ getIpfs, domain, resolve }) { + handler ({ getIpfs, domain, resolve, ...opts }) { resolve((async () => { const ipfs = await getIpfs() - const path = await ipfs.dns(domain) + const path = await ipfs.dns(domain, opts) print(path) })()) } diff --git a/src/core/runtime/dns-nodejs.js b/src/core/runtime/dns-nodejs.js index d187ed0915..a3dd5a0e30 100644 --- a/src/core/runtime/dns-nodejs.js +++ b/src/core/runtime/dns-nodejs.js @@ -2,10 +2,27 @@ const dns = require('dns') const _ = require('lodash') +const isIPFS = require('is-ipfs') const errcode = require('err-code') +const maxRecursiveDepth = 32 + module.exports = (domain, opts, callback) => { - resolveDnslink(domain) + const recursive = opts.recursive && opts.recursive.toString() === 'true' + let depth + if (recursive) { + depth = maxRecursiveDepth + } + + return recursiveResolveDnslink(domain, depth, callback) +} + +function recursiveResolveDnslink (domain, depth, callback) { + if (depth === 0) { + return callback(errcode(`recursion limit exceeded`, 'ERR_DNSLINK_RECURSION_LIMIT')) + } + + return resolveDnslink(domain) .catch(err => { // If the code is not ENOTFOUND or ERR_DNSLINK_NOT_FOUND or ENODATA then throw the error if (err.code !== 'ENOTFOUND' && err.code !== 'ERR_DNSLINK_NOT_FOUND' && err.code !== 'ENODATA') throw err @@ -22,7 +39,14 @@ module.exports = (domain, opts, callback) => { return resolveDnslink(_dnslinkDomain) }) .then(dnslinkRecord => { - callback(null, dnslinkRecord.replace('dnslink=', '')) + const result = dnslinkRecord.replace('dnslink=', '') + const domainOrCID = result.split('/')[2] + const isIPFSCID = isIPFS.cid(domainOrCID) + + if (isIPFSCID || !depth) { + return callback(null, result) + } + return recursiveResolveDnslink(domainOrCID, depth - 1, callback) }) .catch(callback) } diff --git a/src/http/api/resources/dns.js b/src/http/api/resources/dns.js index db20a9f170..272334b332 100644 --- a/src/http/api/resources/dns.js +++ b/src/http/api/resources/dns.js @@ -3,11 +3,13 @@ const Boom = require('boom') module.exports = async (request, h) => { - if (!request.query.arg) { + const { arg: domain, ...opts } = request.query + + if (!domain) { throw Boom.badRequest("Argument 'domain' is required") } - const path = await request.server.app.ipfs.dns(request.query.arg) + const path = await request.server.app.ipfs.dns(domain, opts) return h.response({ Path: path }) diff --git a/test/core/dns.spec.js b/test/core/dns.spec.js new file mode 100644 index 0000000000..cecd56b2fe --- /dev/null +++ b/test/core/dns.spec.js @@ -0,0 +1,62 @@ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) + +const IPFSFactory = require('ipfsd-ctl') +const IPFS = require('../../src/core') + +describe('.dns', () => { + let ipfsd, ipfs + + before(function (done) { + this.timeout(20 * 1000) + + const factory = IPFSFactory.create({ type: 'proc' }) + + factory.spawn({ + exec: IPFS, + initOptions: { bits: 512 }, + config: { Bootstrap: [] } + }, (err, _ipfsd) => { + expect(err).to.not.exist() + ipfsd = _ipfsd + ipfs = _ipfsd.api + done() + }) + }) + + after((done) => { + if (ipfsd) { + ipfsd.stop(done) + } else { + done() + } + }) + + // skipping because there is an error in https://ipfs.io/api/v0/dns?arg=ipfs.io + // unskip once this is resolved: https://github.com/ipfs/go-ipfs/issues/6086 + it.skip('should resolve ipfs.io', () => { + return ipfs.dns('ipfs.io').then(res => { + // matches pattern /ipns/ + expect(res).to.match(/\/ipns\/.+$/) + }) + }) + + it('should recursively resolve ipfs.io', () => { + return ipfs.dns('ipfs.io', { recursive: true }).then(res => { + // matches pattern /ipfs/ + expect(res).to.match(/\/ipfs\/.+$/) + }) + }) + + it('should resolve subdomain docs.ipfs.io', () => { + return ipfs.dns('docs.ipfs.io').then(res => { + // matches pattern /ipfs/ + expect(res).to.match(/\/ipfs\/.+$/) + }) + }) +}) From 5e0210860c06b34dfdad0ad55f27a496ec92f9e8 Mon Sep 17 00:00:00 2001 From: Nitin Patel Date: Sat, 16 Mar 2019 12:52:01 +0530 Subject: [PATCH 2/6] feat: make dns recursive by default --- src/cli/commands/dns.js | 2 +- src/core/runtime/dns-nodejs.js | 4 +++- test/cli/dns.js | 21 ++++++++++++++++++--- test/core/dns.spec.js | 4 ++-- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/cli/commands/dns.js b/src/cli/commands/dns.js index cf4f023f23..5fba61558e 100644 --- a/src/cli/commands/dns.js +++ b/src/cli/commands/dns.js @@ -9,7 +9,7 @@ module.exports = { builder: { recursive: { type: 'boolean', - default: false, + default: true, alias: 'r', desc: 'Resolve until the result is not a DNS link' }, diff --git a/src/core/runtime/dns-nodejs.js b/src/core/runtime/dns-nodejs.js index a3dd5a0e30..df7226e0b0 100644 --- a/src/core/runtime/dns-nodejs.js +++ b/src/core/runtime/dns-nodejs.js @@ -8,7 +8,9 @@ const errcode = require('err-code') const maxRecursiveDepth = 32 module.exports = (domain, opts, callback) => { - const recursive = opts.recursive && opts.recursive.toString() === 'true' + // recursive is true by default, it's set to false only if explicitly passed as argument in opts + const recursive = opts.recursive === undefined || opts.recursive.toString() === 'true' + let depth if (recursive) { depth = maxRecursiveDepth diff --git a/test/cli/dns.js b/test/cli/dns.js index 09286aa325..2f7a9fab10 100644 --- a/test/cli/dns.js +++ b/test/cli/dns.js @@ -3,6 +3,7 @@ const expect = require('chai').expect const runOnAndOff = require('../utils/on-and-off') +const isIPFS = require('is-ipfs') describe('dns', () => runOnAndOff((thing) => { let ipfs @@ -12,19 +13,33 @@ describe('dns', () => runOnAndOff((thing) => { ipfs = thing.ipfs }) - it('resolve ipfs.io dns', function () { + it('recursively resolve ipfs.io dns', function () { this.timeout(60 * 1000) return ipfs('dns ipfs.io').then((res) => { - expect(res.substr(0, 6)).to.eql('/ipns/') + expect(res.substr(0, 6)).to.eql('/ipfs/') + const resultingDomainOrCid = res.split('/')[2].trim() + expect(isIPFS.cid(resultingDomainOrCid)).to.eql(true) }) }) - it('resolve _dnslink.ipfs.io dns', function () { + it('recursively resolve _dnslink.ipfs.io dns', function () { this.timeout(60 * 1000) return ipfs('dns _dnslink.ipfs.io').then((res) => { + expect(res.substr(0, 6)).to.eql('/ipfs/') + const resultingDomainOrCid = res.split('/')[2].trim() + expect(isIPFS.cid(resultingDomainOrCid)).to.eql(true) + }) + }) + + it('non-recursive resolve ipfs.io', function () { + this.timeout(60 * 1000) + + return ipfs('dns --recursive false ipfs.io').then((res) => { expect(res.substr(0, 6)).to.eql('/ipns/') + const resultingDomainOrCid = res.split('/')[2].trim() + expect(isIPFS.cid(resultingDomainOrCid)).to.eql(false) }) }) diff --git a/test/core/dns.spec.js b/test/core/dns.spec.js index cecd56b2fe..efba2c241d 100644 --- a/test/core/dns.spec.js +++ b/test/core/dns.spec.js @@ -39,8 +39,8 @@ describe('.dns', () => { // skipping because there is an error in https://ipfs.io/api/v0/dns?arg=ipfs.io // unskip once this is resolved: https://github.com/ipfs/go-ipfs/issues/6086 - it.skip('should resolve ipfs.io', () => { - return ipfs.dns('ipfs.io').then(res => { + it.skip('should non-recursively resolve ipfs.io', () => { + return ipfs.dns('ipfs.io', { recursive: false }).then(res => { // matches pattern /ipns/ expect(res).to.match(/\/ipns\/.+$/) }) From 0518da38ffe95eb53602d0a404363e1d8817fd0f Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 4 Apr 2019 22:36:38 +0530 Subject: [PATCH 3/6] fix: apply suggestions from code review Co-Authored-By: niinpatel <31539366+niinpatel@users.noreply.github.com> --- src/cli/commands/dns.js | 2 +- src/core/runtime/dns-nodejs.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/commands/dns.js b/src/cli/commands/dns.js index 5fba61558e..c7cf466ddf 100644 --- a/src/cli/commands/dns.js +++ b/src/cli/commands/dns.js @@ -18,7 +18,7 @@ module.exports = { } }, - handler ({ getIpfs, domain, resolve, ...opts }) { + handler ({ getIpfs, domain, resolve, recursive, format }) { resolve((async () => { const ipfs = await getIpfs() const path = await ipfs.dns(domain, opts) diff --git a/src/core/runtime/dns-nodejs.js b/src/core/runtime/dns-nodejs.js index df7226e0b0..fb2766c840 100644 --- a/src/core/runtime/dns-nodejs.js +++ b/src/core/runtime/dns-nodejs.js @@ -5,11 +5,11 @@ const _ = require('lodash') const isIPFS = require('is-ipfs') const errcode = require('err-code') -const maxRecursiveDepth = 32 +const MAX_RECURSIVE_DEPTH = 32 module.exports = (domain, opts, callback) => { // recursive is true by default, it's set to false only if explicitly passed as argument in opts - const recursive = opts.recursive === undefined || opts.recursive.toString() === 'true' + const recursive = opts.recursive == null ? true : Boolean(opts.recursive) let depth if (recursive) { From aed2f5367a8009eaae75e005515a7f46b20f1c9d Mon Sep 17 00:00:00 2001 From: Nitin Patel Date: Thu, 4 Apr 2019 22:46:31 +0530 Subject: [PATCH 4/6] refactor: move tests to interface-ipfs-core --- src/cli/commands/dns.js | 2 +- src/core/runtime/dns-nodejs.js | 4 +-- src/http/api/resources/dns.js | 4 +-- test/core/dns.spec.js | 62 ---------------------------------- 4 files changed, 5 insertions(+), 67 deletions(-) delete mode 100644 test/core/dns.spec.js diff --git a/src/cli/commands/dns.js b/src/cli/commands/dns.js index c7cf466ddf..f07dd0e8f6 100644 --- a/src/cli/commands/dns.js +++ b/src/cli/commands/dns.js @@ -21,7 +21,7 @@ module.exports = { handler ({ getIpfs, domain, resolve, recursive, format }) { resolve((async () => { const ipfs = await getIpfs() - const path = await ipfs.dns(domain, opts) + const path = await ipfs.dns(domain, { recursive, format }) print(path) })()) } diff --git a/src/core/runtime/dns-nodejs.js b/src/core/runtime/dns-nodejs.js index fb2766c840..be0eabd740 100644 --- a/src/core/runtime/dns-nodejs.js +++ b/src/core/runtime/dns-nodejs.js @@ -9,11 +9,11 @@ const MAX_RECURSIVE_DEPTH = 32 module.exports = (domain, opts, callback) => { // recursive is true by default, it's set to false only if explicitly passed as argument in opts - const recursive = opts.recursive == null ? true : Boolean(opts.recursive) + const recursive = opts.recursive.toString() !== 'false' let depth if (recursive) { - depth = maxRecursiveDepth + depth = MAX_RECURSIVE_DEPTH } return recursiveResolveDnslink(domain, depth, callback) diff --git a/src/http/api/resources/dns.js b/src/http/api/resources/dns.js index 272334b332..a827c4cab0 100644 --- a/src/http/api/resources/dns.js +++ b/src/http/api/resources/dns.js @@ -3,13 +3,13 @@ const Boom = require('boom') module.exports = async (request, h) => { - const { arg: domain, ...opts } = request.query + const { arg: domain, recursive, format } = request.query if (!domain) { throw Boom.badRequest("Argument 'domain' is required") } - const path = await request.server.app.ipfs.dns(domain, opts) + const path = await request.server.app.ipfs.dns(domain, { recursive, format }) return h.response({ Path: path }) diff --git a/test/core/dns.spec.js b/test/core/dns.spec.js deleted file mode 100644 index efba2c241d..0000000000 --- a/test/core/dns.spec.js +++ /dev/null @@ -1,62 +0,0 @@ -/* eslint-env mocha */ -'use strict' - -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) - -const IPFSFactory = require('ipfsd-ctl') -const IPFS = require('../../src/core') - -describe('.dns', () => { - let ipfsd, ipfs - - before(function (done) { - this.timeout(20 * 1000) - - const factory = IPFSFactory.create({ type: 'proc' }) - - factory.spawn({ - exec: IPFS, - initOptions: { bits: 512 }, - config: { Bootstrap: [] } - }, (err, _ipfsd) => { - expect(err).to.not.exist() - ipfsd = _ipfsd - ipfs = _ipfsd.api - done() - }) - }) - - after((done) => { - if (ipfsd) { - ipfsd.stop(done) - } else { - done() - } - }) - - // skipping because there is an error in https://ipfs.io/api/v0/dns?arg=ipfs.io - // unskip once this is resolved: https://github.com/ipfs/go-ipfs/issues/6086 - it.skip('should non-recursively resolve ipfs.io', () => { - return ipfs.dns('ipfs.io', { recursive: false }).then(res => { - // matches pattern /ipns/ - expect(res).to.match(/\/ipns\/.+$/) - }) - }) - - it('should recursively resolve ipfs.io', () => { - return ipfs.dns('ipfs.io', { recursive: true }).then(res => { - // matches pattern /ipfs/ - expect(res).to.match(/\/ipfs\/.+$/) - }) - }) - - it('should resolve subdomain docs.ipfs.io', () => { - return ipfs.dns('docs.ipfs.io').then(res => { - // matches pattern /ipfs/ - expect(res).to.match(/\/ipfs\/.+$/) - }) - }) -}) From d5fa74ea2fbd3e7f9e27a921cbb049e3e7678c15 Mon Sep 17 00:00:00 2001 From: Nitin Patel Date: Fri, 5 Apr 2019 00:14:05 +0530 Subject: [PATCH 5/6] fix: parse the "recursive" option correctly --- src/core/runtime/dns-nodejs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/runtime/dns-nodejs.js b/src/core/runtime/dns-nodejs.js index be0eabd740..6541122d06 100644 --- a/src/core/runtime/dns-nodejs.js +++ b/src/core/runtime/dns-nodejs.js @@ -9,7 +9,7 @@ const MAX_RECURSIVE_DEPTH = 32 module.exports = (domain, opts, callback) => { // recursive is true by default, it's set to false only if explicitly passed as argument in opts - const recursive = opts.recursive.toString() !== 'false' + const recursive = opts.recursive == null || opts.recursive.toString() !== 'false' let depth if (recursive) { From fb114f1ece896130ea5eddc59248c304b0ae6036 Mon Sep 17 00:00:00 2001 From: Nitin Patel Date: Fri, 5 Apr 2019 14:33:43 +0530 Subject: [PATCH 6/6] refactor: parse query strings in http-api endpoint --- src/core/runtime/dns-nodejs.js | 2 +- src/http/api/resources/dns.js | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/core/runtime/dns-nodejs.js b/src/core/runtime/dns-nodejs.js index 6541122d06..95f88d75f0 100644 --- a/src/core/runtime/dns-nodejs.js +++ b/src/core/runtime/dns-nodejs.js @@ -9,7 +9,7 @@ const MAX_RECURSIVE_DEPTH = 32 module.exports = (domain, opts, callback) => { // recursive is true by default, it's set to false only if explicitly passed as argument in opts - const recursive = opts.recursive == null || opts.recursive.toString() !== 'false' + const recursive = opts.recursive == null ? true : Boolean(opts.recursive) let depth if (recursive) { diff --git a/src/http/api/resources/dns.js b/src/http/api/resources/dns.js index a827c4cab0..fa9ec9dea3 100644 --- a/src/http/api/resources/dns.js +++ b/src/http/api/resources/dns.js @@ -3,12 +3,18 @@ const Boom = require('boom') module.exports = async (request, h) => { - const { arg: domain, recursive, format } = request.query + const domain = request.query.arg if (!domain) { throw Boom.badRequest("Argument 'domain' is required") } + const format = request.query.format + + // query parameters are passed as strings and need to be parsed to expected type + let recursive = request.query.recursive || request.query.r + recursive = !(recursive && recursive === 'false') + const path = await request.server.app.ipfs.dns(domain, { recursive, format }) return h.response({ Path: path