如何在Python中重置TCP套接字?

5
我有一个用Python编写的套接字代理,当它从一对通信对中收到RST时,将通过使套接字被垃圾回收来关闭两个对等体之间的连接。这导致另一个对等体看到了FIN而不是RST。
这意味着代理有效地将RST转换为FIN,我认为这并不理想。
我发现在Linux中可以通过使用地址族AF_UNSPEC调用connect来重置TCP连接。但我还没有找到从Python程序中执行此操作的方法。
如何在Python中连接到AF_UNSPEC地址?
到目前为止我尝试过查看相关connect方法的help输出,并找到了以下内容:
Help on built-in function connect:

connect(...)
    connect(address)

    Connect the socket to a remote address.  For IP sockets, the address
    is a pair (host, port).

很遗憾,这并没有告诉我构建AF_UNSPEC地址时address参数需要是什么。
我尝试用家族为AF_UNSPEC的新套接字对象包装原始套接字fd,如下所示:
socket.fromfd(s.fileno(), socket.AF_UNSPEC, 0)

生成的对象产生相同的帮助文本,任何尝试在新构建的套接字对象上调用connect的操作都会产生...

socket.error: getsockaddrarg: bad family

所以看起来使用socket.fromfd可能不是我问题的答案。

2个回答

2

查看当前的socket包在CPython中的实现,截至2019-01(即在Linux上重置连接),不存在真正的Pythonic方式来将套接字连接到AF_UNSPEC地址。

下一个最好的方法是在已接受的套接字上设置SO_LINGER选项(直接或通过继承)。当启用Linger(并将其设置为零超时)时,关闭套接字会导致连接被重置。

您必须小心地在正确的套接字API级别上设置SO_LINGER选项,并使用正确的编码选项值(它是一个结构体)。

例如:

import socket
import struct
import time

s = socket.socket(socket.AF_INET6)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
# if we want to inherit this option:
#s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0))
s.bind(('', 2323))
s.listen()
con, addr = s.accept()
con.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0))
time.sleep(1)
con.close()
time.sleep(3)

使用curl连接到此端口:

$ curl localhost:2323
curl: (56) Recv failure: Connection reset by peer

连接到此端口而不发送任何内容:

$ socat - tcp:localhost:2323

当使用例如以下命令转储数据包时:

$ tshark -i lo -f 'tcp port 2323'

最后一个数据包应该是RST(从服务器发送到客户端),在这两种情况下都是如此-例如:

39 9758.478140247    127.0.0.1 → 127.0.0.1    TCP 66 2323 → 34494 [RST, ACK]
        Seq=1 Ack=1 Win=43776 Len=0 TSval=2787120418 TSecr=2787119417

在你的示例中,我不得不修复一些错误才能运行。其中包括 TypeError: listen() takes exactly one argument (0 given)NameError: name 'struct' is not defined。一旦我修复了这些问题,我发现如果套接字上有尚未读取的数据,则会产生 RST,否则会产生 FIN。这与我没有使用 SO_LINGER 时看到的完全相同的行为。因此,似乎你收到 RST 的原因是 curl 发送了一个请求,但服务器没有读取它。但是当我想要产生 RST 时,我无法强制对等方向我发送更多数据。 - kasperd
@kasperd,我更新了我的答案。我添加了缺失的struct导入并修复了SO_LINGER选项值。启用延迟和零超时后,在两种情况下我都会收到RST。请注意,自Python 3.5以来,listen()的参数是可选的。 - maxschlepzig

0
你可以尝试使用 SO_LINGER 套接字选项(setsockopt),并将延迟时间设置为0。对于设置了0秒延迟时间的 SO_LINGER 套接字调用 close 将导致 RST 而不是 FIN。

我测试了c.setsockopt(socket.SOL_IP, socket.SO_LINGER, 0),但它没有起作用。连接仍然以我不想看到的FIN而不是RST关闭了。 - kasperd

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接