Skip to content

Commit

Permalink
fix!: do not dial private addresses in browsers (#1735)
Browse files Browse the repository at this point in the history
Dialling is very expensive in browsers. If we're dialling addresses
that include private networks, chances are they're not going to
succeed so filter them out for browsers only.

This can be re-enabled by configuring a permissive connection gater,
see the upgrade guide for more information.

BREAKING CHANGE: browsers will no longer try to dial private addresses by default
  • Loading branch information
achingbrain authored May 4, 2023
1 parent 94df577 commit e3deaa4
Show file tree
Hide file tree
Showing 23 changed files with 168 additions and 61 deletions.
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
2 changes: 1 addition & 1 deletion examples/delegated-routing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"libp2p": "file:../../",
"@libp2p/delegated-content-routing": "^4.0.0",
"@libp2p/delegated-peer-routing": "^4.0.0",
"@libp2p/kad-dht": "^9.1.0",
"@libp2p/kad-dht": "^9.1.4",
"@libp2p/mplex": "^8.0.1",
"@libp2p/webrtc-star": "^7.0.0",
"@libp2p/websockets": "^6.0.1",
Expand Down
2 changes: 1 addition & 1 deletion examples/libp2p-in-the-browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"license": "ISC",
"dependencies": {
"@chainsafe/libp2p-noise": "^11.0.0",
"@libp2p/bootstrap": "^7.0.0",
"@libp2p/bootstrap": "^8.0.0",
"@libp2p/mplex": "^8.0.1",
"@libp2p/webrtc-star": "^7.0.0",
"@libp2p/websockets": "^6.0.1",
Expand Down
6 changes: 5 additions & 1 deletion examples/peer-and-content-routing/1.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ const createNode = async () => {
streamMuxers: [mplex()],
connectionEncryption: [noise()],
services: {
dht: kadDHT(),
dht: kadDHT({
// this is necessary because this node is not connected to the public network
// it can be removed if, for example bootstrappers are configured
allowQueryWithZeroPeers: true
}),
identify: identifyService()
}
})
Expand Down
6 changes: 5 additions & 1 deletion examples/peer-and-content-routing/2.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ const createNode = async () => {
streamMuxers: [mplex()],
connectionEncryption: [noise()],
services: {
dht: kadDHT(),
dht: kadDHT({
// this is necessary because this node is not connected to the public network
// it can be removed if, for example bootstrappers are configured
allowQueryWithZeroPeers: true
}),
identify: identifyService()
}
})
Expand Down
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
2 changes: 1 addition & 1 deletion examples/webrtc-direct/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"dependencies": {
"@libp2p/webrtc-direct": "^6.0.0",
"@chainsafe/libp2p-noise": "^11.0.0",
"@libp2p/bootstrap": "^7.0.0",
"@libp2p/bootstrap": "^8.0.0",
"@libp2p/mplex": "^8.0.1",
"libp2p": "file:../../",
"wrtc": "^0.4.7"
Expand Down
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,10 @@
"@libp2p/interface-connection-gater": "^3.0.0",
"@libp2p/interface-connection-manager": "^3.0.0",
"@libp2p/interface-content-routing": "^2.1.0",
"@libp2p/interface-dht": "^2.0.1",
"@libp2p/interface-keychain": "^2.0.4",
"@libp2p/interface-libp2p": "^3.0.0",
"@libp2p/interface-metrics": "^4.0.0",
"@libp2p/interface-peer-discovery": "^1.1.0",
"@libp2p/interface-peer-discovery": "^2.0.0",
"@libp2p/interface-peer-id": "^2.0.1",
"@libp2p/interface-peer-info": "^1.0.3",
"@libp2p/interface-peer-routing": "^1.1.0",
Expand Down Expand Up @@ -190,17 +189,17 @@
"@chainsafe/libp2p-gossipsub": "^7.0.0",
"@chainsafe/libp2p-noise": "^11.0.0",
"@chainsafe/libp2p-yamux": "^4.0.0",
"@libp2p/bootstrap": "^7.0.0",
"@libp2p/bootstrap": "^8.0.0",
"@libp2p/daemon-client": "^6.0.2",
"@libp2p/daemon-server": "^5.0.2",
"@libp2p/floodsub": "^7.0.1",
"@libp2p/interface-compliance-tests": "^3.0.6",
"@libp2p/interface-connection-compliance-tests": "^2.0.8",
"@libp2p/interface-connection-encrypter-compliance-tests": "^5.0.0",
"@libp2p/interface-mocks": "^11.0.0",
"@libp2p/interface-mocks": "^12.0.0",
"@libp2p/interop": "^8.0.0",
"@libp2p/kad-dht": "^9.1.0",
"@libp2p/mdns": "^7.0.0",
"@libp2p/kad-dht": "^9.1.4",
"@libp2p/mdns": "^8.0.0",
"@libp2p/mplex": "^8.0.1",
"@libp2p/pubsub": "^7.0.1",
"@libp2p/tcp": "^7.0.1",
Expand All @@ -223,6 +222,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
6 changes: 3 additions & 3 deletions test/content-routing/content-routing.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type { PeerInfo } from '@libp2p/interface-peer-info'
import type { ContentRouting } from '@libp2p/interface-content-routing'
import { StubbedInstance, stubInterface } from 'sinon-ts'
import { peerIdFromString } from '@libp2p/peer-id'
import type { DHT } from '@libp2p/interface-dht'
import type { KadDHT } from '@libp2p/kad-dht'

describe('content-routing', () => {
describe('no routers', () => {
Expand Down Expand Up @@ -50,7 +50,7 @@ describe('content-routing', () => {

describe('via dht router', () => {
const number = 5
let nodes: Array<Libp2p<{ dht: DHT }>>
let nodes: Array<Libp2p<{ dht: KadDHT }>>

before(async () => {
nodes = await Promise.all([
Expand Down Expand Up @@ -222,7 +222,7 @@ describe('content-routing', () => {
})

describe('via dht and delegate routers', () => {
let node: Libp2p<{ dht: DHT }>
let node: Libp2p<{ dht: KadDHT }>
let delegate: StubbedInstance<ContentRouting>

beforeEach(async () => {
Expand Down
14 changes: 9 additions & 5 deletions test/content-routing/dht/operation.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { subsystemMulticodecs } from './utils.js'
import { createPeerId } from '../../utils/creators/peer.js'
import type { PeerId } from '@libp2p/interface-peer-id'
import type { Libp2p } from '@libp2p/interface-libp2p'
import type { DualDHT } from '@libp2p/interface-dht'
import type { DualKadDHT } from '@libp2p/kad-dht'
import { createLibp2p } from '../../../src/index.js'
import { kadDHT } from '@libp2p/kad-dht'
import { tcp } from '@libp2p/tcp'
Expand All @@ -34,8 +34,8 @@ async function getRemoteAddr (remotePeerId: PeerId, libp2p: Libp2p): Promise<Mul
describe('DHT subsystem operates correctly', () => {
let peerId: PeerId
let remotePeerId: PeerId
let libp2p: Libp2p<{ dht: DualDHT }>
let remoteLibp2p: Libp2p<{ dht: DualDHT }>
let libp2p: Libp2p<{ dht: DualKadDHT }>
let remoteLibp2p: Libp2p<{ dht: DualKadDHT }>
let remAddr: Multiaddr

beforeEach(async () => {
Expand All @@ -62,7 +62,9 @@ describe('DHT subsystem operates correctly', () => {
mplex()
],
services: {
dht: kadDHT()
dht: kadDHT({
allowQueryWithZeroPeers: true
})
}
})

Expand All @@ -81,7 +83,9 @@ describe('DHT subsystem operates correctly', () => {
mplex()
],
services: {
dht: kadDHT()
dht: kadDHT({
allowQueryWithZeroPeers: true
})
}
})

Expand Down
Loading

0 comments on commit e3deaa4

Please sign in to comment.