Python 套接字窃取彼此的数据包
Python sockets stealing each other's packets
我正在尝试编写一个程序来并行测试各种大小的数据包的数据传输速度。不过,我注意到有些奇怪,根据我的程序,数据包的大小似乎对传输时间没有影响,而 Unix ping
二进制文件会在我使用的某些数据包大小上超时。我发送了 4 个包含字符串 'testquest' 的数据包和一个只有 2000 字节设置为 0 的数据包。但是,当我打印结果时,它们 all 包含 'testquest' (并且远短于 2000 字节)。我唯一可以得出的结论是,这些套接字都以某种方式接收到相同的数据包,这可以解释它们为何具有相同的 rtt。
我做了这个 MCVE 来说明这个问题(你可以忽略 'checksum' 函数,它包含在完整性中,但我从经验中知道它有效):
#!/usr/bin/env python3
import socket
import struct
import time
from multiprocessing.pool import ThreadPool as Pool
from sys import argv, byteorder
def calculate_checksum(pkt):
"""
Implementation of the "Internet Checksum" specified in RFC 1071 (https://tools.ieft.org/html/rfc1071)
Ideally this would act on the string as a series of 16-bit ints (host
packed), but this works.
Network data is big-endian, hosts are typically little-endian,
which makes this much more tedious than it needs to be.
"""
countTo = len(pkt) // 2 * 2
total, count = 0, 0
# Handle bytes in pairs (decoding as short ints)
loByte, hiByte = 0, 0
while count < countTo:
if (byteorder == "little"):
loByte = pkt[count]
hiByte = pkt[count + 1]
else:
loByte = pkt[count + 1]
hiByte = pkt[count]
total += hiByte * 256 + loByte
count += 2
# Handle last byte if applicable (odd-number of bytes)
# Endianness should be irrelevant in this case
if countTo < len(pkt): # Check for odd length
total += pkt[len(pkt) - 1]
total &= 0xffffffff # Truncate sum to 32 bits (a variance from ping.c, which
# uses signed ints, but overflow is unlikely in ping)
total = (total >> 16) + (total & 0xffff) # Add high 16 bits to low 16 bits
total += (total >> 16) # Add carry from above (if any)
return socket.htons((~total) & 0xffff)
def ping(args):
sock, payload = args[0], args[1]
header = struct.pack("!BBH", 8, 0, 0)
checksum = calculate_checksum(header+payload)
header = struct.pack("!BBH", 8, 0, checksum)
timestamp = time.time()
sock.send(header+payload)
try:
response = sock.recv(20+len(payload))
except socket.timeout:
return 0
return (len(response), (time.time() - timestamp) * 1000)
host = argv[1] # A host that doesn't respond to ping packets > 1500B
# 1 is ICMP protocol number
sockets = [socket.socket(socket.AF_INET, socket.SOCK_RAW, proto=1) for i in range(12)]
for i, sock in enumerate(sockets):
sock.settimeout(0.1)
sock.bind(("0.0.0.0", i))
sock.connect((host, 1)) # Port number should never matter for ICMP
args = [(sockets[i], bytes(2**i)) for i in range(12)]
for arg in args:
print(ping(arg))
arg[0].close()
这实际上向我展示了一些更麻烦的事情 - rtt 似乎随着数据包大小的增加实际上 减少 !调用这个程序(以 root 身份,获取套接字权限)输出:
0
0
(24, 15.784025192260742)
(28, 0.04601478576660156)
(28, 0.025033950805664062)
(28, 0.033855438232421875)
(28, 0.03528594970703125)
(28, 0.04887580871582031)
(28, 0.05316734313964844)
(28, 0.03790855407714844)
(28, 0.0209808349609375)
(28, 0.024080276489257812)
但现在请注意当我尝试使用 ping
:
发送大小为 2048 的数据包时会发生什么
user@mycomputer ~/src/connvitals $ time ping -c1 -s2048 $box
PING <hostname redacted> (<IP address redacted>): 2048 data bytes
--- <hostname redacted> ping statistics ---
1 packets transmitted, 0 packets received, 100.0% packet loss
real 0m11.018s
user 0m0.005s
sys 0m0.008s
不仅丢包,而且还耗时11秒!那么为什么 - 如果我的超时设置为 100 毫秒 - 这个数据包是否仅在 ~0.04 毫秒内从我的 python 脚本获得 "successful" 响应?
提前感谢您提供的任何帮助。
更新:
刚刚又查了下,好像是多套接字的问题,跟线程似乎没有关系。当我按顺序对每个套接字执行 ping 命令时,我遇到了同样的问题——然后立即关闭它。
您所有的套接字都是相同的,并且都绑定到同一主机。数据包中根本没有任何信息可以让内核知道去哪个套接字,raw(7) 似乎暗示所有套接字都会收到它们。
您可能会在所有讨论帖中收到所有回复,这意味着您在每个讨论帖中收到的回复数量是您预期的 12 倍。
我正在尝试编写一个程序来并行测试各种大小的数据包的数据传输速度。不过,我注意到有些奇怪,根据我的程序,数据包的大小似乎对传输时间没有影响,而 Unix ping
二进制文件会在我使用的某些数据包大小上超时。我发送了 4 个包含字符串 'testquest' 的数据包和一个只有 2000 字节设置为 0 的数据包。但是,当我打印结果时,它们 all 包含 'testquest' (并且远短于 2000 字节)。我唯一可以得出的结论是,这些套接字都以某种方式接收到相同的数据包,这可以解释它们为何具有相同的 rtt。
我做了这个 MCVE 来说明这个问题(你可以忽略 'checksum' 函数,它包含在完整性中,但我从经验中知道它有效):
#!/usr/bin/env python3
import socket
import struct
import time
from multiprocessing.pool import ThreadPool as Pool
from sys import argv, byteorder
def calculate_checksum(pkt):
"""
Implementation of the "Internet Checksum" specified in RFC 1071 (https://tools.ieft.org/html/rfc1071)
Ideally this would act on the string as a series of 16-bit ints (host
packed), but this works.
Network data is big-endian, hosts are typically little-endian,
which makes this much more tedious than it needs to be.
"""
countTo = len(pkt) // 2 * 2
total, count = 0, 0
# Handle bytes in pairs (decoding as short ints)
loByte, hiByte = 0, 0
while count < countTo:
if (byteorder == "little"):
loByte = pkt[count]
hiByte = pkt[count + 1]
else:
loByte = pkt[count + 1]
hiByte = pkt[count]
total += hiByte * 256 + loByte
count += 2
# Handle last byte if applicable (odd-number of bytes)
# Endianness should be irrelevant in this case
if countTo < len(pkt): # Check for odd length
total += pkt[len(pkt) - 1]
total &= 0xffffffff # Truncate sum to 32 bits (a variance from ping.c, which
# uses signed ints, but overflow is unlikely in ping)
total = (total >> 16) + (total & 0xffff) # Add high 16 bits to low 16 bits
total += (total >> 16) # Add carry from above (if any)
return socket.htons((~total) & 0xffff)
def ping(args):
sock, payload = args[0], args[1]
header = struct.pack("!BBH", 8, 0, 0)
checksum = calculate_checksum(header+payload)
header = struct.pack("!BBH", 8, 0, checksum)
timestamp = time.time()
sock.send(header+payload)
try:
response = sock.recv(20+len(payload))
except socket.timeout:
return 0
return (len(response), (time.time() - timestamp) * 1000)
host = argv[1] # A host that doesn't respond to ping packets > 1500B
# 1 is ICMP protocol number
sockets = [socket.socket(socket.AF_INET, socket.SOCK_RAW, proto=1) for i in range(12)]
for i, sock in enumerate(sockets):
sock.settimeout(0.1)
sock.bind(("0.0.0.0", i))
sock.connect((host, 1)) # Port number should never matter for ICMP
args = [(sockets[i], bytes(2**i)) for i in range(12)]
for arg in args:
print(ping(arg))
arg[0].close()
这实际上向我展示了一些更麻烦的事情 - rtt 似乎随着数据包大小的增加实际上 减少 !调用这个程序(以 root 身份,获取套接字权限)输出:
0
0
(24, 15.784025192260742)
(28, 0.04601478576660156)
(28, 0.025033950805664062)
(28, 0.033855438232421875)
(28, 0.03528594970703125)
(28, 0.04887580871582031)
(28, 0.05316734313964844)
(28, 0.03790855407714844)
(28, 0.0209808349609375)
(28, 0.024080276489257812)
但现在请注意当我尝试使用 ping
:
user@mycomputer ~/src/connvitals $ time ping -c1 -s2048 $box
PING <hostname redacted> (<IP address redacted>): 2048 data bytes
--- <hostname redacted> ping statistics ---
1 packets transmitted, 0 packets received, 100.0% packet loss
real 0m11.018s
user 0m0.005s
sys 0m0.008s
不仅丢包,而且还耗时11秒!那么为什么 - 如果我的超时设置为 100 毫秒 - 这个数据包是否仅在 ~0.04 毫秒内从我的 python 脚本获得 "successful" 响应?
提前感谢您提供的任何帮助。
更新:
刚刚又查了下,好像是多套接字的问题,跟线程似乎没有关系。当我按顺序对每个套接字执行 ping 命令时,我遇到了同样的问题——然后立即关闭它。
您所有的套接字都是相同的,并且都绑定到同一主机。数据包中根本没有任何信息可以让内核知道去哪个套接字,raw(7) 似乎暗示所有套接字都会收到它们。
您可能会在所有讨论帖中收到所有回复,这意味着您在每个讨论帖中收到的回复数量是您预期的 12 倍。