Skip to content

Commit 636e950

Browse files
authored
fix: support recursive DNSLink lookups (#897)
If an IPNS name in a TXT record is not parsable as a PeerId, it's probably another DNSLink, so try to resolve it as such.
1 parent 027bd35 commit 636e950

File tree

4 files changed

+97
-9
lines changed

4 files changed

+97
-9
lines changed

packages/dnslink/src/dnslink.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,8 @@ export class DNSLink <Namespaces extends Record<string, DNSLinkParser<DNSLinkRes
159159

160160
if (protocol === 'dnslink') {
161161
// if the result was another DNSLink domain, try to follow it
162-
return await this.recursiveResolveDomain(domainOrCID, depth - 1, options)
162+
output.push(...await this.recursiveResolveDomain(domainOrCID, depth - 1, options))
163+
continue
163164
}
164165

165166
const parser = this.namespaces[protocol]
@@ -169,6 +170,14 @@ export class DNSLink <Namespaces extends Record<string, DNSLinkParser<DNSLinkRes
169170
continue
170171
}
171172

173+
const record = parser(result, answer)
174+
175+
if (record.namespace === 'dnslink') {
176+
// if the result was another DNSLink domain, try to follow it
177+
output.push(...await this.recursiveResolveDomain(record.value, depth - 1, options))
178+
continue
179+
}
180+
172181
output.push(parser(result, answer))
173182
} catch (err: any) {
174183
this.log.error('could not parse DNS link record for domain %s, %s - %e', domain, answer.data, err)

packages/dnslink/src/index.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,28 @@ export interface DNSLinkIPNSResult {
181181
path: string
182182
}
183183

184+
export interface DNSLinkDNSLinkResult {
185+
/**
186+
* The resolved record
187+
*/
188+
answer: Answer
189+
190+
/**
191+
* The IPNS namespace
192+
*/
193+
namespace: 'dnslink'
194+
195+
/**
196+
* The resolved value
197+
*/
198+
value: string
199+
200+
/**
201+
* If the resolved value is an IPFS path, it will be present here
202+
*/
203+
path: string
204+
}
205+
184206
export interface DNSLinkResolveResult {
185207
/**
186208
* The resolved record
Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,30 @@
11
import { peerIdFromString } from '@libp2p/peer-id'
22
import { InvalidNamespaceError } from '../errors.ts'
3-
import type { DNSLinkParser, DNSLinkIPNSResult } from '../index.js'
3+
import type { DNSLinkParser, DNSLinkIPNSResult, DNSLinkDNSLinkResult } from '../index.js'
44
import type { Answer } from '@multiformats/dns'
55

6-
export const ipns: DNSLinkParser<DNSLinkIPNSResult> = (value: string, answer: Answer): DNSLinkIPNSResult => {
6+
export const ipns: DNSLinkParser<DNSLinkIPNSResult | DNSLinkDNSLinkResult> = (value: string, answer: Answer): DNSLinkIPNSResult | DNSLinkDNSLinkResult => {
77
const [, protocol, peerId, ...rest] = value.split('/')
88

99
if (protocol !== 'ipns') {
1010
throw new InvalidNamespaceError(`Namespace ${protocol} was not "ipns"`)
1111
}
1212

13-
// if the result is a CID, we've reached the end of the recursion
14-
return {
15-
namespace: 'ipns',
16-
peerId: peerIdFromString(peerId),
17-
path: rest.length > 0 ? `/${rest.join('/')}` : '',
18-
answer
13+
try {
14+
// if the result parses as a PeerId, we've reached the end of the recursion
15+
return {
16+
namespace: 'ipns',
17+
peerId: peerIdFromString(peerId),
18+
path: rest.length > 0 ? `/${rest.join('/')}` : '',
19+
answer
20+
}
21+
} catch {
22+
// if the value did not parse as a PeerId, it's probably another DNSLink
23+
return {
24+
namespace: 'dnslink',
25+
value: peerId,
26+
path: rest.length > 0 ? `/${rest.join('/')}` : '',
27+
answer
28+
}
1929
}
2030
}

packages/dnslink/test/index.spec.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,4 +362,51 @@ describe('dnslink', () => {
362362
expect(result).to.have.nested.property('[0].namespace', 'hello')
363363
expect(result).to.have.nested.property('[0].value', 'world')
364364
})
365+
366+
it('should resolve recursive DNSLink names', async () => {
367+
dns.query.withArgs('_dnslink.foobar.baz').resolves(dnsResponse([{
368+
name: '_dnslink.foobar.baz.',
369+
TTL: 60,
370+
type: RecordType.TXT,
371+
data: 'dnslink=/ipns/qux.quux'
372+
}]))
373+
374+
dns.query.withArgs('_dnslink.qux.quux').resolves(dnsResponse([{
375+
name: '_dnslink.qux.quux.',
376+
TTL: 60,
377+
type: RecordType.TXT,
378+
data: 'dnslink=/ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn'
379+
}]))
380+
381+
const result = await name.resolve('foobar.baz')
382+
383+
expect(result).to.have.deep.nested.property('[0].cid', CID.parse('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn'))
384+
})
385+
386+
it('should resolve multiple recursive DNSLink names', async () => {
387+
dns.query.withArgs('_dnslink.foobar.baz').resolves(dnsResponse([{
388+
name: '_dnslink.foobar.baz.',
389+
TTL: 60,
390+
type: RecordType.TXT,
391+
data: 'dnslink=/ipns/qux.quux'
392+
}, {
393+
name: '_dnslink.foobar.baz.',
394+
TTL: 60,
395+
type: RecordType.TXT,
396+
// spellchecker:disable-next-line
397+
data: 'dnslink=/ipfs/bafybeifcaqowoyito3qvsmbwbiugsu4umlxn4ehu223hvtubbfvwyuxjoe'
398+
}]))
399+
400+
dns.query.withArgs('_dnslink.qux.quux').resolves(dnsResponse([{
401+
name: '_dnslink.qux.quux.',
402+
TTL: 60,
403+
type: RecordType.TXT,
404+
data: 'dnslink=/ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn'
405+
}]))
406+
407+
const result = await name.resolve('foobar.baz')
408+
409+
expect(result).to.have.deep.nested.property('[0].cid', CID.parse('bafybeifcaqowoyito3qvsmbwbiugsu4umlxn4ehu223hvtubbfvwyuxjoe'))
410+
expect(result).to.have.deep.nested.property('[1].cid', CID.parse('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn'))
411+
})
365412
})

0 commit comments

Comments
 (0)