Skip to content

Commit 0f87479

Browse files
authored
fix: confirm dns mappings with ip mappings (#2861)
Wildcard DNS servers let us map anything we think is a public ip address to a domain name, but ports stil need to be opened in firewalls so don't use the DNS mapping until an external port mapping has been configured.
1 parent f2f9008 commit 0f87479

File tree

2 files changed

+123
-45
lines changed

2 files changed

+123
-45
lines changed

packages/libp2p/src/address-manager.ts

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ interface PublicAddressMapping {
8787
externalPort: number
8888
}
8989

90+
interface DNSMapping {
91+
domain: string
92+
confident: boolean
93+
}
94+
9095
export class AddressManager implements AddressManagerInterface {
9196
private readonly log: Logger
9297
private readonly components: AddressManagerComponents
@@ -96,7 +101,7 @@ export class AddressManager implements AddressManagerInterface {
96101
private readonly appendAnnounce: Set<string>
97102
private readonly observed: Map<string, ObservedAddressMetadata>
98103
private readonly announceFilter: AddressFilter
99-
private readonly ipDomainMappings: Map<string, string>
104+
private readonly ipDomainMappings: Map<string, DNSMapping>
100105
private readonly publicAddressMappings: Map<string, PublicAddressMapping[]>
101106

102107
/**
@@ -269,7 +274,7 @@ export class AddressManager implements AddressManagerInterface {
269274
return
270275
}
271276

272-
mappings.forEach(mapping => {
277+
for (const mapping of mappings) {
273278
tuples[0][0] = isIPv4(mapping.externalIp) ? CODEC_IP4 : CODEC_IP6
274279
tuples[0][1] = mapping.externalIp
275280
tuples[1][1] = `${mapping.externalPort}`
@@ -284,7 +289,7 @@ export class AddressManager implements AddressManagerInterface {
284289
}).join('/')
285290
}`)
286291
)
287-
})
292+
}
288293
})
289294
multiaddrs = multiaddrs.concat(ipMappedMultiaddrs)
290295

@@ -294,21 +299,25 @@ export class AddressManager implements AddressManagerInterface {
294299
const tuples = ma.stringTuples()
295300
let mappedIp = false
296301

297-
for (const [ip, domain] of this.ipDomainMappings.entries()) {
302+
for (const [ip, mapping] of this.ipDomainMappings.entries()) {
303+
if (!mapping.confident) {
304+
continue
305+
}
306+
298307
for (let i = 0; i < tuples.length; i++) {
299308
if (tuples[i][1] !== ip) {
300309
continue
301310
}
302311

303312
if (tuples[i][0] === CODEC_IP4) {
304313
tuples[i][0] = CODEC_DNS4
305-
tuples[i][1] = domain
314+
tuples[i][1] = mapping.domain
306315
mappedIp = true
307316
}
308317

309318
if (tuples[i][0] === CODEC_IP6) {
310319
tuples[i][0] = CODEC_DNS6
311-
tuples[i][1] = domain
320+
tuples[i][1] = mapping.domain
312321
mappedIp = true
313322
}
314323
}
@@ -366,14 +375,23 @@ export class AddressManager implements AddressManagerInterface {
366375
addDNSMapping (domain: string, addresses: string[]): void {
367376
addresses.forEach(ip => {
368377
this.log('add DNS mapping %s to %s', ip, domain)
369-
this.ipDomainMappings.set(ip, domain)
378+
379+
// check ip/public ip mappings to see if we think we are contactable
380+
const confident = [...this.publicAddressMappings.entries()].some(([key, mappings]) => {
381+
return mappings.some(mapping => mapping.externalIp === ip)
382+
})
383+
384+
this.ipDomainMappings.set(ip, {
385+
domain,
386+
confident
387+
})
370388
})
371389
this._updatePeerStoreAddresses()
372390
}
373391

374392
removeDNSMapping (domain: string): void {
375-
for (const [key, value] of this.ipDomainMappings.entries()) {
376-
if (value === domain) {
393+
for (const [key, mapping] of this.ipDomainMappings.entries()) {
394+
if (mapping.domain === domain) {
377395
this.log('remove DNS mapping for %s', domain)
378396
this.ipDomainMappings.delete(key)
379397
}
@@ -390,6 +408,17 @@ export class AddressManager implements AddressManagerInterface {
390408
})
391409

392410
this.publicAddressMappings.set(key, mappings)
411+
412+
// update domain mappings to indicate we are now confident that any matching
413+
// ip/domain combination can now be resolved externally
414+
for (const [key, mapping] of this.ipDomainMappings.entries()) {
415+
if (key === externalIp) {
416+
mapping.confident = true
417+
418+
this.ipDomainMappings.set(key, mapping)
419+
}
420+
}
421+
393422
this._updatePeerStoreAddresses()
394423
}
395424

packages/libp2p/test/addresses/address-manager.spec.ts

Lines changed: 85 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -257,97 +257,146 @@ describe('Address Manager', () => {
257257
})
258258

259259
it('should add an IPv4 DNS mapping', () => {
260+
const transportManager = stubInterface<TransportManager>()
261+
260262
const am = new AddressManager({
261263
peerId,
262-
transportManager: stubInterface<TransportManager>({
263-
getAddrs: () => []
264-
}),
264+
transportManager,
265265
peerStore,
266266
events,
267267
logger: defaultLogger()
268268
})
269269

270-
expect(am.getAddresses()).to.be.empty()
271-
272-
const externalIp = '81.12.12.1'
273-
const externalAddress = multiaddr(`/ip4/${externalIp}/tcp/1234`)
270+
const internalIp = '192.168.1.123'
271+
const internalPort = 1234
272+
const protocol = 'tcp'
274273

275-
am.confirmObservedAddr(externalAddress)
274+
// one loopback, one LAN address
275+
transportManager.getAddrs.returns([
276+
multiaddr(`/ip4/127.0.0.1/${protocol}/${internalPort}`),
277+
multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}`)
278+
])
276279

277-
expect(am.getAddresses()).to.deep.equal([externalAddress.encapsulate(`/p2p/${peerId.toString()}`)])
280+
expect(am.getAddresses()).to.deep.equal([
281+
multiaddr(`/ip4/127.0.0.1/${protocol}/${internalPort}/p2p/${peerId}`),
282+
multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId}`)
283+
])
278284

279285
const domain = 'example.com'
286+
const externalIp = '81.12.12.1'
287+
const externalPort = 4566
280288

281289
am.addDNSMapping(domain, [externalIp])
282290

291+
// have not verified DNS mapping so it is not included
283292
expect(am.getAddresses()).to.deep.equal([
284-
externalAddress.encapsulate(`/p2p/${peerId.toString()}`),
285-
multiaddr(`/dns4/${domain}/tcp/1234/p2p/${peerId.toString()}`)
293+
multiaddr(`/ip4/127.0.0.1/${protocol}/${internalPort}/p2p/${peerId}`),
294+
multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId}`)
295+
])
296+
297+
// public address mapping confirms DNS mapping
298+
am.addPublicAddressMapping(internalIp, internalPort, externalIp, externalPort, protocol)
299+
300+
expect(am.getAddresses()).to.deep.equal([
301+
multiaddr(`/ip4/127.0.0.1/${protocol}/${internalPort}/p2p/${peerId}`),
302+
multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId}`),
303+
multiaddr(`/ip4/${externalIp}/tcp/${externalPort}/p2p/${peerId}`),
304+
multiaddr(`/dns4/${domain}/tcp/${externalPort}/p2p/${peerId}`)
286305
])
287306
})
288307

289308
it('should add an IPv6 DNS mapping', () => {
309+
const transportManager = stubInterface<TransportManager>()
310+
290311
const am = new AddressManager({
291312
peerId,
292-
transportManager: stubInterface<TransportManager>({
293-
getAddrs: () => []
294-
}),
313+
transportManager,
295314
peerStore,
296315
events,
297316
logger: defaultLogger()
298317
})
299318

300-
expect(am.getAddresses()).to.be.empty()
301-
302-
const externalIp = 'fe80::7c98:a9ff:fe94'
303-
const externalAddress = multiaddr(`/ip6/${externalIp}/tcp/1234`)
319+
const internalIp = '192.168.1.123'
320+
const internalPort = 1234
321+
const protocol = 'tcp'
304322

305-
am.confirmObservedAddr(externalAddress)
323+
// one loopback, one LAN address
324+
transportManager.getAddrs.returns([
325+
multiaddr(`/ip4/127.0.0.1/${protocol}/${internalPort}`),
326+
multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}`)
327+
])
306328

307-
expect(am.getAddresses()).to.deep.equal([externalAddress.encapsulate(`/p2p/${peerId.toString()}`)])
329+
expect(am.getAddresses()).to.deep.equal([
330+
multiaddr(`/ip4/127.0.0.1/${protocol}/${internalPort}/p2p/${peerId}`),
331+
multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId}`)
332+
])
308333

309334
const domain = 'example.com'
335+
const externalIp = '2a00:23c6:14b1:7e00:c010:8ecf:2a25:dcd1'
336+
const externalPort = 4566
310337

311338
am.addDNSMapping(domain, [externalIp])
312339

340+
// have not verified DNS mapping so it is not included
313341
expect(am.getAddresses()).to.deep.equal([
314-
externalAddress.encapsulate(`/p2p/${peerId.toString()}`),
315-
multiaddr(`/dns6/${domain}/tcp/1234/p2p/${peerId.toString()}`)
342+
multiaddr(`/ip4/127.0.0.1/${protocol}/${internalPort}/p2p/${peerId}`),
343+
multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId}`)
344+
])
345+
346+
// public address mapping confirms DNS mapping
347+
am.addPublicAddressMapping(internalIp, internalPort, externalIp, externalPort, protocol)
348+
349+
expect(am.getAddresses()).to.deep.equal([
350+
multiaddr(`/ip4/127.0.0.1/${protocol}/${internalPort}/p2p/${peerId}`),
351+
multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId}`),
352+
multiaddr(`/ip6/${externalIp}/tcp/${externalPort}/p2p/${peerId}`),
353+
multiaddr(`/dns6/${domain}/tcp/${externalPort}/p2p/${peerId}`)
316354
])
317355
})
318356

319-
it('should remove add a DNS mapping', () => {
357+
it('should remove a DNS mapping', () => {
358+
const transportManager = stubInterface<TransportManager>()
359+
320360
const am = new AddressManager({
321361
peerId,
322-
transportManager: stubInterface<TransportManager>({
323-
getAddrs: () => []
324-
}),
362+
transportManager,
325363
peerStore,
326364
events,
327365
logger: defaultLogger()
328366
})
329367

330-
expect(am.getAddresses()).to.be.empty()
331-
332-
const externalIp = '81.12.12.1'
333-
const externalAddress = multiaddr(`/ip4/${externalIp}/tcp/1234`)
334-
335-
am.confirmObservedAddr(externalAddress)
368+
const internalIp = '192.168.1.123'
369+
const internalPort = 1234
370+
const protocol = 'tcp'
336371

337-
expect(am.getAddresses()).to.deep.equal([externalAddress.encapsulate(`/p2p/${peerId.toString()}`)])
372+
// one loopback, one LAN address
373+
transportManager.getAddrs.returns([
374+
multiaddr(`/ip4/127.0.0.1/${protocol}/${internalPort}`),
375+
multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}`)
376+
])
338377

339378
const domain = 'example.com'
379+
const externalIp = '81.12.12.1'
380+
const externalPort = 4566
340381

341382
am.addDNSMapping(domain, [externalIp])
383+
am.addPublicAddressMapping(internalIp, internalPort, externalIp, externalPort, protocol)
342384

343385
expect(am.getAddresses()).to.deep.equal([
344-
externalAddress.encapsulate(`/p2p/${peerId.toString()}`),
345-
multiaddr(`/dns4/${domain}/tcp/1234/p2p/${peerId.toString()}`)
386+
multiaddr(`/ip4/127.0.0.1/${protocol}/${internalPort}/p2p/${peerId}`),
387+
multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId}`),
388+
multiaddr(`/ip4/${externalIp}/tcp/${externalPort}/p2p/${peerId}`),
389+
multiaddr(`/dns4/${domain}/tcp/${externalPort}/p2p/${peerId}`)
346390
])
347391

392+
// public address mapping confirms DNS mapping
348393
am.removeDNSMapping(domain)
349394

350-
expect(am.getAddresses()).to.deep.equal([externalAddress.encapsulate(`/p2p/${peerId.toString()}`)])
395+
expect(am.getAddresses()).to.deep.equal([
396+
multiaddr(`/ip4/127.0.0.1/${protocol}/${internalPort}/p2p/${peerId}`),
397+
multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId}`),
398+
multiaddr(`/ip4/${externalIp}/tcp/${externalPort}/p2p/${peerId}`)
399+
])
351400
})
352401

353402
it('should add a public IPv4 address mapping', () => {

0 commit comments

Comments
 (0)