Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

Platform-specific ERR_INTERNAL_ASSERTION HTTP error #47159

Closed
WorldEditAxe opened this issue Mar 19, 2023 · 0 comments
Closed

Platform-specific ERR_INTERNAL_ASSERTION HTTP error #47159

WorldEditAxe opened this issue Mar 19, 2023 · 0 comments

Comments

@WorldEditAxe
Copy link

WorldEditAxe commented Mar 19, 2023

Version

v19.1.0 (from nix, stable 22.11)

Platform

Linux 9dbe5639fcc3 5.15.0-1030-gcp #37~20.04.1-Ubuntu SMP Mon Feb 20 04:30:57 UTC 2023 x86_64 GNU/Linux (more specifically, Replit)

Subsystem

No response

What steps will reproduce the bug?

I've been trying to proxy HTTP requests over a WebSocket connection, but however, I've been getting this weird error that, for some reason, only happens when running my code on the aforementioned platform. Requests are converted to their raw TCP equivalent (through a custom Duplex class), sent over the connection, and forwarded to the actual HTTP server. I'm not sure of the exact cause, but below is the code that proxies the connection and my custom Duplex implementation. These are just the suspected culprits and aren't guaranteed to be the root causes of the issue.\

Code used to forward/proxy HTTP requests: (everything is written in TypeScript)

    export async function forwardHTTP(req: http.IncomingMessage, res: http.ServerResponse) {
        const headers = req.headers,
            route = new URL.URL(req.url!, `http://${req.headers.host!}`),
            agent = new http.Agent()
        ;(agent as any).createConnection = (options: http.RequestOptions, callback: (err: Error, socket: Duplex) => void) => {
            const cId = BACKEND!.getNextConnectionId(),
                packet = new SNewConnectionPacket(),
                tunnel = new UpstreamConnection(cId, BACKEND!)
            packet.channelId = cId
            packet.ip = req.socket.remoteAddress
            packet.port = req.socket.remotePort
            BACKEND!.handler.writePacket(packet, 0)

            ;(tunnel as any).setTimeout = () => true
            ;(tunnel as any).setNoDelay = () => true

            callback(null as any, tunnel as any)
            return tunnel as any
        }
        const httpConnection = http.request(route, {
            agent: agent,
            method: req.method,
            headers: headers
        })
        httpConnection.on('response', remote => {
            res.writeHead(remote.statusCode!, remote.statusMessage, remote.headers)
            remote.pipe(res)
            remote.once('close', () => res.end())
            res.once('close', () => res.end())
        })
        let isEnded = false
        req.pipe(httpConnection)
        res.once('close', () => {
            if (isEnded) return
            isEnded = true
            if (httpConnection.socket) {
                httpConnection.socket!.end()
            } else {
                httpConnection.destroy()
            }
        })
        httpConnection.once('close', () => {
            if (isEnded) return
            isEnded = true
            req.socket!.end()
        })
    }

Duplex implementation:

export class UpstreamConnection extends Duplex {
    backend: RemoteBackend
    channelId: number
    isClosed: boolean = false
    _dataCb?: Function

    constructor(connectionId: number, self: RemoteBackend) {
        super()
        this.channelId = connectionId
        this.backend = self

        const cb = (id: number, data: Buffer) => {
            if (id == this.channelId) {
                this.emit('data', data)
            }
        }

        this.backend.handler.on('packet', cb.bind(this))
        this._dataCb = cb

        this.backend.connections.push(this)
        this.backend.emit('connectionOpen', this)
    }
    
    public _write(chunk: any, encoding: BufferEncoding, callback: (error?: Error | null | undefined) => void): void {
        const data = chunk instanceof Buffer ? chunk : Buffer.from(chunk as string, encoding)
        this.backend.handler.writeRaw(data, this.channelId)
        callback()
    }

    public _read(): void {
        // handled by this._readCb
    }

    public _destroy(error?: Error | null, callback?: (error: Error | null) => void): void {
        const destroyPacket = new SConnectionEndPacket()
        destroyPacket.channelId = this.channelId
        this.backend.handler.writePacket(destroyPacket, 0)
        this.backend.handler.removeListener("packet", this._dataCb! as any)
        this.backend.connections = this.backend.connections.splice(this.backend.connections.indexOf(this), 1)
        this.isClosed = true
        if (callback) callback(error ?? null)
    }

    public end(cb?: (() => void) | undefined): this;
    public end(chunk: any, cb?: (() => void) | undefined): this;
    public end(chunk: any, encoding?: BufferEncoding | undefined, cb?: (() => void) | undefined): this;
    public end(chunk?: unknown, encoding?: unknown, cb?: unknown): this {
        if (chunk != null) {
            this.write(chunk, encoding as any)
        }
        this._destroy(null)
        if (cb != null) (cb as Function)()
        return this
    }
}

Packages used in code (all on NPM): chalk, dotenv, msgpackr, varint, ws

How often does it reproduce? Is there a required condition?

I've tried to run the same code/server on both other Linux and Windows, all on the same version of node.js I've discovered this bug on (via nvm), but to no avail. The error simply can't be replicated outside the problematic platform. Everything works as normal, and the request gets successfully proxied. After some digging around, I found out that this only happened on the 7th proxied request.

What is the expected behavior? Why is that the expected behavior?

I expected the request to get successfully proxied without issue, as shown by running the same code/server on other platforms.

What do you see instead?

The below error, with a reference to this repo's issues page:

node:internal/assert:14
    throw new ERR_INTERNAL_ASSERTION(message);
    ^

Error [ERR_INTERNAL_ASSERTION]: This is caused by either a bug in Node.js or incorrect usage of Node.js internals.
Please open an issue with this stack trace at https://github.com/nodejs/node/issues

    at new NodeError (node:internal/errors:393:5)
    at assert (node:internal/assert:14:11)
    at IncomingMessage.responseOnEnd (node:_http_client:751:5)
    at IncomingMessage.emit (node:events:525:35)
    at IncomingMessage.emit (node:domain:489:12)
    at endReadableNT (node:internal/streams/readable:1359:12) {
  code: 'ERR_INTERNAL_ASSERTION'
}

Node.js v19.1.0

Additional information

No response

@nodejs nodejs locked and limited conversation to collaborators Mar 19, 2023
@bnoordhuis bnoordhuis converted this issue into discussion #47162 Mar 19, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant