Skip to content

Commit a1ec46b

Browse files
authored
feat: mark connections with limits as transient (#1890)
Some connections have resources limits imposed on them, such as circuit relay connections. If these limits are breached the connection will be closed by the remote. When this is the case, the connection will have a `.transient` boolean property set to true. By default any attempt to run a protocol over a transient connection will throw (outgoing) or be reset (incoming), this is to prevent, for example, bitswap exceeding the connection transfer limit and causing the connection to be closed by the relay server when it should be reserved for the WebRTC SDP exchange to allow incoming dials. Protocols can opt-in to being run over transient connections by specifying a `runOnTransientConnection` flag during `libp2p.handle` (incoming) and `connection.openStream`/`libp2p.dialProtocol` (outgoing). Closes #1611
1 parent 7debe03 commit a1ec46b

File tree

21 files changed

+227
-57
lines changed

21 files changed

+227
-57
lines changed

packages/interface-compliance-tests/src/mocks/connection.ts

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class MockConnection implements Connection {
4343
public status: ConnectionStatus
4444
public streams: Stream[]
4545
public tags: string[]
46+
public transient: boolean
4647

4748
private readonly muxer: StreamMuxer
4849
private readonly maConn: MultiaddrConnection
@@ -63,6 +64,7 @@ class MockConnection implements Connection {
6364
this.tags = []
6465
this.muxer = muxer
6566
this.maConn = maConn
67+
this.transient = false
6668
}
6769

6870
async newStream (protocols: string | string[], options?: AbortOptions): Promise<Stream> {

packages/interface-compliance-tests/src/mocks/upgrader.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { mockConnection } from './connection.js'
22
import type { Libp2pEvents } from '@libp2p/interface'
33
import type { Connection, MultiaddrConnection } from '@libp2p/interface/connection'
44
import type { EventEmitter } from '@libp2p/interface/events'
5+
import type { Upgrader, UpgraderOptions } from '@libp2p/interface/transport'
56
import type { Registrar } from '@libp2p/interface-internal/registrar'
6-
import type { Upgrader, UpgraderOptions } from '@libp2p/interface-internal/upgrader'
77

88
export interface MockUpgraderInit {
99
registrar?: Registrar

packages/interface-internal/src/registrar/index.ts

+7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ export interface StreamHandlerOptions {
2020
* How many outgoing streams can be open for this protocol at the same time on each connection (default: 64)
2121
*/
2222
maxOutboundStreams?: number
23+
24+
/**
25+
* If true, allow this protocol to run on limited connections (e.g.
26+
* connections with data or duration limits such as circuit relay
27+
* connections) (default: false)
28+
*/
29+
runOnTransientConnection?: boolean
2330
}
2431

2532
export interface StreamHandlerRecord {

packages/interface-internal/src/upgrader/index.ts

-20
This file was deleted.

packages/interface/src/connection/index.ts

+14
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,12 @@ export interface NewStreamOptions extends AbortOptions {
178178
* for the protocol
179179
*/
180180
maxOutboundStreams?: number
181+
182+
/**
183+
* Opt-in to running over a transient connection - one that has time/data limits
184+
* placed on it.
185+
*/
186+
runOnTransientConnection?: boolean
181187
}
182188

183189
export type ConnectionStatus = 'open' | 'closing' | 'closed'
@@ -239,6 +245,14 @@ export interface Connection {
239245
*/
240246
status: ConnectionStatus
241247

248+
/**
249+
* A transient connection is one that is not expected to be open for very long
250+
* or one that cannot transfer very much data, such as one being used as a
251+
* circuit relay connection. Protocols need to explicitly opt-in to being run
252+
* over transient connections.
253+
*/
254+
transient: boolean
255+
242256
/**
243257
* Create a new stream on this connection and negotiate one of the passed protocols
244258
*/

packages/interface/src/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* ```
1515
*/
1616

17-
import type { Connection, Stream } from './connection/index.js'
17+
import type { Connection, NewStreamOptions, Stream } from './connection/index.js'
1818
import type { ContentRouting } from './content-routing/index.js'
1919
import type { EventEmitter } from './events.js'
2020
import type { KeyChain } from './keychain/index.js'
@@ -503,7 +503,7 @@ export interface Libp2p<T extends ServiceMap = ServiceMap> extends Startable, Ev
503503
* pipe([1, 2, 3], stream, consume)
504504
* ```
505505
*/
506-
dialProtocol: (peer: PeerId | Multiaddr | Multiaddr[], protocols: string | string[], options?: AbortOptions) => Promise<Stream>
506+
dialProtocol: (peer: PeerId | Multiaddr | Multiaddr[], protocols: string | string[], options?: NewStreamOptions) => Promise<Stream>
507507

508508
/**
509509
* Attempts to gracefully close an open connection to the given peer. If the

packages/interface/src/stream-handler/index.ts

+6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ export interface StreamHandlerOptions {
1919
* How many outgoing streams can be open for this protocol at the same time on each connection (default: 64)
2020
*/
2121
maxOutboundStreams?: number
22+
23+
/**
24+
* Opt-in to running over a transient connection - one that has time/data limits
25+
* placed on it.
26+
*/
27+
runOnTransientConnection?: boolean
2228
}
2329

2430
export interface StreamHandlerRecord {

packages/interface/src/transport/index.ts

+6
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ export interface UpgraderOptions {
9696
skipEncryption?: boolean
9797
skipProtection?: boolean
9898
muxerFactory?: StreamMuxerFactory
99+
100+
/**
101+
* The passed MultiaddrConnection has limits place on duration and/or data
102+
* transfer amounts so is not expected to be open for very long.
103+
*/
104+
transient?: boolean
99105
}
100106

101107
export interface Upgrader {

packages/libp2p-daemon-server/src/index.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,9 @@ export class Server implements Libp2pServer {
103103
const { peer, proto } = request.streamOpen
104104
const peerId = peerIdFromBytes(peer)
105105
const connection = await this.libp2p.dial(peerId)
106-
const stream = await connection.newStream(proto)
106+
const stream = await connection.newStream(proto, {
107+
runOnTransientConnection: true
108+
})
107109

108110
return {
109111
streamInfo: {
@@ -178,6 +180,8 @@ export class Server implements Libp2pServer {
178180
})
179181
}
180182
})
183+
}, {
184+
runOnTransientConnection: true
181185
})
182186
}
183187

packages/libp2p/src/circuit-relay/server/index.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import { MAX_CONNECTIONS } from '../../connection-manager/constants.js'
1010
import {
1111
CIRCUIT_PROTO_CODE,
1212
DEFAULT_HOP_TIMEOUT,
13-
RELAY_SOURCE_TAG
14-
, RELAY_V2_HOP_CODEC, RELAY_V2_STOP_CODEC
13+
RELAY_SOURCE_TAG,
14+
RELAY_V2_HOP_CODEC,
15+
RELAY_V2_STOP_CODEC
1516
} from '../constants.js'
1617
import { HopMessage, type Reservation, Status, StopMessage } from '../pb/index.js'
1718
import { createLimitedRelay } from '../utils.js'
@@ -172,7 +173,8 @@ class CircuitRelayServer extends EventEmitter<RelayServerEvents> implements Star
172173
})
173174
}, {
174175
maxInboundStreams: this.maxInboundHopStreams,
175-
maxOutboundStreams: this.maxOutboundHopStreams
176+
maxOutboundStreams: this.maxOutboundHopStreams,
177+
runOnTransientConnection: true
176178
})
177179

