Skip to content

Commit

Permalink
feat: auto-tls for websockets
Browse files Browse the repository at this point in the history
Starts a tcp server on the listen port and hands connections off
to internal http or https servers (depdending on connection type).

Upgrade requests from both servers are handled by a websocket
server.

The https server is enabled when either a secure websocket address
is listened to explicitly, or when a TLS certificate is provisioned
by another libp2p component, likely `@libp2p/auto-tls`.

This means we don't need to add another port mapping for the https
server since we run http and https over the same port.
  • Loading branch information
achingbrain committed Nov 19, 2024
1 parent d866eb5 commit b97e91a
Show file tree
Hide file tree
Showing 5 changed files with 413 additions and 120 deletions.
3 changes: 2 additions & 1 deletion packages/transport-websockets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,11 @@
"@libp2p/utils": "^6.2.1",
"@multiformats/multiaddr": "^12.2.3",
"@multiformats/multiaddr-matcher": "^1.4.0",
"@multiformats/multiaddr-to-uri": "^10.0.1",
"@multiformats/multiaddr-to-uri": "^11.0.0",
"@types/ws": "^8.5.10",
"it-ws": "^6.1.1",
"p-defer": "^4.0.1",
"p-event": "^6.0.1",
"progress-events": "^1.0.0",
"race-signal": "^1.0.2",
"wherearewe": "^2.0.1",
Expand Down
45 changes: 37 additions & 8 deletions packages/transport-websockets/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
* ```
*/

import { transportSymbol, serviceCapabilities, ConnectionFailedError } from '@libp2p/interface'
import { transportSymbol, serviceCapabilities, ConnectionFailedError, serviceDependencies } from '@libp2p/interface'
import { multiaddrToUri as toUri } from '@multiformats/multiaddr-to-uri'
import { connect, type WebSocketOptions } from 'it-ws/client'
import pDefer from 'p-defer'
Expand All @@ -67,21 +67,39 @@ import { isBrowser, isWebWorker } from 'wherearewe'
import * as filters from './filters.js'
import { createListener } from './listener.js'
import { socketToMaConn } from './socket-to-conn.js'
import type { Transport, MultiaddrFilter, CreateListenerOptions, DialTransportOptions, Listener, AbortOptions, ComponentLogger, Logger, Connection, OutboundConnectionUpgradeEvents, Metrics, CounterGroup } from '@libp2p/interface'
import type { Transport, MultiaddrFilter, CreateListenerOptions, DialTransportOptions, Listener, AbortOptions, ComponentLogger, Logger, Connection, OutboundConnectionUpgradeEvents, Metrics, CounterGroup, TypedEventTarget, Libp2pEvents } from '@libp2p/interface'
import type { Multiaddr } from '@multiformats/multiaddr'
import type { Server } from 'http'
import type { DuplexWebSocket } from 'it-ws/duplex'
import type http from 'node:http'
import type https from 'node:https'
import type { ProgressEvent } from 'progress-events'
import type { ClientOptions } from 'ws'

export interface WebSocketsInit extends AbortOptions, WebSocketOptions {
filter?: MultiaddrFilter
websocket?: ClientOptions
server?: Server
http?: http.ServerOptions
https?: https.ServerOptions

/**
* If a service like `@libp2p/auto-tls` creates a TLS certificate this
* transport can use, upgrade any listeners from `/ws` to `/wss`.
*
* @default false
*/
autoTLS?: boolean

/**
* Inbound connections must complete their upgrade within this many ms
*
* @default 5000
*/
inboundConnectionUpgradeTimeout?: number
}

export interface WebSocketsComponents {
logger: ComponentLogger
events: TypedEventTarget<Libp2pEvents>
metrics?: Metrics
}

Expand All @@ -95,12 +113,12 @@ export type WebSocketsDialEvents =

class WebSockets implements Transport<WebSocketsDialEvents> {
private readonly log: Logger
private readonly init?: WebSocketsInit
private readonly init: WebSocketsInit
private readonly logger: ComponentLogger
private readonly metrics?: WebSocketsMetrics
private readonly components: WebSocketsComponents

constructor (components: WebSocketsComponents, init?: WebSocketsInit) {
constructor (components: WebSocketsComponents, init: WebSocketsInit = {}) {
this.log = components.logger.forComponent('libp2p:websockets')
this.logger = components.logger
this.components = components
Expand All @@ -124,6 +142,16 @@ class WebSockets implements Transport<WebSocketsDialEvents> {
'@libp2p/transport'
]

get [serviceDependencies] (): string[] {
if (this.init.autoTLS === true) {
return [
'@libp2p/auto-tls'
]
}

return []
}

async dial (ma: Multiaddr, options: DialTransportOptions<WebSocketsDialEvents>): Promise<Connection> {
this.log('dialing %s', ma)
options = options ?? {}
Expand Down Expand Up @@ -180,13 +208,14 @@ class WebSockets implements Transport<WebSocketsDialEvents> {
}

/**
* Creates a Websockets listener. The provided `handler` function will be called
* Creates a WebSockets listener. The provided `handler` function will be called
* anytime a new incoming Connection has been successfully upgraded via
* `upgrader.upgradeInbound`
*/
createListener (options: CreateListenerOptions): Listener {
return createListener({
logger: this.logger,
events: this.components.events,
metrics: this.components.metrics
}, {
...this.init,
Expand All @@ -195,7 +224,7 @@ class WebSockets implements Transport<WebSocketsDialEvents> {
}

/**
* Takes a list of `Multiaddr`s and returns only valid Websockets addresses.
* Takes a list of `Multiaddr`s and returns only valid WebSockets addresses.
* By default, in a browser environment only DNS+WSS multiaddr is accepted,
* while in a Node.js environment DNS+{WS, WSS} multiaddrs are accepted.
*/
Expand Down
Loading

0 comments on commit b97e91a

Please sign in to comment.