diff --git a/packages/transport-websockets/src/listener.ts b/packages/transport-websockets/src/listener.ts index f19cc2456c..aba19f51ad 100644 --- a/packages/transport-websockets/src/listener.ts +++ b/packages/transport-websockets/src/listener.ts @@ -4,7 +4,7 @@ import net from 'node:net' import os from 'node:os' import { TypedEventEmitter, setMaxListeners } from '@libp2p/interface' import { ipPortToMultiaddr as toMultiaddr } from '@libp2p/utils/ip-port-to-multiaddr' -import { multiaddr, protocols } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import { WebSockets, WebSocketsSecure } from '@multiformats/multiaddr-matcher' import duplex from 'it-ws/duplex' import { pEvent } from 'p-event' @@ -138,7 +138,6 @@ export class WebSocketListener extends TypedEventEmitter impleme } // store the socket so we can close it when the listener closes - // TODO: is this necessary if we can `this.https.closeAllConnections`? this.sockets.add(socket) socket.on('close', () => { this.sockets.delete(socket) @@ -233,7 +232,7 @@ export class WebSocketListener extends TypedEventEmitter impleme const { host, port } = ma.toOptions() this.addr = `${host}:${port}` - this.server.listen(port) + this.server.listen(port, host) await new Promise((resolve, reject) => { const onListening = (): void => { @@ -313,7 +312,6 @@ export class WebSocketListener extends TypedEventEmitter impleme } getAddrs (): Multiaddr[] { - const multiaddrs: Multiaddr[] = [] const address = this.server.address() if (address == null) { @@ -328,16 +326,10 @@ export class WebSocketListener extends TypedEventEmitter impleme throw new Error('Listener is not ready yet') } - const protos = this.listeningMultiaddr.protos() - - // because TCP will only return the IPv6 version, we need to capture from - // the passed multiaddr - if (protos.some(proto => proto.code === protocols('ip4').code)) { - const wsProto = protos.some(proto => proto.code === protocols('ws').code) ? '/ws' : '/wss' - let m = this.listeningMultiaddr.decapsulate('tcp') - m = m.encapsulate(`/tcp/${address.port}${wsProto}`) - const options = m.toOptions() + const options = this.listeningMultiaddr.toOptions() + const multiaddrs: Multiaddr[] = [] + if (options.family === 4) { if (options.host === '0.0.0.0') { Object.values(os.networkInterfaces()).forEach(niInfos => { if (niInfos == null) { @@ -346,24 +338,51 @@ export class WebSocketListener extends TypedEventEmitter impleme niInfos.forEach(ni => { if (ni.family === 'IPv4') { - multiaddrs.push(multiaddr(`/ip${options.family}/${ni.address}/${options.transport}/${options.port}/ws`)) + multiaddrs.push(multiaddr(`/ip${options.family}/${ni.address}/${options.transport}/${address.port}`)) + } + }) + }) + } else { + multiaddrs.push(multiaddr(`/ip${options.family}/${options.host}/${options.transport}/${address.port}`)) + } + } else if (options.family === 6) { + if (options.host === '::') { + Object.values(os.networkInterfaces()).forEach(niInfos => { + if (niInfos == null) { + return + } - if (this.https != null && WebSockets.exactMatch(m)) { - multiaddrs.push(multiaddr(`/ip${options.family}/${ni.address}/${options.transport}/${options.port}/tls/ws`)) - } + niInfos.forEach(ni => { + if (ni.family === 'IPv6') { + multiaddrs.push(multiaddr(`/ip${options.family}/${ni.address}/${options.transport}/${address.port}`)) } }) }) } else { - if (this.https != null && WebSockets.exactMatch(m)) { - multiaddrs.push(m.decapsulate('/ws').encapsulate('/tls/ws')) - } else { - multiaddrs.push(m) - } + multiaddrs.push(multiaddr(`/ip${options.family}/${options.host}/${options.transport}/${address.port}`)) } } - return multiaddrs + const insecureMultiaddrs: Multiaddr[] = [] + + if (this.http != null) { + multiaddrs.forEach(ma => { + insecureMultiaddrs.push(ma.encapsulate('/ws')) + }) + } + + const secureMultiaddrs: Multiaddr[] = [] + + if (this.https != null) { + multiaddrs.forEach(ma => { + secureMultiaddrs.push(ma.encapsulate('/tls/ws')) + }) + } + + return [ + ...insecureMultiaddrs, + ...secureMultiaddrs + ] } private httpRequestHandler (req: http.IncomingMessage, res: http.ServerResponse): void { diff --git a/packages/transport-websockets/test/node.ts b/packages/transport-websockets/test/node.ts index 6b3f274cff..6baae5a67d 100644 --- a/packages/transport-websockets/test/node.ts +++ b/packages/transport-websockets/test/node.ts @@ -76,7 +76,8 @@ describe('listen', () => { it('should error on starting two listeners on same address', async () => { listener = ws.createListener({ upgrader }) const dumbServer = http.createServer() - await new Promise(resolve => dumbServer.listen(ma.toOptions().port, resolve)) + const options = ma.toOptions() + await new Promise(resolve => dumbServer.listen(options.port, options.host, resolve)) await expect(listener.listen(ma)).to.eventually.rejectedWith('listen EADDRINUSE') await new Promise(resolve => dumbServer.close(() => { resolve() })) }) @@ -152,7 +153,7 @@ describe('listen', () => { }) it('getAddrs preserves p2p Id', async () => { - const ma = multiaddr('/ip4/127.0.0.1/tcp/47382/ws/p2p/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw') + const ma = multiaddr('/ip4/127.0.0.1/tcp/47382/ws') listener = ws.createListener({ upgrader }) await listener.listen(ma) @@ -335,7 +336,7 @@ describe('dial', () => { describe('ip4 with wss', () => { let ws: Transport let listener: Listener - const ma = multiaddr('/ip4/127.0.0.1/tcp/37284/wss') + const ma = multiaddr('/ip4/127.0.0.1/tcp/37284/tls/ws') beforeEach(async () => { ws = webSockets({ @@ -614,7 +615,7 @@ describe('filter addrs', () => { }) }) -describe('auto-tls', () => { +describe('auto-tls (IPv4)', () => { let ws: Transport let listener: Listener let events: TypedEventEmitter @@ -652,7 +653,72 @@ describe('auto-tls', () => { const addrs = listener.getAddrs() expect(addrs).to.have.lengthOf(1) expect(WebSockets.exactMatch(addrs[0])).to.be.true() + const listeningPromise = pEvent(listener, 'listening') + + events.safeDispatchEvent('certificate:provision', { + detail: { + key: fs.readFileSync('./test/fixtures/key.pem', { + encoding: 'utf-8' + }), + cert: fs.readFileSync('./test/fixtures/certificate.pem', { + encoding: 'utf-8' + }) + } + }) + + await listeningPromise + + const addrs2 = listener.getAddrs() + expect(addrs2).to.have.lengthOf(2) + expect(WebSockets.exactMatch(addrs2[0])).to.be.true() + expect(WebSocketsSecure.exactMatch(addrs2[1])).to.be.true() + + const wsOptions = addrs2[0].toOptions() + const wssOptions = addrs2[1].toOptions() + + expect(wsOptions.host).to.equal(wssOptions.host) + expect(wsOptions.port).to.equal(wssOptions.port) + }) +}) + +describe('auto-tls (IPv6)', () => { + let ws: Transport + let listener: Listener + let events: TypedEventEmitter + const ma = multiaddr('/ip6/::1/tcp/37284/ws') + beforeEach(async () => { + events = new TypedEventEmitter() + + const upgrader = stubInterface({ + upgradeInbound: Sinon.stub().resolves(), + upgradeOutbound: async () => { + return stubInterface() + } + }) + + ws = webSockets({ + websocket: { + rejectUnauthorized: false + } + })({ + events, + logger: defaultLogger() + }) + listener = ws.createListener({ + upgrader + }) + await listener.listen(ma) + }) + + afterEach(async () => { + await listener.close() + }) + + it('should listen on wss after a certificate is found', async () => { + const addrs = listener.getAddrs() + expect(addrs).to.have.lengthOf(1) + expect(WebSockets.exactMatch(addrs[0])).to.be.true() const listeningPromise = pEvent(listener, 'listening') events.safeDispatchEvent('certificate:provision', {