Skip to content

fix!: do not dial private addresses in browsers #1735

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

Merged
merged 5 commits into from
May 4, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions doc/migrations/v0.44-v0.45.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ A migration guide for refactoring your application code from libp2p v0.44.x to v
- [`peer:update`](#peerupdate)
- [`self:peer:update`](#selfpeerupdate)
- [Atomic peer store methods](#atomic-peer-store-methods)
- [Do not dial private addresses by default in browsers](#do-not-dial-private-addresses-by-default-in-browsers)

## Services

Expand Down Expand Up @@ -234,7 +235,43 @@ await node.peerStore.merge(peerId, {
})
```

## Do not dial private addresses by default in browsers

Browsers are incredibly resource-constrained environments in which to run a network-heavy program like libp2p.

This is compounded by the fact that remote peers often include private network addresses in their peer records, so a libp2p node will often waste resources by trying to dial unroutable addresses.

The default [connection gater][] used by libp2p in browsers will filter out any private addresses and not attempt to dial them.

No change has been made to the connection gater used by Node.js.

This can be re-enabled by configuring a more permissive connection gater:

**Before**

```js
import { createLibp2p } from 'libp2p'

const node = createLibp2p({
// ... other options here
})
```

**After**

```js
import { createLibp2p } from 'libp2p'

const node = createLibp2p({
connectionGater: {
denyDialMultiaddr: () => false
}
// ... other options here
})
```

[Connection]: https://libp2p.github.io/js-libp2p-interfaces/interfaces/_libp2p_interface_connection.Connection.html
[PeerId]: https://libp2p.github.io/js-libp2p-interfaces/types/_libp2p_interface_peer_id.PeerId.html
[Identify]: https://github.com/libp2p/specs/blob/master/identify/README.md
[AutoNAT]: https://github.com/libp2p/specs/blob/master/autonat/README.md
[Connection Gater]: https://github.com/libp2p/js-libp2p/blob/master/doc/CONFIGURATION.md#configuring-connection-gater
5 changes: 4 additions & 1 deletion examples/webrtc-direct/dialer.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ document.addEventListener('DOMContentLoaded', async () => {
bootstrap({
list: [`/ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct/p2p/${hardcodedPeerId}`]
})
]
],
connectionGater: {
denyDialMultiaddr: () => false
}
})

const status = document.getElementById('status')
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@
"sinon-ts": "^1.0.0"
},
"browser": {
"./dist/src/config/connection-gater.js": "./dist/src/config/connection-gater.browser.js",
"nat-api": false
}
}
31 changes: 31 additions & 0 deletions src/config/connection-gater.browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { ConnectionGater } from '@libp2p/interface-connection-gater'
import type { Multiaddr } from '@multiformats/multiaddr'
import isPrivate from 'private-ip'

/**
* Returns a connection gater that disallows dialling private addresses by
* default. Browsers are severely limited in their resource usage so don't
* waste time trying to dial undiallable addresses.
*/
export function connectionGater (gater: ConnectionGater = {}): ConnectionGater {
return {
denyDialPeer: async () => false,
denyDialMultiaddr: async (multiaddr: Multiaddr) => {
const tuples = multiaddr.stringTuples()

if (tuples[0][0] === 4 || tuples[0][0] === 41) {
return Boolean(isPrivate(`${tuples[0][1]}`))
}

return false
},
denyInboundConnection: async () => false,
denyOutboundConnection: async () => false,
denyInboundEncryptedConnection: async () => false,
denyOutboundEncryptedConnection: async () => false,
denyInboundUpgradedConnection: async () => false,
denyOutboundUpgradedConnection: async () => false,
filterMultiaddrForPeer: async () => true,
...gater
}
}
19 changes: 19 additions & 0 deletions src/config/connection-gater.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { ConnectionGater } from '@libp2p/interface-connection-gater'

/**
* Returns a default connection gater implementation that allows everything
*/
export function connectionGater (gater: ConnectionGater = {}): ConnectionGater {
return {
denyDialPeer: async () => false,
denyDialMultiaddr: async () => false,
denyInboundConnection: async () => false,
denyOutboundConnection: async () => false,
denyInboundEncryptedConnection: async () => false,
denyOutboundEncryptedConnection: async () => false,
denyInboundUpgradedConnection: async () => false,
denyOutboundUpgradedConnection: async () => false,
filterMultiaddrForPeer: async () => true,
...gater
}
}
14 changes: 2 additions & 12 deletions src/libp2p.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { validateConfig } from './config.js'
import { ContentRouting, contentRouting } from '@libp2p/interface-content-routing'
import { PeerRouting, peerRouting } from '@libp2p/interface-peer-routing'
import { peerDiscovery } from '@libp2p/interface-peer-discovery'
import { connectionGater } from './config/connection-gater.js'

const log = logger('libp2p')

Expand Down Expand Up @@ -82,18 +83,7 @@ export class Libp2pNode<T extends ServiceMap = {}> extends EventEmitter<Libp2pEv
peerId: init.peerId,
events,
datastore: init.datastore ?? new MemoryDatastore(),
connectionGater: {
denyDialPeer: async () => await Promise.resolve(false),
denyDialMultiaddr: async () => await Promise.resolve(false),
denyInboundConnection: async () => await Promise.resolve(false),
denyOutboundConnection: async () => await Promise.resolve(false),
denyInboundEncryptedConnection: async () => await Promise.resolve(false),
denyOutboundEncryptedConnection: async () => await Promise.resolve(false),
denyInboundUpgradedConnection: async () => await Promise.resolve(false),
denyOutboundUpgradedConnection: async () => await Promise.resolve(false),
filterMultiaddrForPeer: async () => await Promise.resolve(true),
...init.connectionGater
}
connectionGater: connectionGater(init.connectionGater)
})

this.peerStore = this.configureComponent('peerStore', new PersistentPeerStore(components, {
Expand Down
4 changes: 3 additions & 1 deletion test/configuration/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { Libp2pInit, Libp2pOptions } from '../../src/index.js'
import type { PeerId } from '@libp2p/interface-peer-id'
import * as cborg from 'cborg'
import { circuitRelayTransport } from '../../src/circuit-relay/index.js'
import { mockConnectionGater } from '@libp2p/interface-mocks'

const relayAddr = MULTIADDRS_WEBSOCKETS[0]

Expand Down Expand Up @@ -88,5 +89,6 @@ export const pubsubSubsystemOptions: Libp2pOptions<{ pubsub: PubSub }> = mergeOp
],
services: {
pubsub: (components: PubSubComponents) => new MockPubSub(components)
}
},
connectionGater: mockConnectionGater()
})
12 changes: 8 additions & 4 deletions test/connection-manager/direct.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,8 @@ describe('libp2p.dialer (direct, WebSockets)', () => {
],
services: {
identify: identifyService()
}
},
connectionGater: mockConnectionGater()
})

if (libp2p.services.identify == null) {
Expand Down Expand Up @@ -413,7 +414,8 @@ describe('libp2p.dialer (direct, WebSockets)', () => {
],
connectionEncryption: [
plaintext()
]
],
connectionGater: mockConnectionGater()
})

await libp2p.start()
Expand Down Expand Up @@ -441,7 +443,8 @@ describe('libp2p.dialer (direct, WebSockets)', () => {
],
connectionEncryption: [
plaintext()
]
],
connectionGater: mockConnectionGater()
})

await libp2p.hangUp(MULTIADDRS_WEBSOCKETS[0])
Expand All @@ -460,7 +463,8 @@ describe('libp2p.dialer (direct, WebSockets)', () => {
],
connectionEncryption: [
plaintext()
]
],
connectionGater: mockConnectionGater()
})

await libp2p.start()
Expand Down
8 changes: 5 additions & 3 deletions test/connection-manager/resolver.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { codes as ErrorCodes } from '../../src/errors.js'
import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js'
import type { PeerId } from '@libp2p/interface-peer-id'
import pDefer from 'p-defer'
import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-mocks'
import { mockConnection, mockConnectionGater, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-mocks'
import { peerIdFromString } from '@libp2p/peer-id'
import { createFromJSON } from '@libp2p/peer-id-factory'
import { RELAY_V2_HOP_CODEC } from '../../src/circuit-relay/constants.js'
Expand Down Expand Up @@ -66,7 +66,8 @@ describe('dialing (resolvable addresses)', () => {
},
connectionEncryption: [
plaintext()
]
],
connectionGater: mockConnectionGater()
}),
createLibp2pNode({
addresses: {
Expand All @@ -91,7 +92,8 @@ describe('dialing (resolvable addresses)', () => {
],
services: {
relay: circuitRelayServer()
}
},
connectionGater: mockConnectionGater()
})
])

Expand Down
15 changes: 10 additions & 5 deletions test/upgrading/upgrader.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,8 @@ describe('libp2p.upgrader', () => {
],
connectionEncryption: [
plaintext()
]
],
connectionGater: mockConnectionGater()
})
await libp2p.start()

Expand All @@ -694,7 +695,8 @@ describe('libp2p.upgrader', () => {
],
connectionEncryption: [
plaintext()
]
],
connectionGater: mockConnectionGater()
})
await remoteLibp2p.start()

Expand Down Expand Up @@ -745,7 +747,8 @@ describe('libp2p.upgrader', () => {
test: (components: any) => {
localDeferred.resolve(components)
}
}
},
connectionGater: mockConnectionGater()
})

remoteLibp2p = await createLibp2p({
Expand All @@ -763,7 +766,8 @@ describe('libp2p.upgrader', () => {
test: (components: any) => {
remoteDeferred.resolve(components)
}
}
},
connectionGater: mockConnectionGater()
})

const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer })
Expand Down Expand Up @@ -820,7 +824,8 @@ describe('libp2p.upgrader', () => {
test: (components: any) => {
localDeferred.resolve(components)
}
}
},
connectionGater: mockConnectionGater()
})

remoteLibp2p = await createLibp2p({
Expand Down
4 changes: 3 additions & 1 deletion test/utils/base-options.browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { Libp2pOptions } from '../../src/index.js'
import mergeOptions from 'merge-options'
import { circuitRelayTransport } from '../../src/circuit-relay/index.js'
import type { ServiceMap } from '@libp2p/interface-libp2p'
import { mockConnectionGater } from '@libp2p/interface-mocks'

export function createBaseOptions <T extends ServiceMap = {}> (overrides?: Libp2pOptions<T>): Libp2pOptions<T> {
const options: Libp2pOptions = {
Expand All @@ -21,7 +22,8 @@ export function createBaseOptions <T extends ServiceMap = {}> (overrides?: Libp2
],
connectionEncryption: [
plaintext()
]
],
connectionGater: mockConnectionGater()
}

return mergeOptions(options, overrides)
Expand Down