Skip to content

Commit 5c4a79e

Browse files
authored
fix: auto-confirm relay addresses (#2886)
After we have created a reservation on a relay, automatically confirm that it is publicly dialable. Fixes #2883
1 parent 58542c6 commit 5c4a79e

File tree

6 files changed

+186
-33
lines changed

6 files changed

+186
-33
lines changed

packages/interface-internal/src/address-manager/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ export interface ConfirmAddressOptions {
5050
* Override the TTL of the observed address verification
5151
*/
5252
ttl?: number
53+
54+
/**
55+
* Allows hinting which type of address this is
56+
*/
57+
type?: AddressType
5358
}
5459

5560
export interface AddressManager {

packages/libp2p/src/address-manager/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -249,19 +249,19 @@ export class AddressManager implements AddressManagerInterface {
249249
addr = stripPeerId(addr, this.components.peerId)
250250
let startingConfidence = true
251251

252-
if (this.observed.has(addr)) {
252+
if (options?.type === 'observed' || this.observed.has(addr)) {
253253
startingConfidence = this.observed.confirm(addr, options?.ttl ?? this.addressVerificationTTL)
254254
}
255255

256-
if (this.transportAddresses.has(addr)) {
256+
if (options?.type === 'transport' || this.transportAddresses.has(addr)) {
257257
startingConfidence = this.transportAddresses.confirm(addr, options?.ttl ?? this.addressVerificationTTL)
258258
}
259259

260-
if (this.dnsMappings.has(addr)) {
260+
if (options?.type === 'dns-mapping' || this.dnsMappings.has(addr)) {
261261
startingConfidence = this.dnsMappings.confirm(addr, options?.ttl ?? this.addressVerificationTTL)
262262
}
263263

264-
if (this.ipMappings.has(addr)) {
264+
if (options?.type === 'ip-mapping' || this.ipMappings.has(addr)) {
265265
startingConfidence = this.ipMappings.confirm(addr, options?.ttl ?? this.addressVerificationTTL)
266266
}
267267

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,4 +680,38 @@ describe('Address Manager', () => {
680680
multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId.toString()}`)
681681
])
682682
})
683+
684+
it('should confirm unknown observed addresses with hints', () => {
685+
const transportManager = stubInterface<TransportManager>()
686+
const am = new AddressManager({
687+
peerId,
688+
transportManager,
689+
peerStore,
690+
events,
691+
logger: defaultLogger()
692+
})
693+
694+
const internalIp = '192.168.1.123'
695+
const internalPort = 4567
696+
const externalIp = '2a00:23c6:14b1:7e00:28b8:30d:944e:27f3'
697+
const externalPort = 8910
698+
const protocol = 'tcp'
699+
700+
// confirm address before fetching addresses
701+
am.confirmObservedAddr(multiaddr(`/ip6/${externalIp}/${protocol}/${externalPort}`), {
702+
type: 'transport'
703+
})
704+
705+
// one loopback, one LAN address
706+
transportManager.getAddrs.returns([
707+
multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}`),
708+
multiaddr(`/ip6/${externalIp}/${protocol}/${externalPort}`)
709+
])
710+
711+
// should have changed the address list
712+
expect(am.getAddresses()).to.deep.equal([
713+
multiaddr(`/ip4/${internalIp}/${protocol}/${internalPort}/p2p/${peerId.toString()}`),
714+
multiaddr(`/ip6/${externalIp}/${protocol}/${externalPort}/p2p/${peerId.toString()}`)
715+
])
716+
})
683717
})

packages/transport-circuit-relay-v2/src/transport/listener.ts

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ import { ListenError, TypedEventEmitter, setMaxListeners } from '@libp2p/interfa
22
import { multiaddr } from '@multiformats/multiaddr'
33
import { DEFAULT_RESERVATION_COMPLETION_TIMEOUT } from '../constants.js'
44
import { CircuitListen, CircuitSearch } from '../utils.js'
5-
import type { RelayDiscovery } from './discovery.js'
65
import type { RelayReservation, ReservationStore } from './reservation-store.js'
76
import type { ComponentLogger, Logger, Listener, ListenerEvents, PeerId } from '@libp2p/interface'
8-
import type { ConnectionManager } from '@libp2p/interface-internal'
7+
import type { AddressManager, ConnectionManager } from '@libp2p/interface-internal'
98
import type { Multiaddr } from '@multiformats/multiaddr'
109

1110
export interface CircuitRelayTransportListenerComponents {
11+
peerId: PeerId
1212
connectionManager: ConnectionManager
13-
relayStore: ReservationStore
13+
addressManager: AddressManager
14+
reservationStore: ReservationStore
1415
logger: ComponentLogger
1516
}
1617

@@ -19,9 +20,10 @@ export interface CircuitRelayTransportListenerInit {
1920
}
2021

2122
class CircuitRelayTransportListener extends TypedEventEmitter<ListenerEvents> implements Listener {
23+
private readonly peerId: PeerId
2224
private readonly connectionManager: ConnectionManager
25+
private readonly addressManager: AddressManager
2326
private readonly reservationStore: ReservationStore
24-
private readonly discovery?: RelayDiscovery
2527
private listeningAddrs: Multiaddr[]
2628
private readonly log: Logger
2729
private readonly listenTimeout: number
@@ -32,8 +34,10 @@ class CircuitRelayTransportListener extends TypedEventEmitter<ListenerEvents> im
3234
super()
3335

3436
this.log = components.logger.forComponent('libp2p:circuit-relay:transport:listener')
37+
this.peerId = components.peerId
3538
this.connectionManager = components.connectionManager
36-
this.reservationStore = components.relayStore
39+
this.addressManager = components.addressManager
40+
this.reservationStore = components.reservationStore
3741
this.listeningAddrs = []
3842
this.listenTimeout = init.listenTimeout ?? DEFAULT_RESERVATION_COMPLETION_TIMEOUT
3943

@@ -51,6 +55,11 @@ class CircuitRelayTransportListener extends TypedEventEmitter<ListenerEvents> im
5155

5256
this.log('relay peer removed %p', evt.detail.relay)
5357

58+
this.listeningAddrs.forEach(ma => {
59+
// mark as externally dialable
60+
this.addressManager.removeObservedAddr(ma)
61+
})
62+
5463
this.listeningAddrs = []
5564

5665
// announce listen addresses change
@@ -59,7 +68,7 @@ class CircuitRelayTransportListener extends TypedEventEmitter<ListenerEvents> im
5968

6069
_onAddRelayPeer = (evt: CustomEvent<RelayReservation>): void => {
6170
const {
62-
relay, details
71+
details
6372
} = evt.detail
6473

6574
if (details.type === 'configured') {
@@ -70,16 +79,7 @@ class CircuitRelayTransportListener extends TypedEventEmitter<ListenerEvents> im
7079
return
7180
}
7281

73-
this.log('relay peer added %p', relay)
74-
75-
this.relay = relay
76-
77-
// add all addresses from the relay reservation
78-
this.listeningAddrs = details.reservation.addrs
79-
.map(buf => multiaddr(buf).encapsulate('/p2p-circuit'))
80-
81-
// announce listen addresses change
82-
this.safeDispatchEvent('listening')
82+
this.addedRelay(evt.detail)
8383
}
8484

8585
async listen (addr: Multiaddr): Promise<void> {
@@ -102,18 +102,7 @@ class CircuitRelayTransportListener extends TypedEventEmitter<ListenerEvents> im
102102
if (!this.reservationStore.hasReservation(relayConn.remotePeer)) {
103103
this.log('making reservation on peer %p', relayConn.remotePeer)
104104
const reservation = await this.reservationStore.addRelay(relayConn.remotePeer, 'configured')
105-
this.log('made reservation on peer %p', relayConn.remotePeer)
106-
107-
this.relay = reservation.relay
108-
109-
// add all addresses from the relay reservation
110-
this.listeningAddrs = reservation.details.reservation.addrs
111-
.map(buf => multiaddr(buf).encapsulate('/p2p-circuit'))
112-
113-
// if that succeeded announce listen addresses change
114-
queueMicrotask(() => {
115-
this.safeDispatchEvent('listening')
116-
})
105+
this.addedRelay(reservation)
117106
}
118107
} else {
119108
throw new ListenError(`Could not listen on p2p-circuit address "${addr}"`)
@@ -136,6 +125,28 @@ class CircuitRelayTransportListener extends TypedEventEmitter<ListenerEvents> im
136125
this.safeDispatchEvent('close')
137126
})
138127
}
128+
129+
private addedRelay (reservation: RelayReservation): void {
130+
this.log('relay peer added %p', reservation.relay)
131+
132+
this.relay = reservation.relay
133+
134+
// add all addresses from the relay reservation
135+
this.listeningAddrs = reservation.details.reservation.addrs
136+
.map(buf => multiaddr(buf).encapsulate('/p2p-circuit'))
137+
138+
this.listeningAddrs.forEach(ma => {
139+
// mark as externally dialable
140+
this.addressManager.confirmObservedAddr(ma, {
141+
type: 'transport'
142+
})
143+
})
144+
145+
// if that succeeded announce listen addresses change
146+
queueMicrotask(() => {
147+
this.safeDispatchEvent('listening')
148+
})
149+
}
139150
}
140151

