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 all 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
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