uNetworking / uWebSockets.js

μWebSockets for Node.js back-ends :metal:
Apache License 2.0
7.84k stars 569 forks source link

ws.getRemoteAddressAsText() always reads 0.0.0.1 behind nginx proxy_pass #1088

Closed antonymott closed 1 month ago

antonymott commented 1 month ago

I'm using uWebSockets.js behind nginx proxy_pass with:

proxy_set_header X-Real-IP $remote_addr;

in the nginx server block. I'm guessing the issue is with nginx.conf as the uWebSockets app works great, except I cannot access actual visitor $remote_addr in uWebSocket.js:

Here's my uWebSockets.js code:

...
open: (ws: uWS.WebSocket<unknown>) => {
    // helper
    function arrayBufferToString(buffer) {
        return decoderUtf8.decode(new Uint8Array(buffer))
    }
    const remoteAddr = ws.getRemoteAddressAsText()
    const ip = arrayBufferToString(remoteAddr)
    console.log('original IP: ', ip)
...

outputs one of: "0000:0000:0000:0000:0000:ffff:7f00:0001" "0000:0000:0000:0000:0000:0000:0000:0001" "0.0.0.1"

Steps I've taken to solve Searched other questions and threads on this forum and StackExchange, which led me to add extra proxy_set_header in my nginx.conf file. nginx access.log shows $remote_addr with correct value. Here's nginx.conf:

map $http_upgrade $connection_upgrade {
    default upgrade;
    #'' close;
}

upstream websocket {
    server localhost:4000;
}

server {
    listen 8443 ssl;

    location / {
        proxy_pass http://websocket;
                proxy_http_version 1.1;

                # set request headers upstream
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_connect_timeout 300;
        proxy_send_timeout 300;
        proxy_read_timeout 300;
        proxy_socket_keepalive on;
                proxy_set_header X-Real-IP $remote_addr;

                # tried various things from other questions in uWebSocket forum and stackexchange 
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_set_header X-Forwarded-Host $remote_addr;
                proxy_set_header X-Forwarded_Port $remote_port;
    }
}
e3dio commented 1 month ago

Get the X-Real-IP header in the upgrade ws event and attach that IP to your ws data Upgrade.js#L21

antonymott commented 1 month ago

THANKS @e3dio, it worked!

I did look at Upgrade.js in the examples, but assumed upgrade.js example was only if I did nginx proxy_pass but without nginx requesting the upgrade...anyway I added the .upgrade section to my nodejs code, and for the time being kept identical my nginx.conf as above.

Is there a reason ws.getRemoteAddressAsText() doesn't show the x-real-ip header?

Anyway, I'm delighted it's working now, so thanks again, and as upgrade.js doesn't include an example of retrieving common header names like x-real-ip or x-forwarded-for, I include below code that worked if it may help others, too:

upgrade: (res, req, context) => {
      res.upgrade(
        // upgrade to websocket
        {
          xRealIp: req.getHeader('x-real-ip'),
          xForwardedFor: req.getHeader('x-forwarded-for'),
          ip: res.getRemoteAddressAsText(),
        }, // 1st argument sets which properties to pass to ws object, in this case ip address
        req.getHeader('sec-websocket-key'),
        req.getHeader('sec-websocket-protocol'),
        req.getHeader('sec-websocket-extensions'), // 3 headers are used to setup websocket
        context // also used to setup websocket
      )
    },
    open: (ws: uWS.WebSocket<unknown>) => {
      const funcName = ' .App().ws/chat:open'
      function arrayBufferToString(buffer) {
        return decoderUtf8.decode(new Uint8Array(buffer))
      }

      console.log('ws.xRealIp:', ws.xRealIp) // outputs real ip
      console.log('ws.xForwardedFor:', ws.xForwardedFor) // also works great

      const remoteAddr = ws.getRemoteAddressAsText()
      const ip = arrayBufferToString(remoteAddr)
      console.log('original IP: ', ip) // doesn't appear to work, outputs 0.0.0.1 or similar
e3dio commented 1 month ago

Is there a reason ws.getRemoteAddressAsText() doesn't show the x-real-ip header?

ws.getRemoteAddressAsText() shows IP of your nginx server, because that is what is connecting to your Node server. The remote IP address of client connected to your nginx is passed in the X-Real-IP header

antonymott commented 1 month ago

@e3dio thanks. I'm surprised in a good way, but unsure how it even works, as nginx already requested the upgrade, so hoping the addition of the upgrade event in the nodejs code doesn't add much overhead to new connections.

I see you are a main contributor, thanks again for being part of an outstanding project. Would it help other users do you think if i updated upgrade.js, through a pull request, to include this x-real-ip header retrieval example, or do you think this thread is good enough and I'll mark it as closed?