无法使用Python向本地端口发送UDP数据包,WireShark显示校验和失败
我最近为这个问题烦恼不已。我正在用Python写一个SOCKS5服务器,目的是为了转发UDP流量。我绑定了一个端口,数据接收得很好。接着,我解析SOCKS5的UDP头(这和普通的UDP头不一样),然后把数据包转发到请求的目标。
一切都很顺利。然后我开始监听目标的响应(如果超时就重新发送),最终收到了响应。太好了!
但是接下来让我抓狂的事情发生了——
我从目标那里收到了数据包。我根据SOCKS5的标准重新封装这个返回的数据包,和之前的UDP头一样,只不过这次我把目标地址和端口改成了最初请求的客户端。我使用:
sock_client = socket.socket(socket.AF_INET, # Internet
socket.SOCK_DGRAM) # UDP
sock_client.sendto(packed_datagram, (self.client_ip, self.client_port))
把数据包发送回客户端。但是客户端根本收不到回复!从来没有收到过!
我用WireShark查看,发现它显示:头部校验和:0x0000 [不正确,应该是0xcd1f(可能是因为“IP校验和卸载”?)]
难道Python的socket实现,在设置了socket.DGRAM的情况下,不应该自动正确打包我的数据并计算合适的校验和吗?为什么会被设置成0x0000?我检查了负载的十六进制,校验和确实是错的。这到底是怎么回事?
4 个回答
使用Linux命令关闭传输校验和:
ethtool -K eth0 tx off
或者使用这个功能来计算校验和
def checksum(data):
s = 0
n = len(data) % 2
for i in range(0, len(data)-n, 2):
s+= ord(data[i]) + (ord(data[i+1]) << 8)
if n:
s+= ord(data[i+1])
while (s >> 16):
s = (s & 0xFFFF) + (s >> 16)
s = ~s & 0xffff
return s
这里的数据是伪头部
如果我理解你的代码没错,你是创建了一个新的连接(socket)来把数据发送回客户端。这样的话,这个新的连接会有一个随机的源IP地址。也就是说,从客户端的角度来看,数据包的流动是这样的:
client_ip:client_port -> socks5_ip:socks_port
client_ip:client_port <- socks5_ip:random_port
客户端可能已经和socks5服务器建立了连接,所以它期待收到的回复是来自socks5_ip:socks_port,而不是随机的端口。因此,你不应该为客户端创建一个新的连接,而是应该使用你从客户端接收到数据时所用的那个现有连接来回复。
校验和的计算是由操作系统中的驱动程序来完成的。在很多情况下,这个计算是由网络卡自己来做的。如果我没记错的话,Wireshark会在数据包被送到网络处理程序之前抓取本地的数据包。对于所有本地生成的数据包,出现校验和错误是很常见的。
我之前没有准确遵循SOCKS5协议的两个部分。
当UDP关联结束时,意味着接收UDP
ASSOCIATE请求的TCP连接也结束了。
我在完成UDP中继握手后就立即关闭了TCP套接字。其实TCP连接应该保持打开,直到所有的数据交换完成。
当UDP中继服务器从远程主机收到回复数据包时,它必须使用上面的UDP请求头来封装这个数据包,并根据认证方法进行相应的封装。
我之前使用的是客户端的IP和端口作为数据包中的值。实际上应该使用远程服务器的IP和端口,简单来说,UDP头的封装就是客户端传递的内容的一个复制。
解决了这两个问题后,SOCKS5服务器就能按预期工作了。