141152
export function createListener (options: CircuitRelayTransportListenerComponents): Listener {

packages/transport-circuit-relay-v2/src/transport/transport.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,10 @@ export class CircuitRelayTransport implements Transport<CircuitRelayDialEvents>
246246
*/
247247
createListener (options: CreateListenerOptions): Listener {
248248
return createListener({
249+
peerId: this.peerId,
249250
connectionManager: this.connectionManager,
250-
relayStore: this.reservationStore,
251+
addressManager: this.addressManager,
252+
reservationStore: this.reservationStore,
251253
logger: this.logger
252254
})
253255
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { generateKeyPair } from '@libp2p/crypto/keys'
2+
import { defaultLogger } from '@libp2p/logger'
3+
import { peerIdFromPrivateKey } from '@libp2p/peer-id'
4+
import { multiaddr } from '@multiformats/multiaddr'
5+
import { expect } from 'aegir/chai'
6+
import { stubInterface } from 'sinon-ts'
7+
import { createListener } from '../src/transport/listener.js'
8+
import { type ReservationStore } from '../src/transport/reservation-store.js'
9+
import type { ComponentLogger, Connection, Listener, PeerId } from '@libp2p/interface'
10+
import type { AddressManager, ConnectionManager } from '@libp2p/interface-internal'
11+
import type { StubbedInstance } from 'sinon-ts'
12+
13+
export interface CircuitRelayTransportListenerComponents {
14+
peerId: PeerId
15+
connectionManager: StubbedInstance<ConnectionManager>
16+
addressManager: StubbedInstance<AddressManager>
17+
reservationStore: StubbedInstance<ReservationStore>
18+
logger: ComponentLogger
19+
}
20+
21+
describe('listener', () => {
22+
let listener: Listener
23+
let components: CircuitRelayTransportListenerComponents
24+
25+
beforeEach(async () => {
26+
components = {
27+
peerId: peerIdFromPrivateKey(await generateKeyPair('Ed25519')),
28+
connectionManager: stubInterface(),
29+
addressManager: stubInterface(),
30+
reservationStore: stubInterface(),
31+
logger: defaultLogger()
32+
}
33+
34+
listener = createListener(components)
35+
})
36+
37+
it('should auto-confirm discovered relay addresses', async () => {
38+
await listener.listen(multiaddr('/p2p-circuit'))
39+
40+
expect(components.reservationStore.reserveRelay).to.have.property('called', true, 'did not begin relay search')
41+
42+
const relayPeer = peerIdFromPrivateKey(await generateKeyPair('Ed25519'))
43+
const relayAddr = multiaddr(`/ip4/123.123.123.123/tcp/1234/p2p/${relayPeer}`)
44+
45+
const createdReservationListener = components.reservationStore.addEventListener.getCall(1).args[1]
46+
47+
if (typeof createdReservationListener === 'function') {
48+
createdReservationListener(
49+
new CustomEvent('relay:created-reservation', {
50+
detail: {
51+
relay: relayPeer,
52+
details: {
53+
type: 'discovered',
54+
reservation: {
55+
addrs: [
56+
relayAddr
57+
]
58+
}
59+
}
60+
}
61+
})
62+
)
63+
}
64+
65+
expect(components.addressManager.confirmObservedAddr.calledWith(
66+
relayAddr.encapsulate('/p2p-circuit')
67+
)).to.be.true()
68+
})
69+
70+
it('should auto-confirm configured relay addresses', async () => {
71+
const relayPeer = peerIdFromPrivateKey(await generateKeyPair('Ed25519'))
72+
const relayAddr = multiaddr(`/ip4/123.123.123.123/tcp/1234/p2p/${relayPeer}/p2p-circuit`)
73+
const conn = stubInterface<Connection>({
74+
id: 'connection-id-1234',
75+
remotePeer: relayPeer
76+
})
77+
78+
components.connectionManager.openConnection.withArgs(relayAddr.decapsulate('/p2p-circuit')).resolves(conn)
79+
80+
components.reservationStore.addRelay.withArgs(relayPeer).resolves({
81+
relay: relayPeer,
82+
details: {
83+
type: 'configured',
84+
reservation: {
85+
addrs: [
86+
relayAddr.bytes
87+
],
88+
expire: 100n
89+
},
90+
timeout: 0 as any,
91+
connection: conn.id
92+
}
93+
})
94+
95+
await listener.listen(relayAddr)
96+
97+
expect(components.addressManager.confirmObservedAddr.calledWith(
98+
relayAddr.encapsulate('/p2p-circuit')
99+
)).to.be.true()
100+
})
101+
})

0 commit comments

Comments
 (0)