178180
this.reservationStore.start()
@@ -404,7 +406,8 @@ class CircuitRelayServer extends EventEmitter<RelayServerEvents> implements Star
404406
}: StopOptions): Promise<Stream | undefined> {
405407
log('starting circuit relay v2 stop request to %s', connection.remotePeer)
406408
const stream = await connection.newStream([RELAY_V2_STOP_CODEC], {
407-
maxOutboundStreams: this.maxOutboundStopStreams
409+
maxOutboundStreams: this.maxOutboundStopStreams,
410+
runOnTransientConnection: true
408411
})
409412
const pbstr = pbStream(stream)
410413
const stopstr = pbstr.pb(StopMessage)

packages/libp2p/src/circuit-relay/transport/index.ts

+10-7
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,8 @@ class CircuitRelayTransport implements Transport {
169169
})
170170
}, {
171171
maxInboundStreams: this.maxInboundStopStreams,
172-
maxOutboundStreams: this.maxOutboundStopStreams
172+
maxOutboundStreams: this.maxOutboundStopStreams,
173+
runOnTransientConnection: true
173174
})
174175

175176
this.started = true
@@ -275,16 +276,16 @@ class CircuitRelayTransport implements Transport {
275276
throw new CodeError(`failed to connect via relay with status ${status?.status?.toString() ?? 'undefined'}`, codes.ERR_HOP_REQUEST_FAILED)
276277
}
277278

278-
// TODO: do something with limit and transient connection
279-
280279
const maConn = streamToMaConnection({
281280
stream: pbstr.unwrap(),
282281
remoteAddr: ma,
283282
localAddr: relayAddr.encapsulate(`/p2p-circuit/p2p/${this.peerId.toString()}`)
284283
})
285284

