node.js - Node.js 服务器在 0.0.0.0 和 localhost 上侦听相同的端口而没有错误
问题描述
我偶然发现了一些有趣的东西,我无法解释它,谷歌搜索也没有成效。
我有一台 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)
Curling0.0.0.0
给出了来自服务器 1 的响应,即绑定到 的响应localhost
,因此很明显这两者是冲突的。
然而,不知何故,这不会引发人们期望的错误,EADDRINUSE,这怎么可能呢?
解决方案
Node 在 OS 中的网络套接字上设置了 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.
从一篇涉及这个确切问题的文章中:
有些人不喜欢 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
第一个测试用例在没有设置 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;
}
推荐阅读
- powershell - gitlab.ci.yml 中无法识别 Powershell 命令
- javascript - 通过网站(服务器)检测用户硬件
- php - php_sqlsrv_71_nts.dll 未加载。在 php 7.1.0 中配置 ms sql server
- entity-framework - 查询组合
- javascript - react es6 项目无法在 Safari 和 Internet Explorer 上运行
- html - 为什么我的导航栏组件在 Angular 中不起作用?
- javascript - 通过 JavaScript 将样式顶部添加到其他高度元素的 ID 元素
- django - 对产品进行分类
- java - 如何修复Firefox浏览器中的ajax调用,但在chrome浏览器中工作正常
- json - 使用 JQ 在树状 json 对象中沿每条路径收集所有根到叶值