Skip to content

Commit

Permalink
fix: allow dialing multiaddrs without peer ids (#1548)
Browse files Browse the repository at this point in the history
Most multiaddrs have a peer id, e.g. `/ip4/123.123.123.123/tcp/234/p2p/Qmfoo`
but some cannot, e.g. `/unix/tmp/app.sock` - we should still be able
to dial these addresses with the caveat that the dialer shoul have some
other way of obtaining the peer id to ensure they aren't being man-in-the-middled.
  • Loading branch information
achingbrain authored Jan 16, 2023
1 parent 141e072 commit 398e231
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 125 deletions.
51 changes: 29 additions & 22 deletions src/connection-manager/dialer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type { Connection, ConnectionGater } from '@libp2p/interface-connection'
import type { AbortOptions } from '@libp2p/interfaces'
import type { Startable } from '@libp2p/interfaces/startable'
import { isPeerId, PeerId } from '@libp2p/interface-peer-id'
import { getPeer } from '../../get-peer.js'
import { getPeerAddress } from '../../get-peer.js'
import type { AddressSorter, PeerStore } from '@libp2p/interface-peer-store'
import type { Metrics } from '@libp2p/interface-metrics'
import type { Dialer } from '@libp2p/interface-connection-manager'
Expand Down Expand Up @@ -151,24 +151,24 @@ export class DefaultDialer implements Startable, Dialer {
* will be used.
*/
async dial (peerIdOrMultiaddr: PeerId | Multiaddr, options: AbortOptions = {}): Promise<Connection> {
const { id, multiaddrs } = getPeer(peerIdOrMultiaddr)
const { peerId, multiaddr } = getPeerAddress(peerIdOrMultiaddr)

if (this.components.peerId.equals(id)) {
throw errCode(new Error('Tried to dial self'), codes.ERR_DIALED_SELF)
}

log('check multiaddrs %p', id)
if (peerId != null) {
if (this.components.peerId.equals(peerId)) {
throw errCode(new Error('Tried to dial self'), codes.ERR_DIALED_SELF)
}

if (multiaddrs != null && multiaddrs.length > 0) {
log('storing multiaddrs %p', id, multiaddrs)
await this.components.peerStore.addressBook.add(id, multiaddrs)
}
if (multiaddr != null) {
log('storing multiaddrs %p', peerId, multiaddr)
await this.components.peerStore.addressBook.add(peerId, [multiaddr])
}

if (await this.components.connectionGater.denyDialPeer(id)) {
throw errCode(new Error('The dial request is blocked by gater.allowDialPeer'), codes.ERR_PEER_DIAL_INTERCEPTED)
if (await this.components.connectionGater.denyDialPeer(peerId)) {
throw errCode(new Error('The dial request is blocked by gater.allowDialPeer'), codes.ERR_PEER_DIAL_INTERCEPTED)
}
}

log('creating dial target for %p', id)
log('creating dial target for %p', peerId)

// resolving multiaddrs can involve dns lookups so allow them to be aborted
const controller = new AbortController()
Expand All @@ -184,7 +184,7 @@ export class DefaultDialer implements Startable, Dialer {
let dialTarget: DialTarget

try {
dialTarget = await this._createDialTarget(peerIdOrMultiaddr, {
dialTarget = await this._createDialTarget({ peerId, multiaddr }, {
...options,
signal
})
Expand All @@ -198,7 +198,7 @@ export class DefaultDialer implements Startable, Dialer {
}

// try to join an in-flight dial for this peer if one is available
const pendingDial = this.pendingDials.get(dialTarget.id.toString()) ?? this._createPendingDial(dialTarget, options)
const pendingDial = this.pendingDials.get(dialTarget.id) ?? this._createPendingDial(dialTarget, options)

try {
const connection = await pendingDial.promise
Expand All @@ -225,26 +225,33 @@ export class DefaultDialer implements Startable, Dialer {
*
* Multiaddrs not supported by the available transports will be filtered out.
*/
async _createDialTarget (peerIdOrMultiaddr: PeerId | Multiaddr, options: AbortOptions): Promise<DialTarget> {
const _resolve = this._resolve.bind(this)
async _createDialTarget (peerIdOrMultiaddr: { peerId?: PeerId, multiaddr?: Multiaddr }, options: AbortOptions): Promise<DialTarget> {
let addrs: Multiaddr[] = []

let addrs = isMultiaddr(peerIdOrMultiaddr) ? [peerIdOrMultiaddr] : await this._loadAddresses(peerIdOrMultiaddr)
if (isMultiaddr(peerIdOrMultiaddr.multiaddr)) {
addrs.push(peerIdOrMultiaddr.multiaddr)
}

// only load addresses if a peer id was passed, otherwise only dial the passed multiaddr
if (!isMultiaddr(peerIdOrMultiaddr.multiaddr) && isPeerId(peerIdOrMultiaddr.peerId)) {
addrs.push(...await this._loadAddresses(peerIdOrMultiaddr.peerId))
}

addrs = (await Promise.all(
addrs.map(async (ma) => await _resolve(ma, options))
addrs.map(async (ma) => await this._resolve(ma, options))
))
.flat()
// Multiaddrs not supported by the available transports will be filtered out.
.filter(ma => Boolean(this.components.transportManager.transportForMultiaddr(ma)))

// deduplicate addresses
addrs = [...new Set(addrs)]
addrs = [...new Set(addrs.map(ma => ma.toString()))].map(ma => multiaddr(ma))

if (addrs.length > this.maxAddrsToDial) {
throw errCode(new Error('dial with more addresses than allowed'), codes.ERR_TOO_MANY_ADDRESSES)
}

const peerId = isPeerId(peerIdOrMultiaddr) ? peerIdOrMultiaddr : undefined
const peerId = isPeerId(peerIdOrMultiaddr.peerId) ? peerIdOrMultiaddr.peerId : undefined

if (peerId != null) {
const peerIdMultiaddr = `/p2p/${peerId.toString()}`
Expand Down
30 changes: 14 additions & 16 deletions src/connection-manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import type { Connection, MultiaddrConnection } from '@libp2p/interface-connecti
import type { ConnectionManager, ConnectionManagerEvents, Dialer } from '@libp2p/interface-connection-manager'
import * as STATUS from '@libp2p/interface-connection/status'
import type { AddressSorter, PeerStore } from '@libp2p/interface-peer-store'
import { isMultiaddr, multiaddr, Multiaddr, Resolver } from '@multiformats/multiaddr'
import { multiaddr, Multiaddr, Resolver } from '@multiformats/multiaddr'
import { PeerMap } from '@libp2p/peer-collections'
import { TimeoutController } from 'timeout-abort-controller'
import { KEEP_ALIVE } from '@libp2p/interface-peer-store/tags'
import { RateLimiterMemory } from 'rate-limiter-flexible'
import type { Metrics } from '@libp2p/interface-metrics'
import type { Upgrader } from '@libp2p/interface-transport'
import { getPeer } from '../get-peer.js'
import { getPeerAddress } from '../get-peer.js'

const log = logger('libp2p:connection-manager')

Expand Down Expand Up @@ -471,24 +471,22 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
}

async openConnection (peerIdOrMultiaddr: PeerId | Multiaddr, options: AbortOptions = {}): Promise<Connection> {
let peerId: PeerId
const { peerId, multiaddr } = getPeerAddress(peerIdOrMultiaddr)

if (isPeerId(peerIdOrMultiaddr)) {
peerId = peerIdOrMultiaddr
} else if (isMultiaddr(peerIdOrMultiaddr)) {
const info = getPeer(peerIdOrMultiaddr)
peerId = info.id
} else {
if (peerId == null && multiaddr == null) {
throw errCode(new TypeError('Can only open connections to PeerIds or Multiaddrs'), codes.ERR_INVALID_PARAMETERS)
}

log('dial to %p', peerId)
const existingConnections = this.getConnections(peerId)
if (peerId != null) {
log('dial to', peerId)

const existingConnections = this.getConnections(peerId)

if (existingConnections.length > 0) {
log('had an existing connection to %p', peerId)
if (existingConnections.length > 0) {
log('had an existing connection to %p', peerId)

return existingConnections[0]
return existingConnections[0]
}
}

let timeoutController: TimeoutController | undefined
Expand All @@ -505,11 +503,11 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven

try {
const connection = await this.components.dialer.dial(peerIdOrMultiaddr, options)
let peerConnections = this.connections.get(peerId.toString())
let peerConnections = this.connections.get(connection.remotePeer.toString())

if (peerConnections == null) {
peerConnections = []
this.connections.set(peerId.toString(), peerConnections)
this.connections.set(connection.remotePeer.toString(), peerConnections)
}

// we get notified of connections via the Upgrader emitting "connection"
Expand Down
48 changes: 13 additions & 35 deletions src/get-peer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,50 +5,28 @@ import errCode from 'err-code'
import { codes } from './errors.js'
import { isPeerId } from '@libp2p/interface-peer-id'
import type { PeerId } from '@libp2p/interface-peer-id'
import type { PeerInfo } from '@libp2p/interface-peer-info'

function peerIdFromMultiaddr (ma: Multiaddr) {
const idStr = ma.getPeerId()

if (idStr == null) {
throw errCode(
new Error(`${ma.toString()} does not have a valid peer type`),
codes.ERR_INVALID_MULTIADDR
)
}

try {
return peerIdFromString(idStr)
} catch (err: any) {
throw errCode(
new Error(`${ma.toString()} is not a valid peer type`),
codes.ERR_INVALID_MULTIADDR
)
}
}

/**
* Converts the given `peer` to a `PeerInfo` object.
* Extracts a PeerId and/or multiaddr from the passed PeerId or Multiaddr
*/
export function getPeer (peer: PeerId | Multiaddr): PeerInfo {
export function getPeerAddress (peer: PeerId | Multiaddr): { peerId?: PeerId, multiaddr?: Multiaddr } {
if (isPeerId(peer)) {
return {
id: peer,
multiaddrs: [],
protocols: []
peerId: peer
}
}

let addr

if (isMultiaddr(peer)) {
addr = peer
peer = peerIdFromMultiaddr(peer)
}
const peerId = peer.getPeerId()

return {
id: peer,
multiaddrs: addr != null ? [addr] : [],
protocols: []
return {
multiaddr: peer,
peerId: peerId == null ? undefined : peerIdFromString(peerId)
}
}

throw errCode(
new Error(`${peer} is not a PeerId or a Multiaddr`), // eslint-disable-line @typescript-eslint/restrict-template-expressions
codes.ERR_INVALID_MULTIADDR
)
}
36 changes: 17 additions & 19 deletions src/libp2p.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ import { logger } from '@libp2p/logger'
import type { AbortOptions } from '@libp2p/interfaces'
import { EventEmitter, CustomEvent } from '@libp2p/interfaces/events'
import { Startable, isStartable } from '@libp2p/interfaces/startable'
import type { Multiaddr } from '@multiformats/multiaddr'
import { isMultiaddr, Multiaddr } from '@multiformats/multiaddr'
import { MemoryDatastore } from 'datastore-core/memory'
import { DefaultPeerRouting } from './peer-routing.js'
import { CompoundContentRouting } from './content-routing/index.js'
import { getPeer } from './get-peer.js'
import { codes } from './errors.js'
import { DefaultAddressManager } from './address-manager/index.js'
import { DefaultConnectionManager } from './connection-manager/index.js'
Expand Down Expand Up @@ -49,6 +48,7 @@ import { DummyDHT } from './dht/dummy-dht.js'
import { DummyPubSub } from './pubsub/dummy-pubsub.js'
import { PeerSet } from '@libp2p/peer-collections'
import { DefaultDialer } from './connection-manager/dialer/index.js'
import { peerIdFromString } from '@libp2p/peer-id'

const log = logger('libp2p')

Expand Down Expand Up @@ -354,11 +354,7 @@ export class Libp2pNode extends EventEmitter<Libp2pEvents> implements Libp2p {
}

async dial (peer: PeerId | Multiaddr, options: AbortOptions = {}): Promise<Connection> {
const { id, multiaddrs } = getPeer(peer)

await this.components.peerStore.addressBook.add(id, multiaddrs)

return await this.components.connectionManager.openConnection(id, options)
return await this.components.connectionManager.openConnection(peer, options)
}

async dialProtocol (peer: PeerId | Multiaddr, protocols: string | string[], options: AbortOptions = {}) {
Expand Down Expand Up @@ -386,9 +382,11 @@ export class Libp2pNode extends EventEmitter<Libp2pEvents> implements Libp2p {
}

async hangUp (peer: PeerId | Multiaddr): Promise<void> {
const { id } = getPeer(peer)
if (isMultiaddr(peer)) {
peer = peerIdFromString(peer.getPeerId() ?? '')
}

await this.components.connectionManager.closeConnections(id)
await this.components.connectionManager.closeConnections(peer)
}

/**
Expand Down Expand Up @@ -431,23 +429,23 @@ export class Libp2pNode extends EventEmitter<Libp2pEvents> implements Libp2p {
}

async fetch (peer: PeerId | Multiaddr, key: string, options: AbortOptions = {}): Promise<Uint8Array | null> {
const { id, multiaddrs } = getPeer(peer)

if (multiaddrs != null) {
await this.components.peerStore.addressBook.add(id, multiaddrs)
if (isMultiaddr(peer)) {
const peerId = peerIdFromString(peer.getPeerId() ?? '')
await this.components.peerStore.addressBook.add(peerId, [peer])
peer = peerId
}

return await this.fetchService.fetch(id, key, options)
return await this.fetchService.fetch(peer, key, options)
}

async ping (peer: PeerId | Multiaddr, options: AbortOptions = {}): Promise<number> {
const { id, multiaddrs } = getPeer(peer)

if (multiaddrs.length > 0) {
await this.components.peerStore.addressBook.add(id, multiaddrs)
if (isMultiaddr(peer)) {
const peerId = peerIdFromString(peer.getPeerId() ?? '')
await this.components.peerStore.addressBook.add(peerId, [peer])
peer = peerId
}

return await this.pingService.ping(id, options)
return await this.pingService.ping(peer, options)
}

async handle (protocols: string | string[], handler: StreamHandler, options?: StreamHandlerOptions): Promise<void> {
Expand Down
20 changes: 12 additions & 8 deletions src/upgrader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,18 +250,18 @@ export class DefaultUpgrader extends EventEmitter<UpgraderEvents> implements Upg
*/
async upgradeOutbound (maConn: MultiaddrConnection, opts?: UpgraderOptions): Promise<Connection> {
const idStr = maConn.remoteAddr.getPeerId()
if (idStr == null) {
throw errCode(new Error('outbound connection must have a peer id'), codes.ERR_INVALID_MULTIADDR)
}
let remotePeerId: PeerId | undefined

const remotePeerId = peerIdFromString(idStr)
if (idStr != null) {
remotePeerId = peerIdFromString(idStr)

if (await this.components.connectionGater.denyOutboundConnection(remotePeerId, maConn)) {
throw errCode(new Error('The multiaddr connection is blocked by connectionGater.denyOutboundConnection'), codes.ERR_CONNECTION_INTERCEPTED)
if (await this.components.connectionGater.denyOutboundConnection(remotePeerId, maConn)) {
throw errCode(new Error('The multiaddr connection is blocked by connectionGater.denyOutboundConnection'), codes.ERR_CONNECTION_INTERCEPTED)
}
}

let encryptedConn
let remotePeer
let remotePeer: PeerId
let upgradedConn
let cryptoProtocol
let muxerFactory
Expand Down Expand Up @@ -300,6 +300,10 @@ export class DefaultUpgrader extends EventEmitter<UpgraderEvents> implements Upg
throw errCode(new Error('The multiaddr connection is blocked by gater.acceptEncryptedConnection'), codes.ERR_CONNECTION_INTERCEPTED)
}
} else {
if (remotePeerId == null) {
throw errCode(new Error('Encryption was skipped but no peer id was passed'), codes.ERR_INVALID_PEER)
}

cryptoProtocol = 'native'
remotePeer = remotePeerId
}
Expand Down Expand Up @@ -593,7 +597,7 @@ export class DefaultUpgrader extends EventEmitter<UpgraderEvents> implements Upg
* Attempts to encrypt the given `connection` with the provided connection encrypters.
* The first `ConnectionEncrypter` module to succeed will be used
*/
async _encryptOutbound (connection: MultiaddrConnection, remotePeerId: PeerId): Promise<CryptoResult> {
async _encryptOutbound (connection: MultiaddrConnection, remotePeerId?: PeerId): Promise<CryptoResult> {
const protocols = Array.from(this.connectionEncryption.keys())
log('selecting outbound crypto protocol', protocols)

Expand Down
Loading

0 comments on commit 398e231

Please sign in to comment.