fix: reduce identify message size limit (#1230)

Adds a config option to specify a maximum message size we'll accept
for an Identify message.

The default is 8KB, the same as go-libp2p - previously we fell back
to the default `maxMessageLength` option of `it-length-prefixed`
which is 4MB.

Also adds a default timeout for reading responses to identify
requests which is used if an AbortSignal is not passed in.

The default timeout also aligns with go-libp2p.
This commit is contained in:
Alex Potsides
2022-05-31 17:10:40 +01:00
committed by GitHub
parent 4c0c2c6d3e
commit 824720fb8f
3 changed files with 145 additions and 7 deletions

View File

@ -1,4 +1,5 @@
/* eslint-env mocha */
/* eslint max-nested-callbacks: ["error", 6] */
import { expect } from 'aegir/chai'
import sinon from 'sinon'
@ -26,6 +27,8 @@ import { DefaultTransportManager } from '../../src/transport-manager.js'
import delay from 'delay'
import { start, stop } from '@libp2p/interfaces/startable'
import { TimeoutController } from 'timeout-abort-controller'
import { CustomEvent } from '@libp2p/interfaces/events'
import pDefer from 'p-defer'
const listenMaddrs = [new Multiaddr('/ip4/127.0.0.1/tcp/15002/ws')]
@ -270,4 +273,105 @@ describe('identify', () => {
const { stream } = await newStreamSpy.getCall(0).returnValue
expect(stream).to.have.nested.property('timeline.close')
})
it('should limit incoming identify message sizes', async () => {
const deferred = pDefer()
const remoteIdentify = new IdentifyService(remoteComponents, {
...defaultInit,
maxIdentifyMessageSize: 100
})
await start(remoteIdentify)
const identifySpy = sinon.spy(remoteIdentify, 'identify')
const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents)
// handle incoming identify requests and send too much data
await localComponents.getRegistrar().handle('/ipfs/id/1.0.0', ({ stream }) => {
const data = new Uint8Array(1024)
void Promise.resolve().then(async () => {
await pipe(
[data],
lp.encode(),
stream,
async (source) => await drain(source)
)
deferred.resolve()
})
})
// ensure connections are registered by connection manager
localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', {
detail: localToRemote
}))
remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', {
detail: remoteToLocal
}))
await deferred.promise
await stop(remoteIdentify)
expect(identifySpy.called).to.be.true()
await expect(identifySpy.getCall(0).returnValue)
.to.eventually.be.rejected.with.property('code', 'ERR_MSG_DATA_TOO_LONG')
})
it('should time out incoming identify messages', async () => {
const deferred = pDefer()
const remoteIdentify = new IdentifyService(remoteComponents, {
...defaultInit,
timeout: 100
})
await start(remoteIdentify)
const identifySpy = sinon.spy(remoteIdentify, 'identify')
const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents)
// handle incoming identify requests and don't send anything
await localComponents.getRegistrar().handle('/ipfs/id/1.0.0', ({ stream }) => {
const data = new Uint8Array(1024)
void Promise.resolve().then(async () => {
await pipe(
[data],
lp.encode(),
async (source) => {
await stream.sink(async function * () {
for await (const buf of source) {
// don't send all of the data, remote will expect another message
yield buf.slice(0, buf.length - 100)
// wait for longer than the timeout without sending any more data or closing the stream
await delay(500)
}
}())
}
)
deferred.resolve()
})
})
// ensure connections are registered by connection manager
localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', {
detail: localToRemote
}))
remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', {
detail: remoteToLocal
}))
await deferred.promise
await stop(remoteIdentify)
expect(identifySpy.called).to.be.true()
await expect(identifySpy.getCall(0).returnValue)
.to.eventually.be.rejected.with.property('code', 'ABORT_ERR')
})
})