get client ip of the request in net library nodejs

2019-08-16 18:50发布

问题:

I am using sticky session in nodejs which is behind nginx. Sticky session does the load balancing by checking the remoteAddress of the connection. Now the problem is it always take ip of nginx server

 server = net.createServer({ pauseOnConnect: true },function(c) {

  // Get int31 hash of ip
  var worker,
      ipHash = hash((c.remoteAddress || '').split(/\./g), seed);

  // Pass connection to worker
  worker = workers[ipHash % workers.length];
  worker.send('sticky-session:connection', c);
});

Can we get the client ip using net library?

Nginx Configuration:

 server {
    listen       80 default_server;
    server_name  localhost;
    root         /usr/share/nginx/html;
    #auth_basic "Restricted";
#auth_basic_user_file /etc/nginx/.htpasswd;
    #charset koi8-r;

    #access_log  /var/log/nginx/host.access.log  main;

    # Load configuration files for the default server block.
    include /etc/nginx/default.d/*.conf;

    location / {
   set_real_ip_from 0.0.0.0/0;
   real_ip_header X-Forwarded-For;
  real_ip_recursive on;
    proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;

    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass http://socket_nodes;
    proxy_read_timeout 3000;

回答1:

As mef points out, sticky-session doesn't, at present, work behind a reverse proxy, where remoteAddress is always the same. The pull request in the aforementioned issue, as well as an earlier pull request, might indeed solve the problem, though I haven't tested myself.

However, those fixes rely on partially parsing packets, doing low-level routing while peeking into headers at a higher level... As the comments on the pull requests indicate, they're unstable, depend on undocumented behavior, suffer from compatibility issues, might degrade performance, etc.

If you don't want to rely on experimental implementations like that, one alternative would be leaving load balancing entirely up to nginx, which can see the client's real IP and so keep sessions sticky. All you need is nginx's built-in ip_hash load balancing.

Your nginx configuration might then look something like this:

upstream socket_nodes {
    ip_hash;
    server 127.0.0.1:8000;
    server 127.0.0.1:8001;
    server 127.0.0.1:8002;
    server 127.0.0.1:8003;
    server 127.0.0.1:8004;
    server 127.0.0.1:8005;
    server 127.0.0.1:8006;
    server 127.0.0.1:8007;
}

server {
    listen       80 default_server;
    server_name  localhost;
    root         /usr/share/nginx/html;

    # Load configuration files for the default server block.
    include /etc/nginx/default.d/*.conf;

    location / {
        # Note: Trusting all addresses like this means anyone
        # can pretend to have any address they want.
        # Only do this if you're absolutely certain only trusted
        # sources can reach nginx with requests to begin with.
        set_real_ip_from 0.0.0.0/0;

        real_ip_header X-Forwarded-For;
        real_ip_recursive on;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_http_version 1.1;

        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_pass http://socket_nodes;
        proxy_read_timeout 3000;
    }
}

Now, to get this to work, your server code would also need to be modified somewhat:

if (cluster.isMaster) {
    var STARTING_PORT = 8000;
    var NUMBER_OF_WORKERS = 8;

    for (var i = 0; i < NUMBER_OF_WORKERS; i++) {
        // Passing each worker its port number as an environment variable.
        cluster.fork({ port: STARTING_PORT + i });
    }

    cluster.on('exit', function(worker, code, signal) {
        // Create a new worker, log, or do whatever else you want. 
    });
}
else {
    server = http.createServer(app);

    // Socket.io initialization would go here.

    // process.env.port is the port passed to this worker by the master.
    server.listen(process.env.port, function(err) {
        if (err) { /* Error handling. */ }
        console.log("Server started on port", process.env.port);
    });
}

The difference is that instead of using cluster to have all worker processes share a single port (load balanced by cluster itself), each worker gets its own port, and nginx can distribute load between the different ports to get to the different workers.

Since nginx chooses which port to go to based on the IP it gets from the client (or the X-Forwarded-For header in your case), all requests in the same session will always end up at the same process.

One major disadvantage of this method, of course, is that the number of workers becomes far less dynamic. If the ports are "hard-coded" in the nginx configuration, the Node server has to be sure to always listen to exactly those ports, no less and no more. In the absence of a good system for syncing the nginx config and the Node server, this introduces the possibility of error, and makes it somewhat more difficult to dynamically scale to e.g. the number of cores in an environment.

Then again, I imagine one could overcome this issue by either programmatically generating/updating the nginx configuration, so it always reflects the desired number of processes, or possibly by configuring a very high number of ports for nginx and then making Node workers each listen to multiple ports as needed (so you could still have exactly as many workers as there are cores). I have not, however, personally verified or tried implementing either of these methods so far.


Note regarding an nginx server behind a proxy

In the nginx configuration you provided, you seem to have made use of ngx_http_realip_module. While you made no explicit mention of this in the question, please note that this may in fact be necessary, in cases where nginx itself sits behind some kind of proxy, e.g. ELB.

The real_ip_header directive is then needed to ensure that it's the real client IP (in e.g. X-Forwarded-For), and not the other proxy's, that's hashed to choose which port to go to.

In such a case, nginx is actually serving a fairly similar purpose to what the pull requests for sticky-session attempted to accomplish: using headers to make the load balancing decisions, and specifically to make sure the same real client IP is always directed to the same process.

The key difference, of course, is that nginx, as a dedicated web server, load balancer and reverse proxy, is designed to do exactly these kinds of operations. Parsing and manipulating the different layers of the protocol stack is its bread and butter. Even more importantly, while it's not clear how many people have actually used these pull requests, nginx is stable, well-maintained and used virtually everywhere.



回答2:

It seems that the module you're using does not support yet to be behind a reverse-proxy source.

Have a look at this Github issue, some pull requests seem to fix your problem, so you may have a solution by using a fork of the module (you can point at it on github from your package.json file.)