侦听已被列为已建立连接的源端口的端口?

侦听已被列为已建立连接的源端口的端口?

如果本地地址为 127.0.0.1:45000 的连接处于 ESTABLISHED 状态,本地进程是否可以在同一端口 45000 上绑定和侦听,或者该端口是否会被视为已在使用中并阻止绑定请求?

答案1

设置为 时将会bind失败。如果你真的想这样做,你必须或使用其他数据包捕获方法来查看发生了什么。进程上的信息以及其他内核诊断也是一个选项。errnoEINVALtcpdumpstrace

答案2

完整的故事是“这取决于你的意思”。如果您想监听与您无法控制的现有程序完全相同的 IP 和端口,那么正如其他人所指出的那样,您可能会不走运。但是,如果您:

  • 可以监听不同的IP地址,或者
  • 对原始应用程序有一定的控制权

那么您可能会对本文的其余部分感兴趣。

相同端口,不同 Internet 协议 (IP) 地址

当程序使用绑定(2)为套接字分配地址的系统调用,该地址(在 AF_INET 套接字的情况下)指定端口和 IP 地址。因此,端口相同但IP地址不同的两个地址是不同的,可以分别分配,不会发生冲突。例如,使用索卡特我可以在一个 shell 中绑定到环回接口 IP 地址上的端口 9000:

 socat TCP4-LISTEN:9000,bind=127.0.0.1 STDOUT

并绑定到相同的端口,但在另一个端口上的我的外部IP地址:

socat TCP4-LISTEN:9000,bind=10.0.2.15 STDOUT

两个进程都可以接受来自它们正在侦听的 IP 地址和端口的连接。但请注意,如果有人正在侦听通配符地址 0.0.0.0,则您无法绑定到更具体的地址,因为第一个进程绑定到系统上的每个 IP。

相同端口、相同IP

默认情况下,两个进程不能将两个不同的文件描述符绑定到同一地址。在 Linux 上,尝试这样做将导致 EADDRINUSE 从以下位置返回bind(2)

socat TCP4-LISTEN:9000,bind=127.0.0.1 STDOUT
2014/11/07 00:10:13 socat[21202] E bind(3, {AF=2 127.0.0.1:9000}, 16): Address already in use

鉴于您的问题和后续情况,我猜您对当前使用所需端口的程序没有太多控制权。但是,如果您这样做了,则一个进程可能在端口 + IP 地址上建立了连接,而另一个进程正在侦听同一地址。例如,许多服务器应用程序执行以下操作:

  • 有一个主进程bind()、listen()和accept()连接
  • 分叉一个新进程来处理已接受的连接,主进程返回尝试accept()任何新的传入连接。

在这种情况下,您将看到一个子进程在该端口上具有 ESTABLISHED 连接,而父进程在同一端口上具有 LISTENing 套接字。

在最近的 Linux 内核中,两个完全不相关的进程可以使用 SO_REUSEPORT 套接字选项绑定到同一地址。如果一个进程在套接字上设置了 SO_REUSEPORT 选项,那么与第一个进程具有相同有效 UID 的其他进程也可以设置 SO_REUSEPORT 选项并绑定到相同的地址。

不幸的是,我的版本socat似乎有一个错误,使得很难给出一个简单的 TCP 示例;然而,我在下面提供了一个简短且写得不好的示例程序。如果您在两个不同的 shell 中以同一用户身份运行该程序,则两者都会毫无问题地进行 bind() :

State    Recv-Q Send-Q  Local Address:Port  Peer Address:Port
LISTEN   0      128     127.0.0.1:9000        *:*     users:(("bind_tcp_reusep",pid=21254,fd=3))
LISTEN   0    128 127.0.0.1:9000           *:*      users:(("bind_tcp_reusep",pid=21253,fd=3))

这样做的目的是为编写网络服务器的人们提供一种新工具来创建可以处理大量并发连接的应用程序。

以下 LWN 文章很好地概述了此选项的用例:

http://lwn.net/Articles/542629/

查看已建立连接上的流量

正如 robbat2 提到的,如果你想监视现有流量tcpdump是最好的选择。

SO_REUSEPORT 程序示例

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <string.h>
#include <errno.h>


int main() {
  struct sockaddr_in bind_addr;
  struct sockaddr peer_addr;
  int optval = 1;
  int tcp_socket;
  int err;
  int addr_len = sizeof(struct sockaddr);

  memset(&bind_addr, 0, sizeof(struct sockaddr_in));
  memset(&peer_addr, 0, sizeof(struct sockaddr));
  bind_addr.sin_family = AF_INET;
  bind_addr.sin_port = htons(9000);

  if (inet_pton(AF_INET, "127.0.0.1", &(bind_addr.sin_addr)) != 1) {
    perror("inet_pton");
    exit(1);
  }

  tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
  setsockopt(tcp_socket, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval));
  err = bind(tcp_socket, (const struct sockaddr *)&bind_addr, sizeof(struct sockaddr));

  if (err != 0) {
    perror("bind failed");
    exit(1);
  }

  err = listen(tcp_socket, 256);
  if (err != 0) {
    perror("listen failed");
    exit(1);
  }
  accept(tcp_socket, &peer_addr, &addr_len);
}

答案3

我自己尝试了这一点,启动本地 Tomcat 服务器并调整配置以绑定到已列为已建立连接的源地址的端口。它允许服务器绑定到处于 LISTEN 状态的端口,同时存在另一个 ESTABLISHED 连接已使用该端口作为本地地址。

相关内容