Node.js сервер прослушивает один и тот же порт на 0.0.0.0 и localhost без ошибок - PullRequest
1 голос
/ 14 февраля 2020

Я наткнулся на что-то интересное, и я не могу объяснить это, и при этом я не нашел его в Google.

У меня есть один Express сервер, сервер 1, связанный с 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)

У меня есть еще один Express сервер, сервер 2, связанный со всеми интерфейсами на 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)

Керлинг 0.0.0.0 дает ответ от сервера 1, тот, который связан с localhost, поэтому ясно, что эти два противоречат.

Почему-то, однако, это не выдает ошибку, которую можно ожидать, EADDRINUSE, как это может быть?

Ответы [ 4 ]

3 голосов
/ 26 февраля 2020

Флаг SO_REUSEADDR устанавливается на сетевых сокетах в ОС узлом, вызывающим это поведение. Флаг REUSEADDR имеет специальное взаимодействие с адресом IPARR_ANY (он же 0.0.0.0 для IPv4). Из справочных страниц по сокету (авторитетный источник):

   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.

Из статьи , которая касается именно этой проблемы:

Некоторым людям не нравится SO_REUSEADDR, потому что к нему прикреплено клеймо безопасности. В некоторых операционных системах он позволяет использовать один и тот же порт с разными адресами на одном компьютере разными процессами в одно и то же время. Это проблема, потому что большинство серверов привязываются к порту, но они не привязываются к указанному адресу c, вместо этого они используют INADDR_ANY (именно поэтому в выводе netstat все отображается как * .8080). Таким образом, если сервер привязан к * .8080, другой злоумышленник на локальном компьютере может подключиться к local-machine.8080, который будет перехватывать все ваши соединения, поскольку он более точен c.

Я изменил некоторый 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

Первый тестовый случай открывает сокет на адресе IPADDR_ANY без установленного флага REUSEADDR, и при попытке открыть сокет в петле EADDRINUSE ошибка выдается 'bind' (как вы и ожидали). Второй тест выполняет то же самое, но с установленным флагом 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;
}
0 голосов
/ 27 февраля 2020

Здравствуйте, я думаю, что могу помочь вам в этом.

  • Сначала посмотрите разницу между 0.0.0.0 и localhost. Предположим, что если вы используете свой сервер на 0.0.0.0, это означает, что он будет запускать тот сервер, который доступен в то время, то есть сервер 1, потому что 0.0.0.0 - это ничто иное, как просто запуск сервера, доступного вам, поэтому 0.0.0.0 знать, что сервер 1 работает, поэтому он перенаправляет на сервер 1, потому что вы инициализировали на тот же порт.

  • Я имею в виду, если вы запустите 0.0.0.0:4000, он будет перенаправлен на localhost:4000, поскольку 0.0.0.0 не является хостом, но это адрес, используемый для ссылки на все IP-адреса на одном компьютере поэтому 0.0.0.0 относится к 127.0.0.1:4000 это обычный адрес обратной связи, а localhost:4000 - это имя хоста для 127.0.0.1:4000.

  • , здесь гораздо более простое объяснение: 0.0.0.0:4000 - -> 127.0.0.1:4000 ---> localhost:4000

0 голосов
/ 22 февраля 2020

Ядро в Windows позволяет нескольким приложениям совместно использовать порт, если URL уникален

возможно ли заставить nodejs сценарии прослушивать одно и то же порт

0 голосов
/ 14 февраля 2020

Вы слушаете один и тот же порт 4000 с вашими двумя серверами.

И если вы хотите запустить два сервера, вы должны явно установить два разных порта для каждого из серверов, как то так.

// 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'))

...