nospaceships / node-raw-socket

Raw sockets for Node.js.
207 stars 69 forks source link

Cant be used in Worker Threads (`Error: Module did not self-register`/ERR_DLOPEN_FAILED) #89

Open mStirner opened 5 months ago

mStirner commented 5 months ago

I try to use raw-socket in a worker thread, which results in a ERR_DLOPEN_FAILED/Module did not self-register error. I have a wrapper script that can be treated as cli tool or as a worker thread entry point.

When used as CLI tool, everything works like expected. As soon as i spawn the script as a worker thread, its results in the error above.

Wrapper script:

#! /usr/bin/node

const { EOL } = require("os");
const {
    isMainThread,
    workerData,
    parentPort
} = require("worker_threads");

const WebSocket = require("ws");
const minimist = require("minimist");

const argv = minimist(process.argv, {
    boolean: ["help"],
    default: {
        upstream: "",
        socket: "tcp",
        host: "",
        port: ""
    }
});

// check arguments if used as cli client or spawend via worker thread
if ((isMainThread && (!argv.upstream || !argv.host)) || argv.help) {
    console.group("Usage of bridge.js as cli tool:", EOL);
    console.log(`bridge.js --upstream="ws://example.com" --host="127.0.0.1" --port="8080"`);
    console.log(`bridge.js --upstream="ws://open-haus.lan/api/foo/bar" --host="172.16.0.13" --socket=udp --port="53"`);
    console.log(`bridge.js --upstream="ws://127.0.0.1:8080/api/devices/663fc49985397fe02064d60d/interfaces/663fc4a06a1e907dd8e86f0e" --host="127.0.0.1" --port="8123"`);
    console.log(`bridge.js --upstream="ws://127.0.0.1:8080/api/devices/663fc4b0490a00181d03486c/interfaces/663fc4b5d6cf46265f713ba4" --host="192.168.2.1" --socket=raw`, EOL);
    console.log("--upstream\tWebSocket upstream endpoint");
    console.log("--socket\tNetwork socket type: tcp|udp|raw");
    console.log("--host\tHost to connect to");
    console.log("--port\tHost port to connect to");
    console.log("");
    process.exit(0);
}

if (!isMainThread) {
    Object.assign(argv, workerData);
}

// bridge the websocket stream to underlaying network socket
let ws = new WebSocket(argv.upstream);

ws.once("error", (err) => {

    console.error(err);
    process.exit(10);

});

ws.once("close", (code) => {
    console.log("Closed with code", code);
    process.exit();
});

ws.once("open", () => {

    let upstream = WebSocket.createWebSocketStream(ws);

    console.log("ARGV", argv);
    console.log("WorkerData", workerData)

    let socket = require(`./sockets/${argv.socket}.js`)({
        host: argv.host,
        port: argv.port
    });

    upstream.pipe(socket);
    socket.pipe(upstream);

    if (!isMainThread) {
        parentPort.on("message", (msg) => {
            if (msg === "disconnect") {

                ws.close(() => {
                    process.exit(0);
                });

            }
        });
    }

});

sockets/raw.js:

const { Duplex } = require("stream");
const raw = require("raw-socket");

// this file handles raw network sockets
// the protocol implemtnation is done on the server side
const logger = require("../system/logger.js");

module.exports = ({ host, port }, options) => {

    let socket = raw.createSocket({
        protocol: raw.Protocol.ICMP
    });

    let stream = new Duplex({
        write(chunk, encoding, cb) {

            console.log("Write to device", `raw://${host}:${port}`, chunk);

            socket.send(chunk, 0, chunk.length, host, (error, bytes) => {
                console.log("Writen to devoce")
                if (error)
                    console.log(error.toString());
            });

        },
        read(size) {
            logger.verbose(`raw://${host}:${port} Read called`, size);
        },
        end(chunk) {
            if (chunk) {
                socket.send(chunk, 0, chunk.length, host, (error, bytes) => {
                    if (error)
                        console.log(error.toString());
                });
            }
            socket.close();
        }
    });

    socket.on("error", (err) => {
        logger.error(`[error] udp://${host}:${port}`, err);
    });

    socket.on("close", () => {
        logger.debug(`[closed] udp://${host}:${port}`);
    });

    socket.on("message", (buffer, source) => {
        if (source === host) {
            console.log("received " + buffer.length + " bytes from " + source);
            stream.push(buffer);
        }
    });

    return stream;

};

Full error message:

Worker died Error: Module did not self-register: '/home/marc/projects/OpenHaus/connector/node_modules/raw-socket/build/Release/raw.node'.
    at Module._extensions..node (node:internal/modules/cjs/loader:1473:18)
    at Module.load (node:internal/modules/cjs/loader:1207:32)
    at Module._load (node:internal/modules/cjs/loader:1023:12)
    at Module.require (node:internal/modules/cjs/loader:1235:19)
    at require (node:internal/modules/helpers:176:18)
    at Object.<anonymous> (/home/marc/projects/OpenHaus/connector/node_modules/raw-socket/index.js:4:11)
    at Module._compile (node:internal/modules/cjs/loader:1376:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
    at Module.load (node:internal/modules/cjs/loader:1207:32)
    at Module._load (node:internal/modules/cjs/loader:1023:12) {
  code: 'ERR_DLOPEN_FAILED'
}

Debugging details:

readelf -h node_modules/raw-socket/build/Release/raw.node 
ELF-Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Klasse:                            ELF64
  Daten:                             2er-Komplement, Little-Endian
  Version:                           1 (aktuell)
  OS/ABI:                            UNIX - System V
  ABI-Version:                       0
  Typ:                               DYN (geteilte Objektadatei)
  Maschine:                          Advanced Micro Devices X86-64
  Version:                           0x1
  Einstiegspunktadresse:               0x0
  Beginn der Programm-Header:          64 (Bytes in Datei)
  Beginn der Sektions-header:          67936 (Bytes in Datei)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         11
  Size of section headers:           64 (bytes)
  Number of section headers:         31
  Section header string table index: 30

node version: v20.11.0 npm version: v10.5.1 raw socket: v1.8.1 os: Ubuntu v23.10

An idea how to fix this?

Already tried to re-install & re-build dependencies. But since it works as cli tool/standalone module, i think this is purposeful anyways.

mStirner commented 5 months ago

Could it be related to how the module is exposed? (Found this: https://stackoverflow.com/a/68598406/5781499)

So NODE_MODULE needs to be changed to NAN_MODULE_WORKER_ENABLED?

mStirner commented 5 months ago

Seems like that using NAN_MODULE_WORKER_ENABLED fix the issue:

index b8e4850..b8f50e5 100644
--- a/src/raw.cc
+++ b/src/raw.cc
@@ -52,7 +52,9 @@ void InitAll (Local<Object> exports) {
    SocketWrap::Init (exports);
 }

-NODE_MODULE(raw, InitAll)
+// see #89
+//NODE_MODULE(raw, InitAll)
+NAN_MODULE_WORKER_ENABLED(raw, InitAll)

 NAN_METHOD(CreateChecksum) {
    Nan::HandleScope scope;