Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
mStirner opened this issue May 11, 2024 · 2 comments

Comments

@mStirner
Copy link

mStirner commented May 11, 2024

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
Copy link
Author

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
Copy link
Author

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;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant