Node.js 服务器在 0.0.0.0 和 localhost 上监听同一个端口,没有错误

Jur*_*lav 3 network-programming node.js express

我偶然发现了一些有趣的东西,我无法解释它,谷歌搜索也没有成效。

我有一台 Express 服务器,即服务器 1,绑定到localhost

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

我有另一个 Express 服务器,即服务器 2,绑定到以下所有接口0.0.0.0

node      37624 user   27u  IPv4 0x681653f502970305      0t0  TCP localhost:4000 (LISTEN)
Run Code Online (Sandbox Code Playgroud)
node      37624 user   27u  IPv4 0x681653f502970305      0t0  TCP localhost:4000 (LISTEN)
node      37693 user   25u  IPv4 0x681653f4fdbdc005      0t0  TCP *:4000 (LISTEN)
Run Code Online (Sandbox Code Playgroud)

Curling0.0.0.0给出了来自服务器 1 的响应,服务器 1 绑定到localhost,因此显然这两个是冲突的。

然而,不知何故,这并没有引发人们预期的错误,EADDRINUSE,这怎么可能?

Lia*_*lly 5

节点在操作系统中的网络套接字上设置 SO_REUSEADDR 标志会导致此行为。REUSEADDR 标志与 IPARR_ANY(也称为 IPv4 的 0.0.0.0)地址有特殊的交互作用。从套接字手册页(信誉良好的来源):

   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.
Run Code Online (Sandbox Code Playgroud)

从进入这个确切问题的文章

有些人不喜欢 SO_REUSEADDR,因为它带有安全标签。在某些操作系统上,它允许不同进程同时在同一台机器上以不同的地址使用相同的端口。这是一个问题,因为大多数服务器绑定到端口,但它们不绑定到特定地址,而是使用 INADDR_ANY(这就是为什么在 netstat 输出中显示为 *.8080)。因此,如果服务器绑定到 *.8080,本地机器上的另一个恶意用户可以绑定到 local-machine.8080,因为它更具体,它将拦截您的所有连接。

我修改了一些Linux 测试代码来明确演示这一点(底部)。当你运行它时,你会得到以下输出:

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
Run Code Online (Sandbox Code Playgroud)

第一个测试用例在没有设置 REUSEADDR 标志的情况下在 IPADDR_ANY 地址上打开一个套接字,当尝试在环回上打开一个套接字时,'bind' 会抛出一个 EADDRINUSE 错误(正如您最初预期的那样)。第二个测试用例做同样的事情,但设置了 REUSEADDR 标志,并且第二个套接字的创建没有错误。

#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;
}
Run Code Online (Sandbox Code Playgroud)