286-
log('new outbound connection %a', maConn.remoteAddr)
287-
return await this.upgrader.upgradeOutbound(maConn)
285+
log('new outbound transient connection %a', maConn.remoteAddr)
286+
return await this.upgrader.upgradeOutbound(maConn, {
287+
transient: true
288+
})
288289
} catch (err) {
289290
log.error(`Circuit relay dial to destination ${destinationPeer.toString()} via relay ${connection.remotePeer.toString()} failed`, err)
290291
disconnectOnFailure && await connection.close()
@@ -380,8 +381,10 @@ class CircuitRelayTransport implements Transport {
380381
localAddr
381382
})
382383

383-
log('new inbound connection %s', maConn.remoteAddr)
384-
await this.upgrader.upgradeInbound(maConn)
384+
log('new inbound transient connection %a', maConn.remoteAddr)
385+
await this.upgrader.upgradeInbound(maConn, {
386+
transient: true
387+
})
385388
log('%s connection %a upgraded', 'inbound', maConn.remoteAddr)
386389
}
387390
}

packages/libp2p/src/connection/index.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { setMaxListeners } from 'events'
2-
import { type Direction, symbol, type Connection, type Stream, type ConnectionTimeline, type ConnectionStatus } from '@libp2p/interface/connection'
2+
import { symbol } from '@libp2p/interface/connection'
33
import { CodeError } from '@libp2p/interface/errors'
44
import { logger } from '@libp2p/logger'
55
import type { AbortOptions } from '@libp2p/interface'
6+
import type { Direction, Connection, Stream, ConnectionTimeline, ConnectionStatus, NewStreamOptions } from '@libp2p/interface/connection'
67
import type { PeerId } from '@libp2p/interface/peer-id'
78
import type { Multiaddr } from '@multiformats/multiaddr'
89

@@ -22,6 +23,7 @@ interface ConnectionInit {
2223
timeline: ConnectionTimeline
2324
multiplexer?: string
2425
encryption?: string
26+
transient?: boolean
2527
}
2628

2729
/**
@@ -49,6 +51,7 @@ export class ConnectionImpl implements Connection {
4951
public multiplexer?: string
5052
public encryption?: string
5153
public status: ConnectionStatus
54+
public transient: boolean
5255

5356
/**
5457
* User provided tags
@@ -59,7 +62,7 @@ export class ConnectionImpl implements Connection {
5962
/**
6063
* Reference to the new stream function of the multiplexer
6164
*/
62-
private readonly _newStream: (protocols: string[], options?: AbortOptions) => Promise<Stream>
65+
private readonly _newStream: (protocols: string[], options?: NewStreamOptions) => Promise<Stream>
6366

