What is the problem this feature will solve?
CAN bus (Controller Area Network) is the universal data bus for automotive (J1939), marine (NMEA 2000), and industrial IoT. On Linux, CAN is accessed via the SocketCAN subsystem using standard AF_CAN sockets — the same POSIX socket model as AF_INET and AF_UNIX, available since kernel 2.6.25 (2008).
Today, every Node.js project that needs CAN bus access must use a native addon. The two available packages — socketcan (sebi2k1/node-can) and rawcan (jjkr/rawcan) — both bridge the kernel CAN fd into Node.js via uv_poll_t. This architecture has a known silent-stall failure mode: under GC pressure or event loop starvation, the poll handle stops being re-armed, and the CAN socket permanently stops delivering data while the underlying kernel socket remains alive. candump in another terminal keeps working — only Node.js goes silent.
This is a real and painful problem. In the Signal K open-source marine electronics project, users on SocketCAN hardware (SailorHat, Pican-M, Waveshare CAN hats) experience intermittent silent N2K data stalls requiring server restarts. It has been reported repeatedly across multiple issues (SignalK/signalk-server#1626, SignalK/signalk-server#2140, canboat/canboatjs#145). The rawcan alternative is abandoned for 9 years and has the identical uv_poll_t problem.
The root cause cannot be fixed in the addons without reimplementing libuv's stream machinery. It is an architectural limitation of uv_poll_t for this use case.
What is the feature you are proposing to solve the problem?
Add a canDevice option to net.createConnection() that creates an AF_CAN socket, similar to how the existing path option creates AF_UNIX sockets:
const socket = net.createConnection({ canDevice: 'can0' });
socket.on('data', (buf) => {
// raw struct can_frame, 16 bytes per frame
});
socket.write(canFrameBuffer);
The socket delivers raw struct can_frame bytes (16 bytes per frame). Frame parsing stays in userland — libraries like canboatjs already handle this.
Implementation approach (working prototype at https://github.com/dirkwa/node/tree/can-socket-support):
- A
CANWrap C++ class performs socket(PF_CAN, SOCK_RAW, CAN_RAW) + bind(), then hands the fd to libuv via uv_pipe_open() which uses uv_stream_t — the same proven stream machinery that handles every TCP and Unix socket in Node.js
- No libuv modifications required
- Linux-only (
#ifdef __linux__), throws ERR_INVALID_ARG_VALUE on other platforms
- CAN sockets are immediately ready after bind (no async connect phase)
This eliminates the uv_poll_t failure mode architecturally. The CAN fd is owned by libuv's stream I/O, not by addon code that must manually re-arm poll handles.
What alternatives have you considered?
- The stall is architectural (
uv_poll_t), not a simple bug. Fixing it would mean rewriting their I/O layer to not use uv_poll_t — essentially reimplementing what Node.js core already has.
- canboatjs PR #401: We have shipped a stopgap fix using a minimal native addon that opens the CAN fd and passes it to
fs.createReadStream (threadpool-based uv_fs_read). This works but is a workaround — it uses the fs threadpool for what should be an event-loop-driven stream.
- Leave it to addons: Every CAN project would continue to independently solve the same
uv_poll_t problem. The automotive, marine, and industrial IoT Node.js ecosystem deserves better.
AF_CAN has the same standing as AF_UNIX — it's a standard Linux kernel socket family using the same POSIX socket API. Adding it to net is a natural extension of Node.js's existing socket support.
We are currently working on a proof-of-concept implementation and have a working prototype that has been tested on both vcan0 (virtual CAN) and real CAN hardware (RPi4 with CAN hat on a live NMEA 2000 bus). The branch is at https://github.com/dirkwa/node/tree/can-socket-support — we intend to open a PR once we have feedback on this proposal.
What is the problem this feature will solve?
CAN bus (Controller Area Network) is the universal data bus for automotive (J1939), marine (NMEA 2000), and industrial IoT. On Linux, CAN is accessed via the SocketCAN subsystem using standard
AF_CANsockets — the same POSIX socket model asAF_INETandAF_UNIX, available since kernel 2.6.25 (2008).Today, every Node.js project that needs CAN bus access must use a native addon. The two available packages —
socketcan(sebi2k1/node-can) andrawcan(jjkr/rawcan) — both bridge the kernel CAN fd into Node.js viauv_poll_t. This architecture has a known silent-stall failure mode: under GC pressure or event loop starvation, the poll handle stops being re-armed, and the CAN socket permanently stops delivering data while the underlying kernel socket remains alive.candumpin another terminal keeps working — only Node.js goes silent.This is a real and painful problem. In the Signal K open-source marine electronics project, users on SocketCAN hardware (SailorHat, Pican-M, Waveshare CAN hats) experience intermittent silent N2K data stalls requiring server restarts. It has been reported repeatedly across multiple issues (SignalK/signalk-server#1626, SignalK/signalk-server#2140, canboat/canboatjs#145). The
rawcanalternative is abandoned for 9 years and has the identicaluv_poll_tproblem.The root cause cannot be fixed in the addons without reimplementing libuv's stream machinery. It is an architectural limitation of
uv_poll_tfor this use case.What is the feature you are proposing to solve the problem?
Add a
canDeviceoption tonet.createConnection()that creates anAF_CANsocket, similar to how the existingpathoption createsAF_UNIXsockets:The socket delivers raw
struct can_framebytes (16 bytes per frame). Frame parsing stays in userland — libraries like canboatjs already handle this.Implementation approach (working prototype at https://github.com/dirkwa/node/tree/can-socket-support):
CANWrapC++ class performssocket(PF_CAN, SOCK_RAW, CAN_RAW)+bind(), then hands the fd to libuv viauv_pipe_open()which usesuv_stream_t— the same proven stream machinery that handles every TCP and Unix socket in Node.js#ifdef __linux__), throwsERR_INVALID_ARG_VALUEon other platformsThis eliminates the
uv_poll_tfailure mode architecturally. The CAN fd is owned by libuv's stream I/O, not by addon code that must manually re-arm poll handles.What alternatives have you considered?
uv_poll_t), not a simple bug. Fixing it would mean rewriting their I/O layer to not useuv_poll_t— essentially reimplementing what Node.js core already has.fs.createReadStream(threadpool-baseduv_fs_read). This works but is a workaround — it uses the fs threadpool for what should be an event-loop-driven stream.uv_poll_tproblem. The automotive, marine, and industrial IoT Node.js ecosystem deserves better.AF_CANhas the same standing asAF_UNIX— it's a standard Linux kernel socket family using the same POSIX socket API. Adding it tonetis a natural extension of Node.js's existing socket support.We are currently working on a proof-of-concept implementation and have a working prototype that has been tested on both vcan0 (virtual CAN) and real CAN hardware (RPi4 with CAN hat on a live NMEA 2000 bus). The branch is at https://github.com/dirkwa/node/tree/can-socket-support — we intend to open a PR once we have feedback on this proposal.