-
Notifications
You must be signed in to change notification settings - Fork 446
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(@libp2p/mdns): do not send TXT records that are too long #2014
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
import { logger } from '@libp2p/logger' | ||
import { peerIdFromString } from '@libp2p/peer-id' | ||
import { multiaddr, type Multiaddr } from '@multiformats/multiaddr' | ||
import { isPrivate } from '@libp2p/utils/multiaddr/is-private' | ||
import { multiaddr, type Multiaddr, protocols } from '@multiformats/multiaddr' | ||
import type { PeerInfo } from '@libp2p/interface/peer-info' | ||
import type { Answer, StringAnswer, TxtAnswer } from 'dns-packet' | ||
import type { MulticastDNS, QueryPacket, ResponsePacket } from 'multicast-dns' | ||
|
@@ -9,7 +10,7 @@ const log = logger('libp2p:mdns:query') | |
|
||
export function queryLAN (mdns: MulticastDNS, serviceTag: string, interval: number): ReturnType<typeof setInterval> { | ||
const query = (): void => { | ||
log('query', serviceTag) | ||
log.trace('query', serviceTag) | ||
|
||
mdns.query({ | ||
questions: [{ | ||
|
@@ -40,6 +41,16 @@ export function gotResponse (rsp: ResponsePacket, localPeerName: string, service | |
} | ||
}) | ||
|
||
// according to the spec, peer details should be in the additional records, | ||
// not the answers though it seems go-libp2p at least ignores this? | ||
// https://github.com/libp2p/specs/blob/master/discovery/mdns.md#response | ||
rsp.additionals.forEach((answer) => { | ||
switch (answer.type) { | ||
case 'TXT': txtAnswers.push(answer); break | ||
default: break | ||
} | ||
}) | ||
|
||
if (answerPTR == null || | ||
answerPTR?.name !== serviceTag || | ||
txtAnswers.length === 0 || | ||
|
@@ -63,7 +74,7 @@ export function gotResponse (rsp: ResponsePacket, localPeerName: string, service | |
|
||
return { | ||
id: peerIdFromString(peerId), | ||
multiaddrs, | ||
multiaddrs: multiaddrs.map(addr => addr.decapsulateCode(protocols('p2p').code)), | ||
protocols: [] | ||
} | ||
} catch (e) { | ||
|
@@ -92,20 +103,45 @@ export function gotQuery (qry: QueryPacket, mdns: MulticastDNS, peerName: string | |
data: peerName + '.' + serviceTag | ||
}) | ||
|
||
multiaddrs.forEach((addr) => { | ||
// spec mandates multiaddr contains peer id | ||
if (addr.getPeerId() != null) { | ||
multiaddrs | ||
// mDNS requires link-local addresses only | ||
// https://github.com/libp2p/specs/blob/master/discovery/mdns.md#issues | ||
.filter(isLinkLocal) | ||
.forEach((addr) => { | ||
const data = 'dnsaddr=' + addr.toString() | ||
|
||
// TXT record fields have a max data length of 255 bytes | ||
// see 6.1 - https://www.ietf.org/rfc/rfc6763.txt | ||
if (data.length > 255) { | ||
log('multiaddr %a is too long to use in mDNS query response', addr) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will likely lead to a lot of circuit relay webtransport multiaddrs failing, won't it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not failing, just being omitted from the mDNS response. go-libp2p just excludes all circuit addresses so I'm not sure if it's a big deal or not. My feeling is that mDNS is really for tcp/quic sort of addresses. |
||
return | ||
} | ||
|
||
// spec mandates multiaddr contains peer id | ||
if (addr.getPeerId() == null) { | ||
log('multiaddr %a did not have a peer ID so cannot be used in mDNS query response', addr) | ||
return | ||
} | ||
|
||
answers.push({ | ||
name: peerName + '.' + serviceTag, | ||
type: 'TXT', | ||
class: 'IN', | ||
ttl: 120, | ||
data: 'dnsaddr=' + addr.toString() | ||
data | ||
}) | ||
} | ||
}) | ||
}) | ||
|
||
log('responding to query') | ||
log.trace('responding to query') | ||
mdns.respond(answers) | ||
} | ||
} | ||
|
||
function isLinkLocal (ma: Multiaddr): boolean { | ||
// match private ip4/ip6 & loopback addresses | ||
if (isPrivate(ma)) { | ||
return true | ||
} | ||
|
||
return false | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,25 +38,25 @@ describe('MulticastDNS', () => { | |
]) | ||
|
||
aMultiaddrs = [ | ||
multiaddr('/ip4/127.0.0.1/tcp/20001'), | ||
multiaddr('/ip4/192.168.1.142/tcp/20001'), | ||
multiaddr('/dns4/webrtc-star.discovery.libp2p.io/tcp/443/wss/p2p-webrtc-star'), | ||
multiaddr('/dns4/discovery.libp2p.io/tcp/8443') | ||
] | ||
|
||
bMultiaddrs = [ | ||
multiaddr('/ip4/127.0.0.1/tcp/20002'), | ||
multiaddr('/ip6/::1/tcp/20002'), | ||
multiaddr('/ip4/192.168.1.143/tcp/20002'), | ||
multiaddr('/ip6/2604:1380:4602:5c00::3/tcp/20002'), | ||
multiaddr('/dnsaddr/discovery.libp2p.io') | ||
] | ||
|
||
cMultiaddrs = [ | ||
multiaddr('/ip4/127.0.0.1/tcp/20003'), | ||
multiaddr('/ip4/127.0.0.1/tcp/30003/ws'), | ||
multiaddr('/ip4/192.168.1.144/tcp/20003'), | ||
multiaddr('/ip4/192.168.1.144/tcp/30003/ws'), | ||
multiaddr('/dns4/discovery.libp2p.io') | ||
] | ||
|
||
dMultiaddrs = [ | ||
multiaddr('/ip4/127.0.0.1/tcp/30003/ws') | ||
multiaddr('/ip4/192.168.1.145/tcp/30003/ws') | ||
] | ||
}) | ||
|
||
|
@@ -110,7 +110,8 @@ describe('MulticastDNS', () => { | |
await pWaitFor(() => peers.has(expectedPeer)) | ||
mdnsA.removeEventListener('peer', foundPeer) | ||
|
||
expect(peers.get(expectedPeer).multiaddrs.length).to.equal(3) | ||
// everything except loopback | ||
expect(peers.get(expectedPeer).multiaddrs.length).to.equal(2) | ||
|
||
await stop(mdnsA, mdnsB, mdnsD) | ||
}) | ||
|
@@ -141,15 +142,6 @@ describe('MulticastDNS', () => { | |
await stop(mdnsC) | ||
}) | ||
|
||
it('should start and stop with go-libp2p-mdns compat', async () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why remove this test? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Historically go-libp2p-mdns didn't speak spec-compliant mDNS so we had a compatibility layer. go-libp2p-mdns has since been updated to behave better so the compatibility layer was removed, this test should have gone too. |
||
const mdnsA = mdns({ | ||
port: 50004 | ||
})(getComponents(pA, aMultiaddrs)) | ||
|
||
await start(mdnsA) | ||
await stop(mdnsA) | ||
}) | ||
|
||
it('should not emit undefined peer ids', async () => { | ||
const mdnsA = mdns({ | ||
port: 50004 | ||
|
@@ -219,4 +211,80 @@ describe('MulticastDNS', () => { | |
|
||
await stop(mdnsA, mdnsB) | ||
}) | ||
|
||
it('only includes link-local addresses', async function () { | ||
this.timeout(40 * 1000) | ||
|
||
// these are not link-local addresses | ||
const publicAddress = '/ip4/48.52.76.32/tcp/1234' | ||
const relayDnsAddress = `/dnsaddr/example.org/tcp/1234/p2p/${pD.toString()}/p2p-circuit` | ||
const dnsAddress = '/dns4/example.org/tcp/1234' | ||
|
||
// this address is too long to fit in a TXT record | ||
const longAddress = `/ip4/192.168.1.142/udp/4001/quic-v1/webtransport/certhash/uEiDils3hWFJmsWOJIoMPxAcpzlyFNxTDZpklIoB8643ddw/certhash/uEiAM4BGr4OMK3O9cFGwfbNc4J7XYnsKE5wNPKKaTLa4fkw/p2p/${pD.toString()}/p2p-circuit` | ||
|
||
// these are link-local addresses | ||
const relayAddress = `/ip4/192.168.1.142/tcp/1234/p2p/${pD.toString()}/p2p-circuit` | ||
const localAddress = '/ip4/192.168.1.123/tcp/1234' | ||
const localWsAddress = '/ip4/192.168.1.123/tcp/1234/ws' | ||
|
||
// these are not link-local but go-libp2p advertises loopback addresses even | ||
// though you shouldn't for mDNS | ||
const loopbackAddress = '/ip4/127.0.0.1/tcp/1234' | ||
const loopbackAddress6 = '/ip6/::1/tcp/1234' | ||
|
||
const mdnsA = mdns({ | ||
broadcast: false, // do not talk to ourself | ||
port: 50005, | ||
ip: '224.0.0.252' | ||
})(getComponents(pA, aMultiaddrs)) | ||
|
||
const mdnsB = mdns({ | ||
port: 50005, // port must be the same | ||
ip: '224.0.0.252' // ip must be the same | ||
})(getComponents(pB, [ | ||
multiaddr(publicAddress), | ||
multiaddr(relayAddress), | ||
multiaddr(relayDnsAddress), | ||
multiaddr(localAddress), | ||
multiaddr(loopbackAddress), | ||
multiaddr(loopbackAddress6), | ||
multiaddr(dnsAddress), | ||
multiaddr(longAddress), | ||
multiaddr(localWsAddress) | ||
])) | ||
|
||
await start(mdnsA, mdnsB) | ||
|
||
const { detail: { id, multiaddrs } } = await new Promise<CustomEvent<PeerInfo>>((resolve) => { | ||
mdnsA.addEventListener('peer', resolve, { | ||
once: true | ||
}) | ||
}) | ||
|
||
expect(pB.toString()).to.eql(id.toString()) | ||
|
||
;[ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pre-colon? 🤢 |
||
publicAddress, | ||
relayDnsAddress, | ||
dnsAddress, | ||
longAddress | ||
].forEach(addr => { | ||
expect(multiaddrs.map(ma => ma.toString())) | ||
.to.not.include(addr) | ||
}) | ||
|
||
;[ | ||
relayAddress, | ||
localAddress, | ||
localWsAddress, | ||
loopbackAddress, | ||
loopbackAddress6 | ||
].forEach(addr => { | ||
expect(multiaddrs.map(ma => ma.toString())) | ||
.to.include(addr) | ||
}) | ||
|
||
await stop(mdnsA, mdnsB) | ||
}) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be .error or .info?