6467
/**
6568
* Reference to the close function of the raw connection
@@ -88,6 +91,7 @@ export class ConnectionImpl implements Connection {
8891
this.timeline = init.timeline
8992
this.multiplexer = init.multiplexer
9093
this.encryption = init.encryption
94+
this.transient = init.transient ?? false
9195

9296
this._newStream = newStream
9397
this._close = close
@@ -110,7 +114,7 @@ export class ConnectionImpl implements Connection {
110114
/**
111115
* Create a new stream from this connection
112116
*/
113-
async newStream (protocols: string | string[], options?: AbortOptions): Promise<Stream> {
117+
async newStream (protocols: string | string[], options?: NewStreamOptions): Promise<Stream> {
114118
if (this.status === 'closing') {
115119
throw new CodeError('the connection is being closed', 'ERR_CONNECTION_BEING_CLOSED')
116120
}
@@ -123,6 +127,10 @@ export class ConnectionImpl implements Connection {
123127
protocols = [protocols]
124128
}
125129

130+
if (this.transient && options?.runOnTransientConnection !== true) {
131+
throw new CodeError('Cannot open protocol stream on transient connection', 'ERR_TRANSIENT_CONNECTION')
132+
}
133+
126134
const stream = await this._newStream(protocols, options)
127135

128136
stream.direction = 'outbound'

packages/libp2p/src/identify/identify.ts

+14-5
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ const defaultValues = {
4545
maxPushOutgoingStreams: 1,
4646
maxObservedAddresses: 10,
4747
maxIdentifyMessageSize: 8192,
48-
runOnConnectionOpen: true
48+
runOnConnectionOpen: true,
49+
runOnTransientConnection: true
4950
}
5051

5152
export class DefaultIdentifyService implements Startable, IdentifyService {
@@ -70,6 +71,7 @@ export class DefaultIdentifyService implements Startable, IdentifyService {
7071
private readonly maxIdentifyMessageSize: number
7172
private readonly maxObservedAddresses: number
7273
private readonly events: EventEmitter<Libp2pEvents>
74+
private readonly runOnTransientConnection: boolean
7375

7476
constructor (components: IdentifyServiceComponents, init: IdentifyServiceInit) {
7577
this.started = false
@@ -89,6 +91,7 @@ export class DefaultIdentifyService implements Startable, IdentifyService {
8991
this.maxPushOutgoingStreams = init.maxPushOutgoingStreams ?? defaultValues.maxPushOutgoingStreams
9092
this.maxIdentifyMessageSize = init.maxIdentifyMessageSize ?? defaultValues.maxIdentifyMessageSize
9193
this.maxObservedAddresses = init.maxObservedAddresses ?? defaultValues.maxObservedAddresses
94+
this.runOnTransientConnection = init.runOnTransientConnection ?? defaultValues.runOnTransientConnection
9295

9396
// Store self host metadata
9497
this.host = {
@@ -141,15 +144,17 @@ export class DefaultIdentifyService implements Startable, IdentifyService {
141144
})
142145
}, {
143146
maxInboundStreams: this.maxInboundStreams,
144-
maxOutboundStreams: this.maxOutboundStreams
147+
maxOutboundStreams: this.maxOutboundStreams,
148+
runOnTransientConnection: this.runOnTransientConnection
145149
})
146150
await this.registrar.handle(this.identifyPushProtocolStr, (data) => {
147151
void this._handlePush(data).catch(err => {
148152
log.error(err)
149153
})
150154
}, {
151155
maxInboundStreams: this.maxPushIncomingStreams,
152-
maxOutboundStreams: this.maxPushOutgoingStreams
156+
maxOutboundStreams: this.maxPushOutgoingStreams,
157+
runOnTransientConnection: this.runOnTransientConnection
153158
})
154159

155160
this.started = true
@@ -189,7 +194,8 @@ export class DefaultIdentifyService implements Startable, IdentifyService {
189194

190195
try {
191196
stream = await connection.newStream([this.identifyPushProtocolStr], {
192-
signal
197+
signal,
198+
runOnTransientConnection: this.runOnTransientConnection
193199
})
194200

195201
const pb = pbStream(stream, {
@@ -257,7 +263,10 @@ export class DefaultIdentifyService implements Startable, IdentifyService {
257263
options.signal = options.signal ?? AbortSignal.timeout(this.timeout)
258264

259265
try {
260-
stream = await connection.newStream([this.identifyProtocolStr], options)
266+
stream = await connection.newStream([this.identifyProtocolStr], {
267+
...options,
268+
runOnTransientConnection: this.runOnTransientConnection
269+
})
261270

262271
const pb = pbStream(stream, {
263272
maxDataLength: this.maxIdentifyMessageSize ?? MAX_IDENTIFY_MESSAGE_SIZE

packages/libp2p/src/identify/index.ts

+5
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ export interface IdentifyServiceInit {
4545
* Whether to automatically dial identify on newly opened connections (default: true)
4646
*/
4747
runOnConnectionOpen?: boolean
48+
49+
/**
50+
* Whether to run on connections with data or duration limits (default: true)
51+
*/
52+
runOnTransientConnection?: boolean
4853
}
4954

5055
export interface IdentifyServiceComponents {

packages/libp2p/src/libp2p.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import { DefaultUpgrader } from './upgrader.js'
3030
import type { Components } from './components.js'
3131
import type { Libp2p, Libp2pInit, Libp2pOptions } from './index.js'
3232
import type { Libp2pEvents, PendingDial, ServiceMap, AbortOptions } from '@libp2p/interface'
33-
import type { Connection, Stream } from '@libp2p/interface/connection'
33+
import type { Connection, NewStreamOptions, Stream } from '@libp2p/interface/connection'
3434
import type { KeyChain } from '@libp2p/interface/keychain'
3535
import type { Metrics } from '@libp2p/interface/metrics'
3636
import type { PeerId } from '@libp2p/interface/peer-id'
@@ -283,7 +283,7 @@ export class Libp2pNode<T extends ServiceMap = Record<string, unknown>> extends
283283
return this.components.connectionManager.openConnection(peer, options)
284284
}
285285

286-
async dialProtocol (peer: PeerId | Multiaddr | Multiaddr[], protocols: string | string[], options: AbortOptions = {}): Promise<Stream> {
286+
async dialProtocol (peer: PeerId | Multiaddr | Multiaddr[], protocols: string | string[], options: NewStreamOptions = {}): Promise<Stream> {
287287
if (protocols == null) {
288288
throw new CodeError('no protocols were provided to open a stream', codes.ERR_INVALID_PROTOCOLS_FOR_STREAM)
289289
}

0 commit comments

Comments
 (0)