如果没有 listen,能否建立 TCP 连接?

发表于 讨论求助 2023-09-02 06:50:23

原子化服务介绍

在 tcp 网络通信过程中,我们都是先有 server 端调用 listen 监听某个端口号,然后 client 向 server 发起连接请求,最终建立起连接。

在 tcp 网络通信过程中,我们都是先有 server 端调用 listen 监听某个端口号,然后 client 向 server 发起连接请求,最终建立起连接。

那么,要是没有一端进行监听,是否可以建立起 tcp 连接呢?

对于 tcp 来讲,一条链路的建立是通过三次握手来完成,而三次握手的过程是由内核完成的,显然三次握手应用程序是无法干扰的。

因此我们可能会想到使用原始套接字来接收 ip 报文,通过应用层来构造三次握手报文来完成三次握手的过程,但是该方法是行不通的,因为原始套接字在收到对端的回应报文 syn+ack 报文时,系统会自动给对端回应 rst 报文中断连接。 该现象原来的文章分析过,本文不在分析。若要实现套接字的三次握手成功,需要解决系统自动回应 rst 报文,比如通过 iptable 过滤掉 rst 报文。

在tcp的三次握手中,client 收到对端回应的 syn+ack 报文后,之所以能找到对应的套接口,是因为在 connect 时根据端口号把套接字加入到 tcp_hashinfo.ehash 的 hash 表中。而原始套接字会自动回复 rst 报文,就是没有在 hash 表中加入套接字,导致找不到套接字。

因此我们可以得到,只要避免找不到套接字就可以完成链路的建立。所以,两端同时打开套接字也可完成链路的建立,并不需要其中一端进行 listen。

同时连接

测试步骤就是2台机器各自绑定一个本地地址和端口号,然后同时向对端绑定的端口发送connect 请求,具体例子不再贴出。

在同时连接中,两端同时发送 syn 报文而进入 syn_sent 状态;当每一端收到 syn 后状态变为 syn_rcvd, 发送 syn 并对收到的 syn 进行确认;当双方都收到对端的 syn 及相应的 ack, 状态变迁为 established。状态变迁过程如下:

以上是两端互相 connect 完成链路的建立,若要去掉两端,我们也可以实现connect 本端绑定的ip和端口号。

使用 nc 测试一个 自己连接自己的 tcp 连接

# nc 10.115.20.30 1234 -p 1234
# netstat -anp | grep 1234
tcp 0 0 10.115.20.30:1234 10.115.20.30:1234 established 2050/nc

上述可以看到源端口号等于目的端口号,并且也完成链路的建立。

# strace nc 10.115.20.30 1234 -p 1234
execve("/usr/bin/nc", ["nc", "10.115.20.30", "1234", "-p", " 1234"], [/* 31 vars */]) = 0
brk(null) = 0x23d4000
mmap(null, 4096, prot_read|prot_write, map_private|map_anonymous, -1, 0) = 0x7f213f394000

...

munmap(0x7f213f393000, 4096) = 0
open("/usr/share/ncat/ca-bundle.crt", o_rdonly) = -1 enoent (no such file or directory)
socket(af_inet, sock_stream, ipproto_tcp) = 3
fcntl(3, f_getfl) = 0x2 (flags o_rdwr)
fcntl(3, f_setfl, o_rdwr|o_nonblock) = 0
setsockopt(3, sol_socket, so_reuseaddr, [1], 4) = 0
bind(3, {sa_family=af_inet, sin_port=htons(1234), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
//bind后直接就是connect,并没有进行listen
connect(3, {sa_family=af_inet, sin_port=htons(1234), sin_addr=inet_addr("10.115.20.30")}, 16) = -1 einprogress (operation now in progress)
select(4, [3], [3], [3], {10, 0}) = 1 (out [3], left {9, 999998})
getsockopt(3, sol_socket, so_error, [0], [4]) = 0
select(4, [0 3], [], [], null

从跟踪结果来看,自己连接自己(公用同一个socket)完成了链路的建立。

自己连接自己只是同时连接中的一个特例。

发表
26906人 签到看排名