简体   繁体   中英

Node.js server listens to the same port on 0.0.0.0 and localhost without error

I've stumbled across something interesting and I can't explain it, nor was Googling it productive.

I have one Express server, server 1, bound to localhost :

const express = require('express')
const app = express()
app.get('/', (req, res) => res.send('server 1'))
app.listen(4000, 'localhost')
node      37624 user   27u  IPv4 0x681653f502970305      0t0  TCP localhost:4000 (LISTEN)

I have another Express server, server 2, bound to all interfaces at 0.0.0.0 :

const express = require('express')
const app = express()
app.get('/', (req, res) => res.send('server 2'))
app.listen(4000, '0.0.0.0')
node      37624 user   27u  IPv4 0x681653f502970305      0t0  TCP localhost:4000 (LISTEN)
node      37693 user   25u  IPv4 0x681653f4fdbdc005      0t0  TCP *:4000 (LISTEN)

Curling 0.0.0.0 gives a response from server 1, the one bound to localhost , so clearly these two are conflicting.

Somehow, however, this does not throw an error one would expect, EADDRINUSE, how can that be?

The SO_REUSEADDR flag is being set on the network sockets in the OS by Node is causing this behavior. The REUSEADDR flag has a special interaction with the IPARR_ANY (aka 0.0.0.0 for IPv4) address. From the socket manual pages (reputable source):

   SO_REUSEADDR
          Indicates that the rules used in validating addresses supplied
          in a bind(2) call should allow reuse of local addresses.  For
          AF_INET sockets this means that a socket may bind, except when
          there is an active listening socket bound to the address.
          When the listening socket is bound to INADDR_ANY with a spe‐
          cific port then it is not possible to bind to this port for
          any local address.  Argument is an integer boolean flag.

From an article that goes into this exact problem:

Some folks don't like SO_REUSEADDR because it has a security stigma attached to it. On some operating systems it allows the same port to be used with a different address on the same machine by different processes at the same time. This is a problem because most servers bind to the port, but they don't bind to a specific address, instead they use INADDR_ANY (this is why things show up in netstat output as *.8080). So if the server is bound to *.8080, another malicious user on the local machine can bind to local-machine.8080, which will intercept all of your connections since it is more specific.

I modified some Linux test code to explicitly demonstrate this (bottom). When you run it you get the following output:

Opening 0.0.0.0 with no reuse flag:19999
Opening Loopback with no resuse flag:19999
bind: Address already in use
Correct: could not open lookpback with no reuse 19999
Opening 0.0.0.0 with with reuse flag:19999
Opening Loopback with with resuse flag:19999
Correct: could open lookpback with reuse 19999

The first test case opens a socket on the IPADDR_ANY address without the REUSEADDR flag set and when there is an attempt to open a socket on the loopback a EADDRINUSE error is thrown by 'bind' (as you originally expected). The second test case does the same thing but with the REUSEADDR flag set and the second socket is created without an error.

#include <errno.h>
#include <error.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#define PORT 19999

int open_port(int any, int reuse)
{
        int fd = -1;
        int reuseaddr = 1;
        int v6only = 1;
        int addrlen;
        int ret = -1;
        struct sockaddr *addr;
        int family = AF_INET;

        struct sockaddr_in addr4 = {
                .sin_family = AF_INET,
                .sin_port = htons(PORT),
                .sin_addr.s_addr = any ? htonl(INADDR_ANY) : inet_addr("127.0.0.1"),
        };


        addr = (struct sockaddr*)&addr4;
        addrlen = sizeof(addr4);

        if ((fd = socket(family, SOCK_STREAM, IPPROTO_TCP)) < 0) {
                perror("socket");
                goto out;
        }

        if (reuse){
                if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr,
                               sizeof(reuseaddr)) < 0) {
                        perror("setsockopt SO_REUSEADDR");
                        goto out;
                }
        }

        if (bind(fd, addr, addrlen) < 0) {
                perror("bind");
                goto out;
        }

        if (any)
                return fd;

        if (listen(fd, 1) < 0) {
                perror("listen");
                goto out;
        }
        return fd;
out:
        close(fd);
        return ret;
}

int main(void)
{
        int listenfd;
        int fd1, fd2;

        fprintf(stderr, "Opening 0.0.0.0 with no reuse flag:%d\n", PORT);
        listenfd = open_port(1, 0);
        if (listenfd < 0)
                error(1, errno, "Couldn't open listen socket");

        fprintf(stderr, "Opening Loopback with no resuse flag:%d\n", PORT);
        fd1 = open_port(0, 0);
        if (fd1 >= 0)
                error(1, 0, "Was allowed to create an loopback with no reuse");
        fprintf(stderr, "Correct: could not open lookpback with no reuse %d\n", PORT);

        close(listenfd);


        fprintf(stderr, "Opening 0.0.0.0 with with reuse flag:%d\n", PORT);
        listenfd = open_port(1, 1);
        if (listenfd < 0)
                error(1, errno, "Couldn't open listen socket");

        fprintf(stderr, "Opening Loopback with with resuse flag:%d\n", PORT);
        fd1 = open_port(0, 1);
        if (fd1 < 0)
                error(1, 0, "Was not allowed to create an loopback with  reuse");
        fprintf(stderr, "Correct: could open lookpback with reuse %d\n", PORT);
        close(fd1);
        close(listenfd);

        return 0;
}

Hello i think i can help you on this .

  • First see difference between 0.0.0.0 and localhost . Suppose if you are running your server on 0.0.0.0 this means that it will run that server which is available at that time so that's server 1 because 0.0.0.0 is nothing but it means that just run a server available to you so 0.0.0.0 know that server 1 is running that's why it is redirecting to server 1 because you have initialized on same port .

  • I mean if you run 0.0.0.0:4000 it will redirect to localhost:4000 because 0.0.0.0 is not a host but it's an address used to refer to all IP addresses on the same machine so 0.0.0.0 refers to 127.0.0.1:4000 it is the normal loopback address, and localhost:4000 is the hostname for 127.0.0.1:4000.

  • here is much more simpler explanation : 0.0.0.0:4000 ---> 127.0.0.1:4000 ---> localhost:4000

The kernel in Windows allows multiple applications to share a port as long as the Url is unique

is it possible to make nodejs scripts listen to same port

You are listening to the same port 4000 with your two servers.

And if you wanna run two servers you should explicitly set two different port for each of the servers, something like this.

// server 1
app.get('/', (req, res) => res.send('server 1'))
app.listen(4000,() => console.log('server 1 listening to port 4000'))

// server 2
app.get('/', (req, res) => res.send('server 2'))
app.listen(5000, () => console.log('server 2 listening to port 5000'))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM