When Fastify is listening on IPv4+6, routes return 404 when connecting from IPv6 addresses
See original GitHub issuePrerequisites
- I have written a descriptive issue title
- I have searched existing issues to ensure the bug has not already been reported
Fastify version
4.0.1
Plugin version
6.0.1
Node.js version
16.15.0
Operating system
macOS
Operating system version (i.e. 20.04, 11.3, 10)
12.4
Description
When configured to listen on localhost
, Fastify spawns two http.Server
s: one on IPv4 address localhost
on startup, which is exposed as fastify.server
(fastify/server.js:15), and a second one on IPv6 address ::
, some time after the first one has finished listening, exposed through symbol kServerBindings
(fastify/server.js:133).
However, fastify-websocket
only listens to upgrade
events on the first server (fastify-websocket/index.js:41), which means connections from IPv6 addresses are never upgraded, and so never handled, and reach the fallback 404 handler.
On my machine, where IPv6 is available, Node ws
connections have a remoteAddress
of 127.0.0.1
, and reach the first http.Server
, triggering the proper upgrade
handling. However, browser WebSocket
connections come from ::1
, and reach the second, which has no upgrade
handler set up. In the following extract from the logs, the only difference in the requests is remoteAddress
:
{"level":30,"time":1655248914754,"pid":86869,"hostname":"<my hostname>","reqId":"req-1","req":{"method":"GET","url":"/connect/worker","hostname":"localhost:3001","remoteAddress":"127.0.0.1","remotePort":55029},"msg":"incoming request"}
☝️ No failure, and my route handler logs successfully afterwards
{"level":30,"time":1655248918573,"pid":86869,"hostname":"<my hostname>","reqId":"req-2","req":{"method":"GET","url":"/connect/worker","hostname":"localhost:3001","remoteAddress":"::1","remotePort":55032},"msg":"incoming request"}
{"level":30,"time":1655248918577,"pid":86869,"hostname":"<my hostname>","reqId":"req-2","res":{"statusCode":404},"responseTime":3.638867139816284,"msg":"request completed"}
☝️ 404 on the same route
Because the second http.Server
is only created some time after the first has finished listening, and is still not ready even when the onReady
hook triggers, a clean fix for this is not obvious, without changing the Fastify API. Additionally, the second server is only exposed through a symbol from an untyped module, which complicates things further.
My current work-around is to forward upgrade events from the second server to the first during the listen callback:
// @ts-ignore untyped module
import symbols from 'fastify/lib/symbols.js';
fastify.listen({port: 3001}, () => {
// @ts-ignore unknown index access
fastify[symbols.kServerBindings].forEach((server) => {
server.on('upgrade', (...args: any[]) => {
fastify.server.emit('upgrade', ...args);
});
});
});
Steps to Reproduce
- Set up a simple websocket route
- Listen on
localhost
, with ipv6 enabled, causing a multiple server binding - Trigger a WebSocket call from
remoteAddress
:::1
- → 404
Expected Behavior
- All connections are handled properly
Issue Analytics
- State:
- Created a year ago
- Reactions:2
- Comments:13 (9 by maintainers)
I genuinely never even think about the websocket module.
Good spot! I will take a look as soon as I can (but it might take a few days).